diff --git a/instrumentation/logback/logback-appender-1.0/javaagent/README.md b/instrumentation/logback/logback-appender-1.0/javaagent/README.md index aaa7be6e03..2a962ae4e7 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 b2b9eb8a5a..a3d1c6d906 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 a002e3a704..14c515071d 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 3d689e7a5e..06904c219c 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 class OpenTelemetryAppender extends UnsynchronizedAppenderBase(numLogsCapturedBeforeOtelInstall); super.start(); @@ -164,6 +166,15 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase> 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 @@ public final class LoggingEventMapper { 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 @@ public final class LoggingEventMapper { 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 @@ public final class LoggingEventMapper { } } + 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 @@ public final class LoggingEventMapper { 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 final class LoggingEventMapper { private boolean captureMarkerAttribute; private boolean captureKeyValuePairAttributes; private boolean captureLoggerContext; + private boolean captureArguments; Builder() {} @@ -397,6 +420,12 @@ public final class LoggingEventMapper { 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 79d3c50729..af17aeaedd 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 @@ public class Slf4j2Test { 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 d02bf77280..366678be33 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 *