2323import io .opentelemetry .semconv .ExceptionAttributes ;
2424import java .io .PrintWriter ;
2525import java .io .StringWriter ;
26+ import java .lang .reflect .Field ;
2627import java .util .ArrayList ;
2728import java .util .Arrays ;
29+ import java .util .Iterator ;
2830import java .util .List ;
2931import java .util .Map ;
3032import java .util .concurrent .TimeUnit ;
3133import java .util .stream .Collectors ;
34+ import javax .annotation .Nullable ;
35+ import net .logstash .logback .marker .LogstashMarker ;
36+ import net .logstash .logback .marker .MapEntriesAppendingMarker ;
37+ import net .logstash .logback .marker .SingleFieldAppendingMarker ;
3238import org .slf4j .Marker ;
3339import org .slf4j .event .KeyValuePair ;
3440
@@ -50,6 +56,7 @@ public final class LoggingEventMapper {
5056 private static final boolean supportsInstant = supportsInstant ();
5157 private static final boolean supportsKeyValuePairs = supportsKeyValuePairs ();
5258 private static final boolean supportsMultipleMarkers = supportsMultipleMarkers ();
59+ private static final boolean supportsLogstashMarkers = supportsLogstashMarkers ();
5360 private static final Cache <String , AttributeKey <String >> mdcAttributeKeys = Cache .bounded (100 );
5461 private static final Cache <String , AttributeKey <String >> attributeKeys = Cache .bounded (100 );
5562
@@ -60,6 +67,8 @@ public final class LoggingEventMapper {
6067 private static final AttributeKey <List <String >> LOG_BODY_PARAMETERS =
6168 AttributeKey .stringArrayKey ("log.body.parameters" );
6269
70+ private static final Cache <Class <?>, Field > valueField = Cache .bounded (20 );
71+
6372 private final boolean captureExperimentalAttributes ;
6473 private final List <String > captureMdcAttributes ;
6574 private final boolean captureAllMdcAttributes ;
@@ -68,6 +77,7 @@ public final class LoggingEventMapper {
6877 private final boolean captureKeyValuePairAttributes ;
6978 private final boolean captureLoggerContext ;
7079 private final boolean captureArguments ;
80+ private final boolean captureLogstashAttributes ;
7181
7282 private LoggingEventMapper (Builder builder ) {
7383 this .captureExperimentalAttributes = builder .captureExperimentalAttributes ;
@@ -77,6 +87,7 @@ private LoggingEventMapper(Builder builder) {
7787 this .captureKeyValuePairAttributes = builder .captureKeyValuePairAttributes ;
7888 this .captureLoggerContext = builder .captureLoggerContext ;
7989 this .captureArguments = builder .captureArguments ;
90+ this .captureLogstashAttributes = builder .captureLogstashAttributes ;
8091 this .captureAllMdcAttributes =
8192 builder .captureMdcAttributes .size () == 1 && builder .captureMdcAttributes .get (0 ).equals ("*" );
8293 }
@@ -170,7 +181,8 @@ private void mapLoggingEvent(
170181 }
171182
172183 if (captureMarkerAttribute ) {
173- captureMarkerAttribute (attributes , loggingEvent );
184+ boolean skipLogstashMarkers = supportsLogstashMarkers && captureLogstashAttributes ;
185+ captureMarkerAttribute (attributes , loggingEvent , skipLogstashMarkers );
174186 }
175187
176188 if (supportsKeyValuePairs && captureKeyValuePairAttributes ) {
@@ -187,6 +199,10 @@ private void mapLoggingEvent(
187199 captureArguments (attributes , loggingEvent .getMessage (), loggingEvent .getArgumentArray ());
188200 }
189201
202+ if (supportsLogstashMarkers && captureLogstashAttributes ) {
203+ captureLogstashAttributes (attributes , loggingEvent );
204+ }
205+
190206 builder .setAllAttributes (attributes .build ());
191207
192208 // span context
@@ -326,31 +342,35 @@ private static boolean supportsKeyValuePairs() {
326342 }
327343
328344 private static void captureMarkerAttribute (
329- AttributesBuilder attributes , ILoggingEvent loggingEvent ) {
345+ AttributesBuilder attributes , ILoggingEvent loggingEvent , boolean skipLogstashMarkers ) {
330346 if (supportsMultipleMarkers && hasMultipleMarkers (loggingEvent )) {
331- captureMultipleMarkerAttributes (attributes , loggingEvent );
347+ captureMultipleMarkerAttributes (attributes , loggingEvent , skipLogstashMarkers );
332348 } else {
333- captureSingleMarkerAttribute (attributes , loggingEvent );
349+ captureSingleMarkerAttribute (attributes , loggingEvent , skipLogstashMarkers );
334350 }
335351 }
336352
337353 @ SuppressWarnings ("deprecation" ) // getMarker is deprecate since 1.3.0
338354 private static void captureSingleMarkerAttribute (
339- AttributesBuilder attributes , ILoggingEvent loggingEvent ) {
355+ AttributesBuilder attributes , ILoggingEvent loggingEvent , boolean skipLogstashMarkers ) {
340356 Marker marker = loggingEvent .getMarker ();
341- if (marker != null ) {
357+ if (marker != null && (! skipLogstashMarkers || ! isLogstashMarker ( marker )) ) {
342358 attributes .put (LOG_MARKER , marker .getName ());
343359 }
344360 }
345361
346362 @ NoMuzzle
347363 private static void captureMultipleMarkerAttributes (
348- AttributesBuilder attributes , ILoggingEvent loggingEvent ) {
364+ AttributesBuilder attributes , ILoggingEvent loggingEvent , boolean skipLogstashMarkers ) {
349365 List <String > markerNames = new ArrayList <>(loggingEvent .getMarkerList ().size ());
350366 for (Marker marker : loggingEvent .getMarkerList ()) {
351- markerNames .add (marker .getName ());
367+ if (!skipLogstashMarkers || !isLogstashMarker (marker )) {
368+ markerNames .add (marker .getName ());
369+ }
370+ }
371+ if (!markerNames .isEmpty ()) {
372+ attributes .put (LOG_MARKER , markerNames .toArray (new String [0 ]));
352373 }
353- attributes .put (LOG_MARKER , markerNames .toArray (new String [0 ]));
354374 }
355375
356376 @ NoMuzzle
@@ -369,6 +389,173 @@ private static boolean supportsMultipleMarkers() {
369389 return true ;
370390 }
371391
392+ private static void captureLogstashAttributes (
393+ AttributesBuilder attributes , ILoggingEvent loggingEvent ) {
394+ try {
395+ if (supportsMultipleMarkers && hasMultipleMarkers (loggingEvent )) {
396+ captureMultipleLogstashAttributes (attributes , loggingEvent );
397+ } else {
398+ captureSingleLogstashAttribute (attributes , loggingEvent );
399+ }
400+ } catch (Throwable e ) {
401+ // ignore
402+ }
403+ }
404+
405+ private static boolean isLogstashMarker (Marker marker ) {
406+ return marker instanceof LogstashMarker ;
407+ }
408+
409+ @ NoMuzzle
410+ @ SuppressWarnings ("deprecation" ) // getMarker is deprecate since 1.3.0
411+ private static void captureSingleLogstashAttribute (
412+ AttributesBuilder attributes , ILoggingEvent loggingEvent ) {
413+ Marker marker = loggingEvent .getMarker ();
414+ if (isLogstashMarker (marker )) {
415+ LogstashMarker logstashMarker = (LogstashMarker ) marker ;
416+ captureLogstashMarker (attributes , logstashMarker );
417+ }
418+ }
419+
420+ @ NoMuzzle
421+ private static void captureMultipleLogstashAttributes (
422+ AttributesBuilder attributes , ILoggingEvent loggingEvent ) {
423+ for (Marker marker : loggingEvent .getMarkerList ()) {
424+ if (isLogstashMarker (marker )) {
425+ LogstashMarker logstashMarker = (LogstashMarker ) marker ;
426+ captureLogstashMarker (attributes , logstashMarker );
427+ }
428+ }
429+ }
430+
431+ private static void captureLogstashMarker (
432+ AttributesBuilder attributes , LogstashMarker logstashMarker ) {
433+ captureLogstashMarkerAttributes (attributes , logstashMarker );
434+
435+ if (logstashMarker .hasReferences ()) {
436+ for (Iterator <Marker > it = logstashMarker .iterator (); it .hasNext (); ) {
437+ Marker referenceMarker = it .next ();
438+ if (isLogstashMarker (referenceMarker )) {
439+ LogstashMarker referenceLogstashMarker = (LogstashMarker ) referenceMarker ;
440+ captureLogstashMarker (attributes , referenceLogstashMarker );
441+ }
442+ }
443+ }
444+ }
445+
446+ @ NoMuzzle
447+ private static void captureLogstashMarkerAttributes (
448+ AttributesBuilder attributes , LogstashMarker logstashMarker ) {
449+ if (logstashMarker instanceof SingleFieldAppendingMarker ) {
450+ SingleFieldAppendingMarker singleFieldAppendingMarker =
451+ (SingleFieldAppendingMarker ) logstashMarker ;
452+ String fieldName = singleFieldAppendingMarker .getFieldName ();
453+ String fieldValue = extractFieldValue (singleFieldAppendingMarker );
454+ if (fieldName != null ) {
455+ if (fieldValue != null ) {
456+ attributes .put (fieldName , fieldValue );
457+ } else {
458+ attributes .put (fieldName , "" );
459+ }
460+ }
461+ } else if (logstashMarker instanceof MapEntriesAppendingMarker ) {
462+ MapEntriesAppendingMarker mapEntriesAppendingMarker =
463+ (MapEntriesAppendingMarker ) logstashMarker ;
464+ Map <?, ?> map = extractMapValue (mapEntriesAppendingMarker );
465+ if (map != null ) {
466+ for (Map .Entry <?, ?> entry : map .entrySet ()) {
467+ Object key = entry .getKey ();
468+ Object value = entry .getValue ();
469+ if (key != null ) {
470+ if (value != null ) {
471+ attributes .put (key .toString (), value .toString ());
472+ } else {
473+ attributes .put (key .toString (), "" );
474+ }
475+ }
476+ }
477+ }
478+ }
479+ }
480+
481+ @ Nullable
482+ private static String extractFieldValue (SingleFieldAppendingMarker singleFieldAppendingMarker ) {
483+ // ObjectAppendingMarker.fieldValue since v7.0
484+ // ObjectAppendingMarker.object since v3.0
485+ // RawJsonAppendingMarker.rawJson since v3.0
486+ Field field =
487+ valueField .computeIfAbsent (
488+ singleFieldAppendingMarker .getClass (),
489+ clazz -> findValueField (clazz , new String [] {"fieldValue" , "object" , "rawJson" }));
490+ if (field != null ) {
491+ try {
492+ Object value = field .get (singleFieldAppendingMarker );
493+ if (value != null ) {
494+ return value .toString ();
495+ }
496+ } catch (IllegalAccessException e ) {
497+ // ignore
498+ }
499+ }
500+ return null ;
501+ }
502+
503+ @ Nullable
504+ private static Map <?, ?> extractMapValue (MapEntriesAppendingMarker mapEntriesAppendingMarker ) {
505+ // MapEntriesAppendingMarker.map since v3.0
506+ Field field =
507+ valueField .computeIfAbsent (
508+ mapEntriesAppendingMarker .getClass (),
509+ clazz -> findValueField (clazz , new String [] {"map" }));
510+ if (field != null ) {
511+ try {
512+ Object value = field .get (mapEntriesAppendingMarker );
513+ if (value instanceof Map ) {
514+ return (Map <?, ?>) value ;
515+ }
516+ } catch (IllegalAccessException e ) {
517+ // ignore
518+ }
519+ }
520+ return null ;
521+ }
522+
523+ @ Nullable
524+ private static Field findValueField (Class <?> clazz , String [] fieldNames ) {
525+ for (String fieldName : fieldNames ) {
526+ try {
527+ Field field = clazz .getDeclaredField (fieldName );
528+ field .setAccessible (true );
529+ return field ;
530+ } catch (NoSuchFieldException e ) {
531+ // ignore
532+ }
533+ }
534+ return null ;
535+ }
536+
537+ private static boolean supportsLogstashMarkers () {
538+ try {
539+ Class .forName ("net.logstash.logback.marker.LogstashMarker" );
540+ } catch (ClassNotFoundException e ) {
541+ return false ;
542+ }
543+
544+ try {
545+ Class .forName ("net.logstash.logback.marker.SingleFieldAppendingMarker" );
546+ } catch (ClassNotFoundException e ) {
547+ return false ;
548+ }
549+
550+ try {
551+ Class .forName ("net.logstash.logback.marker.MapEntriesAppendingMarker" );
552+ } catch (ClassNotFoundException e ) {
553+ return false ;
554+ }
555+
556+ return true ;
557+ }
558+
372559 /**
373560 * This class is internal and is hence not for public use. Its APIs are unstable and can change at
374561 * any time.
@@ -381,6 +568,7 @@ public static final class Builder {
381568 private boolean captureKeyValuePairAttributes ;
382569 private boolean captureLoggerContext ;
383570 private boolean captureArguments ;
571+ private boolean captureLogstashAttributes ;
384572
385573 Builder () {}
386574
@@ -426,6 +614,12 @@ public Builder setCaptureArguments(boolean captureArguments) {
426614 return this ;
427615 }
428616
617+ @ CanIgnoreReturnValue
618+ public Builder setCaptureLogstashAttributes (boolean captureLogstashAttributes ) {
619+ this .captureLogstashAttributes = captureLogstashAttributes ;
620+ return this ;
621+ }
622+
429623 public LoggingEventMapper build () {
430624 return new LoggingEventMapper (this );
431625 }
0 commit comments