55
66package io .opentelemetry .instrumentation .log4j .appender .v2_17 ;
77
8+ import static io .opentelemetry .instrumentation .log4j .appender .v2_17 .internal .ContextDataKeys .OTEL_CONTEXT_DATA_KEY ;
89import static java .util .Collections .emptyList ;
910import static java .util .concurrent .TimeUnit .MILLISECONDS ;
1011import static java .util .concurrent .TimeUnit .NANOSECONDS ;
5253import org .apache .logging .log4j .core .config .plugins .PluginBuilderFactory ;
5354import org .apache .logging .log4j .core .time .Instant ;
5455import org .apache .logging .log4j .message .MapMessage ;
56+ import org .apache .logging .log4j .status .StatusLogger ;
5557import org .apache .logging .log4j .util .ReadOnlyStringMap ;
5658
5759@ Plugin (
@@ -67,8 +69,10 @@ public class OpenTelemetryAppender extends AbstractAppender {
6769
6870 private final BlockingQueue <LogEventToReplay > eventsToReplay ;
6971 private final AtomicBoolean replayLimitWarningLogged = new AtomicBoolean ();
72+ private final AtomicBoolean legacyContextDataWarningLogged = new AtomicBoolean ();
7073 private final ReadWriteLock lock = new ReentrantReadWriteLock ();
7174 private final boolean captureCodeAttributes ;
75+ private final boolean v3Preview ;
7276
7377 /**
7478 * Installs the {@code openTelemetry} instance on any {@link OpenTelemetryAppender}s identified in
@@ -225,16 +229,16 @@ private OpenTelemetryAppender(
225229 boolean v3Preview = commonConfig .getBoolean ("v3_preview" , false );
226230
227231 this .mapper =
228- new LogEventMapper <>(
229- ContextDataAccessorImpl .INSTANCE ,
232+ createMapper (
230233 captureExperimentalAttributes ,
231234 captureCodeAttributes ,
232235 captureMapMessageAttributes ,
233236 captureMarkerAttribute ,
234- splitAndFilterBlanksAndNulls ( captureContextDataAttributes ) ,
237+ captureContextDataAttributes ,
235238 v3Preview );
236239 this .openTelemetry = openTelemetry ;
237240 this .captureCodeAttributes = captureCodeAttributes ;
241+ this .v3Preview = v3Preview ;
238242 if (numLogsCapturedBeforeOtelInstall != 0 ) {
239243 this .eventsToReplay = new ArrayBlockingQueue <>(numLogsCapturedBeforeOtelInstall );
240244 } else {
@@ -252,6 +256,23 @@ private static List<String> splitAndFilterBlanksAndNulls(@Nullable String value)
252256 .collect (toList ());
253257 }
254258
259+ private static LogEventMapper <ReadOnlyStringMap > createMapper (
260+ boolean captureExperimentalAttributes ,
261+ boolean captureCodeAttributes ,
262+ boolean captureMapMessageAttributes ,
263+ boolean captureMarkerAttribute ,
264+ @ Nullable String captureContextDataAttributes ,
265+ boolean v3Preview ) {
266+ return new LogEventMapper <>(
267+ ContextDataAccessorImpl .INSTANCE ,
268+ captureExperimentalAttributes ,
269+ captureCodeAttributes ,
270+ captureMapMessageAttributes ,
271+ captureMarkerAttribute ,
272+ splitAndFilterBlanksAndNulls (captureContextDataAttributes ),
273+ v3Preview );
274+ }
275+
255276 /**
256277 * Configures the {@link OpenTelemetry} used to append logs. This MUST be called for the appender
257278 * to function. See {@link #install(OpenTelemetry)} for simple installation option.
@@ -280,6 +301,7 @@ private void resetAppenderForTest() {
280301 openTelemetry = null ;
281302 eventsToReplay .clear ();
282303 replayLimitWarningLogged .set (false );
304+ legacyContextDataWarningLogged .set (false );
283305 } finally {
284306 writeLock .unlock ();
285307 }
@@ -325,28 +347,7 @@ private void emit(OpenTelemetry openTelemetry, LogEvent event) {
325347 LogRecordBuilder builder =
326348 openTelemetry .getLogsBridge ().loggerBuilder (instrumentationName ).build ().logRecordBuilder ();
327349 ReadOnlyStringMap contextData = event .getContextData ();
328- Context context = Context .current ();
329- // when using async logger we'll be executing on a different thread than what started logging
330- // reconstruct the context from context data
331- if (context == Context .root ()) {
332- ContextDataAccessor <ReadOnlyStringMap > contextDataAccessor = ContextDataAccessorImpl .INSTANCE ;
333- ContextDataKeys contextDataKeys = ContextDataKeys .create (openTelemetry );
334- String traceId = contextDataAccessor .getValue (contextData , contextDataKeys .getTraceIdKey ());
335- String spanId = contextDataAccessor .getValue (contextData , contextDataKeys .getSpanIdKey ());
336- String traceFlags =
337- contextDataAccessor .getValue (contextData , contextDataKeys .getTraceFlags ());
338- if (traceId != null && spanId != null && traceFlags != null ) {
339- context =
340- Context .root ()
341- .with (
342- Span .wrap (
343- SpanContext .create (
344- traceId ,
345- spanId ,
346- TraceFlags .fromHex (traceFlags , 0 ),
347- TraceState .getDefault ())));
348- }
349- }
350+ Context context = getContext (openTelemetry , event , contextData );
350351
351352 mapper .mapLogEvent (
352353 builder ,
@@ -369,18 +370,85 @@ private void emit(OpenTelemetry openTelemetry, LogEvent event) {
369370 builder .emit ();
370371 }
371372
373+ private Context getContext (
374+ OpenTelemetry openTelemetry , LogEvent event , ReadOnlyStringMap contextData ) {
375+ Object context = contextData .getValue (OTEL_CONTEXT_DATA_KEY );
376+ if (context instanceof Context ) {
377+ return (Context ) context ;
378+ }
379+ Context currentContext = Context .current ();
380+ if (currentContext != Context .root ()) {
381+ return currentContext ;
382+ }
383+
384+ if (!v3Preview ) {
385+ // when using async logger we'll be executing on a different thread than what started logging
386+ // reconstruct the context from context data
387+ ContextDataAccessor <ReadOnlyStringMap > contextDataAccessor = ContextDataAccessorImpl .INSTANCE ;
388+ ContextDataKeys contextDataKeys = ContextDataKeys .create (openTelemetry );
389+ String traceId = contextDataAccessor .getValue (contextData , contextDataKeys .getTraceIdKey ());
390+ String spanId = contextDataAccessor .getValue (contextData , contextDataKeys .getSpanIdKey ());
391+ String traceFlags =
392+ contextDataAccessor .getValue (contextData , contextDataKeys .getTraceFlags ());
393+ if (traceId != null && spanId != null && traceFlags != null ) {
394+ warnIfUsingLegacyContextDataForAsyncLoggers (event );
395+ return Context .root ()
396+ .with (
397+ Span .wrap (
398+ SpanContext .create (
399+ traceId ,
400+ spanId ,
401+ TraceFlags .fromHex (traceFlags , 0 ),
402+ TraceState .getDefault ())));
403+ }
404+ }
405+ return currentContext ;
406+ }
407+
408+ private void warnIfUsingLegacyContextDataForAsyncLoggers (LogEvent event ) {
409+ if (!wasLoggedOnDifferentThread (event )) {
410+ return ;
411+ }
412+ if (legacyContextDataWarningLogged .getAndSet (true )) {
413+ return ;
414+ }
415+ StatusLogger .getLogger ()
416+ .warn (
417+ "OpenTelemetry Log4j appender is recovering span context from Log4j context data "
418+ + "for an event logged on another thread. This compatibility behavior only "
419+ + "propagates span context and will be removed in 3.0. Configure "
420+ + "log4j2.ContextDataInjector="
421+ + OpenTelemetryAppenderContextDataInjector .class .getName ()
422+ + " to propagate the full OpenTelemetry Context for async loggers." );
423+ }
424+
425+ private static boolean wasLoggedOnDifferentThread (LogEvent event ) {
426+ long eventThreadId = event .getThreadId ();
427+ // Only treat this as async handoff when Log4j captured a usable, different thread id.
428+ return eventThreadId > 0 && eventThreadId != Thread .currentThread ().getId ();
429+ }
430+
372431 private enum ContextDataAccessorImpl implements ContextDataAccessor <ReadOnlyStringMap > {
373432 INSTANCE ;
374433
375434 @ Override
376435 @ Nullable
377436 public String getValue (ReadOnlyStringMap contextData , String key ) {
378- return contextData .getValue (key );
437+ Object value = contextData .getValue (key );
438+ if (value instanceof String ) {
439+ return (String ) value ;
440+ }
441+ return null ;
379442 }
380443
381444 @ Override
382445 public void forEach (ReadOnlyStringMap contextData , BiConsumer <String , String > action ) {
383- contextData .forEach (action ::accept );
446+ contextData .forEach (
447+ (key , value ) -> {
448+ if (value instanceof String ) {
449+ action .accept (key , (String ) value );
450+ }
451+ });
384452 }
385453 }
386454}
0 commit comments