From 023f30cf4625cbc014a0514d4c39e6a0cf74aecf Mon Sep 17 00:00:00 2001 From: Igor Suhorukov Date: Thu, 15 Aug 2024 03:16:04 +0300 Subject: [PATCH] Save ILoggingEvent.getArgumentArray() arguments from Logback (#11865) Co-authored-by: Lauri Tulmin --- .../logback-appender-1.0/javaagent/README.md | 1 + .../appender/v1_0/LogbackSingletons.java | 4 ++ .../logback-appender-1.0/library/README.md | 1 + .../appender/v1_0/OpenTelemetryAppender.java | 11 ++++++ .../v1_0/internal/LoggingEventMapper.java | 39 ++++++++++++++++--- .../logback/appender/v1_0/Slf4j2Test.java | 39 +++++++++++++++++++ .../slf4j2ApiTest/resources/logback-test.xml | 1 + 7 files changed, 91 insertions(+), 5 deletions(-) diff --git a/instrumentation/logback/logback-appender-1.0/javaagent/README.md b/instrumentation/logback/logback-appender-1.0/javaagent/README.md index aaa7be6e0364..2a962ae4e739 100644 --- a/instrumentation/logback/logback-appender-1.0/javaagent/README.md +++ b/instrumentation/logback/logback-appender-1.0/javaagent/README.md @@ -7,6 +7,7 @@ | `otel.instrumentation.logback-appender.experimental.capture-marker-attribute` | Boolean | `false` | Enable the capture of Logback markers as attributes. | | `otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes` | Boolean | `false` | Enable the capture of Logback key value pairs as attributes. | | `otel.instrumentation.logback-appender.experimental.capture-logger-context-attributes` | Boolean | `false` | Enable the capture of Logback logger context properties as attributes. | +| `otel.instrumentation.logback-appender.experimental.capture-arguments` | Boolean | `false` | Enable the capture of Logback logger arguments. | | `otel.instrumentation.logback-appender.experimental.capture-mdc-attributes` | String | | Comma separated list of MDC attributes to capture. Use the wildcard character `*` to capture all attributes. | [source code attributes]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/attributes.md#source-code-attributes diff --git a/instrumentation/logback/logback-appender-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/appender/v1_0/LogbackSingletons.java b/instrumentation/logback/logback-appender-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/appender/v1_0/LogbackSingletons.java index b2b9eb8a5a92..a3d1c6d90688 100644 --- a/instrumentation/logback/logback-appender-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/appender/v1_0/LogbackSingletons.java +++ b/instrumentation/logback/logback-appender-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/appender/v1_0/LogbackSingletons.java @@ -36,6 +36,9 @@ public final class LogbackSingletons { config.getBoolean( "otel.instrumentation.logback-appender.experimental.capture-logger-context-attributes", false); + boolean captureArguments = + config.getBoolean( + "otel.instrumentation.logback-appender.experimental.capture-arguments", false); List captureMdcAttributes = config.getList( "otel.instrumentation.logback-appender.experimental.capture-mdc-attributes", @@ -49,6 +52,7 @@ public final class LogbackSingletons { .setCaptureMarkerAttribute(captureMarkerAttribute) .setCaptureKeyValuePairAttributes(captureKeyValuePairAttributes) .setCaptureLoggerContext(captureLoggerContext) + .setCaptureArguments(captureArguments) .build(); } diff --git a/instrumentation/logback/logback-appender-1.0/library/README.md b/instrumentation/logback/logback-appender-1.0/library/README.md index a002e3a704a0..14c515071d76 100644 --- a/instrumentation/logback/logback-appender-1.0/library/README.md +++ b/instrumentation/logback/logback-appender-1.0/library/README.md @@ -100,6 +100,7 @@ The available settings are: | `captureMarkerAttribute` | Boolean | `false` | Enable the capture of Logback markers as attributes. | | `captureKeyValuePairAttributes` | Boolean | `false` | Enable the capture of Logback key value pairs as attributes. | | `captureLoggerContext` | Boolean | `false` | Enable the capture of Logback logger context properties as attributes. | +| `captureArguments` | Boolean | `false` | Enable the capture of Logback logger arguments. | | `captureMdcAttributes` | String | | Comma separated list of MDC attributes to capture. Use the wildcard character `*` to capture all attributes. | | `numLogsCapturedBeforeOtelInstall` | Integer | 1000 | Log telemetry is emitted after the initialization of the OpenTelemetry Logback appender with an OpenTelemetry object. This setting allows you to modify the size of the cache used to replay the first logs. thread.id attribute is not captured. | diff --git a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java index 3d689e7a5eb5..06904c219c81 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java @@ -33,6 +33,7 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase captureMdcAttributes = emptyList(); private volatile OpenTelemetry openTelemetry; @@ -79,6 +80,7 @@ public void start() { .setCaptureMarkerAttribute(captureMarkerAttribute) .setCaptureKeyValuePairAttributes(captureKeyValuePairAttributes) .setCaptureLoggerContext(captureLoggerContext) + .setCaptureArguments(captureArguments) .build(); eventsToReplay = new ArrayBlockingQueue<>(numLogsCapturedBeforeOtelInstall); super.start(); @@ -164,6 +166,15 @@ public void setCaptureLoggerContext(boolean captureLoggerContext) { this.captureLoggerContext = captureLoggerContext; } + /** + * Sets whether the arguments should be set to logs. + * + * @param captureArguments To enable or disable capturing logger arguments + */ + public void setCaptureArguments(boolean captureArguments) { + this.captureArguments = captureArguments; + } + /** Configures the {@link MDC} attributes that will be copied to logs. */ public void setCaptureMdcAttributes(String attributes) { if (attributes != null) { diff --git a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java index 1fd6b4eda3b3..8f0af6048202 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java @@ -24,9 +24,11 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import org.slf4j.Marker; import org.slf4j.event.KeyValuePair; @@ -53,6 +55,10 @@ public final class LoggingEventMapper { private static final AttributeKey> LOG_MARKER = AttributeKey.stringArrayKey("logback.marker"); + private static final AttributeKey LOG_BODY_TEMPLATE = + AttributeKey.stringKey("log.body.template"); + private static final AttributeKey> LOG_BODY_PARAMETERS = + AttributeKey.stringArrayKey("log.body.parameters"); private final boolean captureExperimentalAttributes; private final List captureMdcAttributes; @@ -61,6 +67,7 @@ public final class LoggingEventMapper { private final boolean captureMarkerAttribute; private final boolean captureKeyValuePairAttributes; private final boolean captureLoggerContext; + private final boolean captureArguments; private LoggingEventMapper(Builder builder) { this.captureExperimentalAttributes = builder.captureExperimentalAttributes; @@ -69,6 +76,7 @@ private LoggingEventMapper(Builder builder) { this.captureMarkerAttribute = builder.captureMarkerAttribute; this.captureKeyValuePairAttributes = builder.captureKeyValuePairAttributes; this.captureLoggerContext = builder.captureLoggerContext; + this.captureArguments = builder.captureArguments; this.captureAllMdcAttributes = builder.captureMdcAttributes.size() == 1 && builder.captureMdcAttributes.get(0).equals("*"); } @@ -173,6 +181,12 @@ private void mapLoggingEvent( captureLoggerContext(attributes, loggingEvent.getLoggerContextVO().getPropertyMap()); } + if (captureArguments + && loggingEvent.getArgumentArray() != null + && loggingEvent.getArgumentArray().length > 0) { + captureArguments(attributes, loggingEvent.getMessage(), loggingEvent.getArgumentArray()); + } + builder.setAllAttributes(attributes.build()); // span context @@ -218,6 +232,13 @@ void captureMdcAttributes(AttributesBuilder attributes, Map mdcP } } + void captureArguments(AttributesBuilder attributes, String message, Object[] arguments) { + attributes.put(LOG_BODY_TEMPLATE, message); + attributes.put( + LOG_BODY_PARAMETERS, + Arrays.stream(arguments).map(String::valueOf).collect(Collectors.toList())); + } + public static AttributeKey getMdcAttributeKey(String key) { return mdcAttributeKeys.computeIfAbsent(key, AttributeKey::stringKey); } @@ -258,19 +279,20 @@ private static void captureKeyValuePairAttributes( if (keyValuePairs != null) { for (KeyValuePair keyValuePair : keyValuePairs) { Object value = keyValuePair.value; - if (keyValuePair.value != null) { + if (value != null) { + String key = keyValuePair.key; // preserve type for boolean and numeric values, everything else is converted to String if (value instanceof Boolean) { - attributes.put(keyValuePair.key, (Boolean) keyValuePair.value); + attributes.put(key, (Boolean) value); } else if (value instanceof Byte || value instanceof Integer || value instanceof Long || value instanceof Short) { - attributes.put(keyValuePair.key, ((Number) keyValuePair.value).longValue()); + attributes.put(key, ((Number) value).longValue()); } else if (value instanceof Double || value instanceof Float) { - attributes.put(keyValuePair.key, ((Number) keyValuePair.value).doubleValue()); + attributes.put(key, ((Number) value).doubleValue()); } else { - attributes.put(getAttributeKey(keyValuePair.key), keyValuePair.value.toString()); + attributes.put(getAttributeKey(key), value.toString()); } } } @@ -358,6 +380,7 @@ public static final class Builder { private boolean captureMarkerAttribute; private boolean captureKeyValuePairAttributes; private boolean captureLoggerContext; + private boolean captureArguments; Builder() {} @@ -397,6 +420,12 @@ public Builder setCaptureLoggerContext(boolean captureLoggerContext) { return this; } + @CanIgnoreReturnValue + public Builder setCaptureArguments(boolean captureArguments) { + this.captureArguments = captureArguments; + return this; + } + public LoggingEventMapper build() { return new LoggingEventMapper(this); } diff --git a/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java b/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java index 79d3c507295d..af17aeaedd84 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java @@ -112,4 +112,43 @@ void multipleMarkers() { AttributeKey.stringArrayKey("logback.marker"), value -> assertThat(value).isEqualTo(Arrays.asList(markerName1, markerName2))); } + + @Test + void arguments() { + logger + .atInfo() + .setMessage("log message {} and {}, bool {}, long {}") + .addArgument("'world'") + .addArgument(Math.PI) + .addArgument(true) + .addArgument(Long.MAX_VALUE) + .log(); + + List logDataList = logRecordExporter.getFinishedLogRecordItems(); + assertThat(logDataList).hasSize(1); + LogRecordData logData = logDataList.get(0); + + assertThat(logData.getResource()).isEqualTo(resource); + assertThat(logData.getInstrumentationScopeInfo()).isEqualTo(instrumentationScopeInfo); + assertThat(logData.getBody().asString()) + .isEqualTo( + "log message 'world' and 3.141592653589793, bool true, long 9223372036854775807"); + assertThat(logData.getAttributes().size()).isEqualTo(6); + assertThat(logData.getAttributes()) + .hasEntrySatisfying( + AttributeKey.stringArrayKey("log.body.parameters"), + value -> + assertThat(value) + .isEqualTo( + Arrays.asList( + "'world'", + String.valueOf(Math.PI), + String.valueOf(true), + String.valueOf(Long.MAX_VALUE)))); + assertThat(logData) + .hasAttributesSatisfying( + equalTo( + AttributeKey.stringKey("log.body.template"), + "log message {} and {}, bool {}, long {}")); + } } diff --git a/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/resources/logback-test.xml b/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/resources/logback-test.xml index d02bf772803d..366678be3369 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/resources/logback-test.xml +++ b/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/resources/logback-test.xml @@ -14,6 +14,7 @@ true true true + true *