11import { DiagConsoleLogger , DiagLogLevel , TracerProvider , diag } from "@opentelemetry/api" ;
2+ import { RandomIdGenerator } from "@opentelemetry/sdk-trace-base" ;
23import { logs } from "@opentelemetry/api-logs" ;
34import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http" ;
45import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http" ;
@@ -15,6 +16,8 @@ import {
1516import {
1617 BatchLogRecordProcessor ,
1718 LoggerProvider ,
19+ LogRecordExporter ,
20+ ReadableLogRecord ,
1821 SimpleLogRecordProcessor ,
1922} from "@opentelemetry/sdk-logs" ;
2023import {
@@ -87,9 +90,12 @@ export type TracingSDKConfig = {
8790 resource ?: IResource ;
8891 instrumentations ?: Instrumentation [ ] ;
8992 exporters ?: SpanExporter [ ] ;
93+ logExporters ?: LogRecordExporter [ ] ;
9094 diagLogLevel ?: TracingDiagnosticLogLevel ;
9195} ;
9296
97+ const idGenerator = new RandomIdGenerator ( ) ;
98+
9399export class TracingSDK {
94100 public readonly asyncResourceDetector = new AsyncResourceDetector ( ) ;
95101 private readonly _logProvider : LoggerProvider ;
@@ -158,7 +164,7 @@ export class TracingSDK {
158164 )
159165 ) ;
160166
161- const externalTraceId = crypto . randomUUID ( ) ;
167+ const externalTraceId = idGenerator . generateTraceId ( ) ;
162168
163169 for ( const exporter of config . exporters ?? [ ] ) {
164170 traceProvider . addSpanProcessor (
@@ -210,6 +216,28 @@ export class TracingSDK {
210216 )
211217 ) ;
212218
219+ for ( const externalLogExporter of config . logExporters ?? [ ] ) {
220+ loggerProvider . addLogRecordProcessor (
221+ getEnvVar ( "OTEL_BATCH_PROCESSING_ENABLED" ) === "1"
222+ ? new BatchLogRecordProcessor (
223+ new ExternalLogRecordExporterWrapper ( externalLogExporter , externalTraceId ) ,
224+ {
225+ maxExportBatchSize : parseInt ( getEnvVar ( "OTEL_LOG_MAX_EXPORT_BATCH_SIZE" ) ?? "64" ) ,
226+ scheduledDelayMillis : parseInt (
227+ getEnvVar ( "OTEL_LOG_SCHEDULED_DELAY_MILLIS" ) ?? "200"
228+ ) ,
229+ exportTimeoutMillis : parseInt (
230+ getEnvVar ( "OTEL_LOG_EXPORT_TIMEOUT_MILLIS" ) ?? "30000"
231+ ) ,
232+ maxQueueSize : parseInt ( getEnvVar ( "OTEL_LOG_MAX_QUEUE_SIZE" ) ?? "512" ) ,
233+ }
234+ )
235+ : new SimpleLogRecordProcessor (
236+ new ExternalLogRecordExporterWrapper ( externalLogExporter , externalTraceId )
237+ )
238+ ) ;
239+ }
240+
213241 this . _logProvider = loggerProvider ;
214242 this . _spanExporter = spanExporter ;
215243 this . _traceProvider = traceProvider ;
@@ -306,3 +334,50 @@ class ExternalSpanExporterWrapper {
306334 : Promise . resolve ( ) ;
307335 }
308336}
337+
338+ class ExternalLogRecordExporterWrapper {
339+ constructor (
340+ private underlyingExporter : LogRecordExporter ,
341+ private externalTraceId : string
342+ ) { }
343+
344+ export ( logs : any [ ] , resultCallback : ( result : any ) => void ) : void {
345+ const modifiedLogs = logs . map ( this . transformLogRecord . bind ( this ) ) ;
346+
347+ this . underlyingExporter . export ( modifiedLogs , resultCallback ) ;
348+ }
349+
350+ shutdown ( ) : Promise < void > {
351+ return this . underlyingExporter . shutdown ( ) ;
352+ }
353+
354+ transformLogRecord ( logRecord : ReadableLogRecord ) : ReadableLogRecord {
355+ // If there's no spanContext, or if the externalTraceId is not set, return the original logRecord.
356+ if ( ! logRecord . spanContext || ! this . externalTraceId ) {
357+ return logRecord ;
358+ }
359+
360+ // Capture externalTraceId for use within the proxy's scope.
361+ const { externalTraceId } = this ;
362+
363+ return new Proxy ( logRecord , {
364+ get ( target , prop , receiver ) {
365+ if ( prop === "spanContext" ) {
366+ // Intercept access to spanContext.
367+ const originalSpanContext = target . spanContext ;
368+ // Ensure originalSpanContext exists (it should, due to the check above, but good for safety).
369+ if ( originalSpanContext ) {
370+ return {
371+ ...originalSpanContext ,
372+ traceId : externalTraceId , // Override traceId.
373+ } ;
374+ }
375+ // Fallback if, for some reason, originalSpanContext is undefined here.
376+ return undefined ;
377+ }
378+ // For all other properties, defer to the original object.
379+ return Reflect . get ( target , prop , receiver ) ;
380+ } ,
381+ } ) ;
382+ }
383+ }
0 commit comments