Skip to content

Commit 087cdd9

Browse files
oldiumOldřich Jedlička
authored and
Oldřich Jedlička
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 087cdd9

File tree

7 files changed

+268
-9
lines changed

7 files changed

+268
-9
lines changed

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

+5
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

+11
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

+11
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

+203-9
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,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

Comments
 (0)