import type { Logger } from "tslog"; export interface TraceContext { traceId: string; spanId: string; parentSpanId?: string; operation: string; // So that we don't violate open closed principle, we allow meta to be added to the trace context meta?: Record; } export interface IdGenerator { generate: () => string; } export class DistributedTracing { constructor( private idGenerator: IdGenerator, private loggerInstance: Logger ) {} createTrace(operation: string, context?: Partial): TraceContext { const { traceId, spanId, meta, ...otherContext } = context || {}; return { ...otherContext, traceId: traceId || `trace_${this.idGenerator.generate()}`, spanId: `span_${this.idGenerator.generate()}`, parentSpanId: spanId, operation, meta, }; } createSpan( parentContext: TraceContext, operation: string, additionalMeta?: Record ): TraceContext { const mergedMeta = { ...parentContext.meta, ...additionalMeta, }; return { ...parentContext, spanId: `span_${this.idGenerator.generate()}`, parentSpanId: parentContext.spanId, operation, meta: mergedMeta, }; } getTracingLogger( context: TraceContext, additionalMeta?: Record ) { const mergedMeta = { ...context.meta, ...additionalMeta, }; const prefixes = [ "distributed-trace", `trace:${context.traceId}`, `span:${context.spanId}`, `op:${context.operation}`, ]; if (mergedMeta) { Object.entries(mergedMeta).forEach(([key, value]) => { if (value !== undefined && value !== null) { prefixes.push(`${key}:${value}`); } }); } return this.loggerInstance.getSubLogger({ prefix: prefixes }); } extractTraceFromPayload(payload: string): TraceContext | null { try { const parsed = JSON.parse(payload); if (parsed._traceContext && parsed._traceContext.traceId) { return parsed._traceContext; } } catch {} return null; } injectTraceIntoPayload(payload: any, traceContext?: TraceContext): any { if (!traceContext) return payload; return { ...payload, _traceContext: traceContext, }; } updateTrace( traceContext: TraceContext, additionalMeta?: Record ): TraceContext { return { ...traceContext, meta: { ...traceContext.meta, ...additionalMeta, }, }; } }