Skip to content

Commit 4129983

Browse files
committed
Implement reading of simple key-value Logstash JSON Marker attributes
Supported are MapEntriesAppendingMarker and SingleFieldAppendingMarker (i.e. ObjectAppendingMarker and RawJsonAppendingMarker) only. The attribute value is always a string retrieved by a call to toString() method. Typically the Logstash markers are added to logs via Markers.append() and Markers.appendEntries() methods. Signed-off-by: Oldřich Jedlička <[email protected]>
1 parent 3cf37dd commit 4129983

File tree

7 files changed

+270
-9
lines changed

7 files changed

+270
-9
lines changed

instrumentation/logback/logback-appender-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/appender/v1_0/LogbackSingletons.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ public final class LogbackSingletons {
3939
boolean captureArguments =
4040
config.getBoolean(
4141
"otel.instrumentation.logback-appender.experimental.capture-arguments", false);
42+
boolean captureLogstashAttributes =
43+
config.getBoolean(
44+
"otel.instrumentation.logback-appender.experimental.capture-logstash-attributes",
45+
false);
4246
List<String> captureMdcAttributes =
4347
config.getList(
4448
"otel.instrumentation.logback-appender.experimental.capture-mdc-attributes",
@@ -53,6 +57,7 @@ public final class LogbackSingletons {
5357
.setCaptureKeyValuePairAttributes(captureKeyValuePairAttributes)
5458
.setCaptureLoggerContext(captureLoggerContext)
5559
.setCaptureArguments(captureArguments)
60+
.setCaptureLogstashAttributes(captureLogstashAttributes)
5661
.build();
5762
}
5863

instrumentation/logback/logback-appender-1.0/library/build.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ dependencies {
1919
strictly("2.0.0")
2020
}
2121
}
22+
compileOnly("net.logstash.logback:logstash-logback-encoder") {
23+
version {
24+
strictly("3.0")
25+
}
26+
}
2227

2328
if (findProperty("testLatestDeps") as Boolean) {
2429
testImplementation("ch.qos.logback:logback-classic:+")
@@ -75,6 +80,7 @@ testing {
7580
if (latestDepTest) {
7681
implementation("ch.qos.logback:logback-classic:+")
7782
implementation("org.slf4j:slf4j-api:+")
83+
implementation("net.logstash.logback:logstash-logback-encoder:+")
7884
} else {
7985
implementation("ch.qos.logback:logback-classic") {
8086
version {
@@ -86,6 +92,11 @@ testing {
8692
strictly("2.0.0")
8793
}
8894
}
95+
implementation("net.logstash.logback:logstash-logback-encoder") {
96+
version {
97+
strictly("3.0")
98+
}
99+
}
89100
}
90101
}
91102
}

instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase<ILoggingEv
3434
private boolean captureKeyValuePairAttributes = false;
3535
private boolean captureLoggerContext = false;
3636
private boolean captureArguments = false;
37+
private boolean captureLogstashAttributes = false;
3738
private List<String> captureMdcAttributes = emptyList();
3839

3940
private volatile OpenTelemetry openTelemetry;
@@ -81,6 +82,7 @@ public void start() {
8182
.setCaptureKeyValuePairAttributes(captureKeyValuePairAttributes)
8283
.setCaptureLoggerContext(captureLoggerContext)
8384
.setCaptureArguments(captureArguments)
85+
.setCaptureLogstashAttributes(captureLogstashAttributes)
8486
.build();
8587
eventsToReplay = new ArrayBlockingQueue<>(numLogsCapturedBeforeOtelInstall);
8688
super.start();
@@ -175,6 +177,15 @@ public void setCaptureArguments(boolean captureArguments) {
175177
this.captureArguments = captureArguments;
176178
}
177179

180+
/**
181+
* Sets whether the Logstash attributes should be set to logs.
182+
*
183+
* @param captureLogstashAttributes To enable or disable capturing Logstash attributes
184+
*/
185+
public void setCaptureLogstashAttributes(boolean captureLogstashAttributes) {
186+
this.captureLogstashAttributes = captureLogstashAttributes;
187+
}
188+
178189
/** Configures the {@link MDC} attributes that will be copied to logs. */
179190
public void setCaptureMdcAttributes(String attributes) {
180191
if (attributes != null) {

instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java

Lines changed: 205 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,18 @@
2323
import io.opentelemetry.semconv.ExceptionAttributes;
2424
import java.io.PrintWriter;
2525
import java.io.StringWriter;
26+
import java.lang.reflect.Field;
2627
import java.util.ArrayList;
2728
import java.util.Arrays;
29+
import java.util.Iterator;
2830
import java.util.List;
2931
import java.util.Map;
3032
import java.util.concurrent.TimeUnit;
3133
import 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;
3238
import org.slf4j.Marker;
3339
import 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

Comments
 (0)