Files
stack/apps/api/src/telemetry/span-context.service.ts
Jason Woltje 51e6ad0792 feat(#131): add OpenTelemetry tracing infrastructure
Implement comprehensive distributed tracing for HTTP requests and LLM
operations using OpenTelemetry with GenAI semantic conventions.

Features:
- TelemetryService: SDK initialization with OTLP HTTP exporter
- TelemetryInterceptor: Automatic HTTP request spans
- @TraceLlmCall decorator: LLM operation tracing
- GenAI semantic conventions for model/token tracking
- Graceful degradation when tracing disabled

Instrumented:
- All HTTP requests (automatic spans)
- OllamaProvider chat/chatStream/embed operations
- Token counts, model names, durations

Environment:
- OTEL_ENABLED (default: true)
- OTEL_SERVICE_NAME (default: mosaic-api)
- OTEL_EXPORTER_OTLP_ENDPOINT (default: localhost:4318)

Tests: 23 passing with full coverage

Fixes #131

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 12:55:11 -06:00

74 lines
1.9 KiB
TypeScript

import { Injectable } from "@nestjs/common";
import { context, trace, type Span, type Context } from "@opentelemetry/api";
/**
* Service for managing OpenTelemetry span context propagation.
* Provides utilities for accessing and manipulating the active trace context.
*
* @example
* ```typescript
* const activeSpan = spanContextService.getActiveSpan();
* if (activeSpan) {
* activeSpan.setAttribute('custom.key', 'value');
* }
* ```
*/
@Injectable()
export class SpanContextService {
/**
* Get the currently active span from the context.
*
* @returns The active span, or undefined if no span is active
*/
getActiveSpan(): Span | undefined {
return trace.getActiveSpan();
}
/**
* Get the current trace context.
*
* @returns The current context
*/
getContext(): Context {
return context.active();
}
/**
* Execute a function within a specific context.
*
* @param ctx - The context to run the function in
* @param fn - The function to execute
* @returns The result of the function
*
* @example
* ```typescript
* const result = spanContextService.with(customContext, () => {
* // This code runs with customContext active
* return doSomething();
* });
* ```
*/
with<T>(ctx: Context, fn: () => T): T {
return context.with(ctx, fn);
}
/**
* Set a span as active for the duration of a function execution.
*
* @param span - The span to make active
* @param fn - The function to execute with the active span
* @returns The result of the function
*
* @example
* ```typescript
* const result = await spanContextService.withActiveSpan(span, async () => {
* // This code runs with span active
* return await doAsyncWork();
* });
* ```
*/
withActiveSpan<T>(span: Span, fn: () => T): T {
return context.with(trace.setSpan(context.active(), span), fn);
}
}