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>
This commit is contained in:
73
apps/api/src/telemetry/span-context.service.ts
Normal file
73
apps/api/src/telemetry/span-context.service.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user