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,175 @@ 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+ @ NoMuzzle
406+ private static boolean isLogstashMarker (Marker marker ) {
407+ return marker instanceof LogstashMarker ;
408+ }
409+
410+ @ SuppressWarnings ("deprecation" ) // getMarker is deprecate since 1.3.0
411+ @ NoMuzzle
412+ private static void captureSingleLogstashAttribute (
413+ AttributesBuilder attributes , ILoggingEvent loggingEvent ) {
414+ Marker marker = loggingEvent .getMarker ();
415+ if (isLogstashMarker (marker )) {
416+ LogstashMarker logstashMarker = (LogstashMarker ) marker ;
417+ captureLogstashMarker (attributes , logstashMarker );
418+ }
419+ }
420+
421+ @ NoMuzzle
422+ private static void captureMultipleLogstashAttributes (
423+ AttributesBuilder attributes , ILoggingEvent loggingEvent ) {
424+ for (Marker marker : loggingEvent .getMarkerList ()) {
425+ if (isLogstashMarker (marker )) {
426+ LogstashMarker logstashMarker = (LogstashMarker ) marker ;
427+ captureLogstashMarker (attributes , logstashMarker );
428+ }
429+ }
430+ }
431+
432+ @ NoMuzzle
433+ private static void captureLogstashMarker (
434+ AttributesBuilder attributes , LogstashMarker logstashMarker ) {
435+ captureLogstashMarkerAttributes (attributes , logstashMarker );
436+
437+ if (logstashMarker .hasReferences ()) {
438+ for (Iterator <Marker > it = logstashMarker .iterator (); it .hasNext (); ) {
439+ Marker referenceMarker = it .next ();
440+ if (isLogstashMarker (referenceMarker )) {
441+ LogstashMarker referenceLogstashMarker = (LogstashMarker ) referenceMarker ;
442+ captureLogstashMarker (attributes , referenceLogstashMarker );
443+ }
444+ }
445+ }
446+ }
447+
448+ @ NoMuzzle
449+ private static void captureLogstashMarkerAttributes (
450+ AttributesBuilder attributes , LogstashMarker logstashMarker ) {
451+ if (logstashMarker instanceof SingleFieldAppendingMarker ) {
452+ SingleFieldAppendingMarker singleFieldAppendingMarker =
453+ (SingleFieldAppendingMarker ) logstashMarker ;
454+ String fieldName = singleFieldAppendingMarker .getFieldName ();
455+ String fieldValue = extractFieldValue (singleFieldAppendingMarker );
456+ if (fieldName != null ) {
457+ if (fieldValue != null ) {
458+ attributes .put (fieldName , fieldValue );
459+ } else {
460+ attributes .put (fieldName , "" );
461+ }
462+ }
463+ } else if (logstashMarker instanceof MapEntriesAppendingMarker ) {
464+ MapEntriesAppendingMarker mapEntriesAppendingMarker =
465+ (MapEntriesAppendingMarker ) logstashMarker ;
466+ Map <?, ?> map = extractMapValue (mapEntriesAppendingMarker );
467+ if (map != null ) {
468+ for (Map .Entry <?, ?> entry : map .entrySet ()) {
469+ Object key = entry .getKey ();
470+ Object value = entry .getValue ();
471+ if (key != null ) {
472+ if (value != null ) {
473+ attributes .put (key .toString (), value .toString ());
474+ } else {
475+ attributes .put (key .toString (), "" );
476+ }
477+ }
478+ }
479+ }
480+ }
481+ }
482+
483+ @ Nullable
484+ private static String extractFieldValue (SingleFieldAppendingMarker singleFieldAppendingMarker ) {
485+ // ObjectAppendingMarker.fieldValue since v7.0
486+ // ObjectAppendingMarker.object since v3.0
487+ // RawJsonAppendingMarker.rawJson since v3.0
488+ Field field =
489+ valueField .computeIfAbsent (
490+ singleFieldAppendingMarker .getClass (),
491+ clazz -> findValueField (clazz , new String [] {"fieldValue" , "object" , "rawJson" }));
492+ if (field != null ) {
493+ try {
494+ Object value = field .get (singleFieldAppendingMarker );
495+ if (value != null ) {
496+ return value .toString ();
497+ }
498+ } catch (IllegalAccessException e ) {
499+ // ignore
500+ }
501+ }
502+ return null ;
503+ }
504+
505+ @ Nullable
506+ private static Map <?, ?> extractMapValue (MapEntriesAppendingMarker mapEntriesAppendingMarker ) {
507+ // MapEntriesAppendingMarker.map since v3.0
508+ Field field =
509+ valueField .computeIfAbsent (
510+ mapEntriesAppendingMarker .getClass (),
511+ clazz -> findValueField (clazz , new String [] {"map" }));
512+ if (field != null ) {
513+ try {
514+ Object value = field .get (mapEntriesAppendingMarker );
515+ if (value instanceof Map ) {
516+ return (Map <?, ?>) value ;
517+ }
518+ } catch (IllegalAccessException e ) {
519+ // ignore
520+ }
521+ }
522+ return null ;
523+ }
524+
525+ @ Nullable
526+ private static Field findValueField (Class <?> clazz , String [] fieldNames ) {
527+ for (String fieldName : fieldNames ) {
528+ try {
529+ Field field = clazz .getDeclaredField (fieldName );
530+ field .setAccessible (true );
531+ return field ;
532+ } catch (NoSuchFieldException e ) {
533+ // ignore
534+ }
535+ }
536+ return null ;
537+ }
538+
539+ private static boolean supportsLogstashMarkers () {
540+ try {
541+ Class .forName ("net.logstash.logback.marker.LogstashMarker" );
542+ } catch (ClassNotFoundException e ) {
543+ return false ;
544+ }
545+
546+ try {
547+ Class .forName ("net.logstash.logback.marker.SingleFieldAppendingMarker" );
548+ } catch (ClassNotFoundException e ) {
549+ return false ;
550+ }
551+
552+ try {
553+ Class .forName ("net.logstash.logback.marker.MapEntriesAppendingMarker" );
554+ } catch (ClassNotFoundException e ) {
555+ return false ;
556+ }
557+
558+ return true ;
559+ }
560+
372561 /**
373562 * This class is internal and is hence not for public use. Its APIs are unstable and can change at
374563 * any time.
@@ -381,6 +570,7 @@ public static final class Builder {
381570 private boolean captureKeyValuePairAttributes ;
382571 private boolean captureLoggerContext ;
383572 private boolean captureArguments ;
573+ private boolean captureLogstashAttributes ;
384574
385575 Builder () {}
386576
@@ -426,6 +616,12 @@ public Builder setCaptureArguments(boolean captureArguments) {
426616 return this ;
427617 }
428618
619+ @ CanIgnoreReturnValue
620+ public Builder setCaptureLogstashAttributes (boolean captureLogstashAttributes ) {
621+ this .captureLogstashAttributes = captureLogstashAttributes ;
622+ return this ;
623+ }
624+
429625 public LoggingEventMapper build () {
430626 return new LoggingEventMapper (this );
431627 }
0 commit comments