From 07ef9f9bb3545c2c3c15912ce35e2aed6efaf72a Mon Sep 17 00:00:00 2001 From: Phil Clay Date: Fri, 21 Nov 2025 10:58:24 -0700 Subject: [PATCH 1/2] Add option for logback to capture message template Added a new `captureTemplate` option to the logback `OpenTelemetryAppender`. Default is false. The new option captures the unformatted log message template in the `log.body.template` log event attribute. Enable in the library by configuring the `OpenTelemetryAppender` with `true`. Enable in the javaagent by setting `otel.instrumentation.logback-appender.experimental.capture-template=true`. The existing `captureArguments` option previously enabled both capturing the template and arguments. Now `captureArguments` only enables capturing arguments (not the message template). This is a backwards incompatible change to an experimental config option. Users currently setting `captureArguments=true` would now need to set both `captureArguments=true` and `captureTemplate=true` to maintain previous behavior. --- .../logback-appender-1.0/javaagent/README.md | 3 ++- .../appender/v1_0/LogbackSingletons.java | 4 ++++ .../logback-appender-1.0/library/README.md | 5 +++-- .../appender/v1_0/OpenTelemetryAppender.java | 11 ++++++++++ .../v1_0/internal/LoggingEventMapper.java | 22 ++++++++++++++++--- .../logback/appender/v1_0/Slf4j2Test.java | 13 ++++++----- .../slf4j2ApiTest/resources/logback-test.xml | 1 + 7 files changed, 48 insertions(+), 11 deletions(-) diff --git a/instrumentation/logback/logback-appender-1.0/javaagent/README.md b/instrumentation/logback/logback-appender-1.0/javaagent/README.md index a071543e88c8..7cedc6154d45 100644 --- a/instrumentation/logback/logback-appender-1.0/javaagent/README.md +++ b/instrumentation/logback/logback-appender-1.0/javaagent/README.md @@ -7,7 +7,8 @@ | `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-template` | Boolean | `false` | Enable the capture of Logback log event message template. | +| `otel.instrumentation.logback-appender.experimental.capture-arguments` | Boolean | `false` | Enable the capture of Logback log event arguments. | | `otel.instrumentation.logback-appender.experimental.capture-logstash-marker-attributes` | Boolean | `false` | Enable the capture of Logstash markers, supported are those added to logs via `Markers.append()`, `Markers.appendEntries()`, `Markers.appendArray()` and `Markers.appendRaw()` methods. | | `otel.instrumentation.logback-appender.experimental.capture-logstash-structured-arguments` | Boolean | `false` | Enable the capture of Logstash StructuredArguments as attributes (e.g., `StructuredArguments.v()` and `StructuredArguments.keyValue()`). | | `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. | 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 ffd92bd1497d..903605643e39 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 @@ -37,6 +37,9 @@ public final class LogbackSingletons { config.getBoolean( "otel.instrumentation.logback-appender.experimental.capture-logger-context-attributes", false); + boolean captureTemplate = + config.getBoolean( + "otel.instrumentation.logback-appender.experimental.capture-template", false); boolean captureArguments = config.getBoolean( "otel.instrumentation.logback-appender.experimental.capture-arguments", false); @@ -66,6 +69,7 @@ public final class LogbackSingletons { .setCaptureMarkerAttribute(captureMarkerAttribute) .setCaptureKeyValuePairAttributes(captureKeyValuePairAttributes) .setCaptureLoggerContext(captureLoggerContext) + .setCaptureTemplate(captureTemplate) .setCaptureArguments(captureArguments) .setCaptureLogstashMarkerAttributes(captureLogstashMarkerAttributes) .setCaptureLogstashStructuredArguments(captureLogstashStructuredArguments) diff --git a/instrumentation/logback/logback-appender-1.0/library/README.md b/instrumentation/logback/logback-appender-1.0/library/README.md index 8f91c76f624a..db16133335f8 100644 --- a/instrumentation/logback/logback-appender-1.0/library/README.md +++ b/instrumentation/logback/logback-appender-1.0/library/README.md @@ -100,9 +100,10 @@ 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. | +| `captureTemplate` | Boolean | `false` | Enable the capture of Logback log event message template. | +| `captureArguments` | Boolean | `false` | Enable the capture of Logback log event arguments. | | `captureLogstashMarkerAttributes` | Boolean | `false` | Enable the capture of Logstash markers, supported are those added to logs via `Markers.append()`, `Markers.appendEntries()`, `Markers.appendArray()` and `Markers.appendRaw()` methods. | -| `captureLogstashStructuredArguments` | Boolean | `false` | Enable the capture of Logstash StructuredArguments as attributes (e.g., `StructuredArguments.v()` and `StructuredArguments.keyValue()`). | +| `captureLogstashStructuredArguments` | Boolean | `false` | Enable the capture of Logstash StructuredArguments as attributes (e.g., `StructuredArguments.v()` and `StructuredArguments.keyValue()`). | | `captureMdcAttributes` | String | | Comma separated list of MDC attributes to capture. Use the wildcard character `*` to capture all attributes. | | `captureEventName` | Boolean | `false` | Enable moving the `event.name` attribute (captured by one of the other mechanisms of capturing attributes) to the log event name. | | `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 3bd21953efdf..690fdf1b41c9 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 @@ -35,6 +35,7 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase 0) { - captureArguments(builder, loggingEvent.getMessage(), loggingEvent.getArgumentArray()); + captureArguments(builder, loggingEvent.getArgumentArray()); } if (supportsLogstashMarkers && captureLogstashMarkerAttributes) { @@ -265,8 +271,11 @@ void captureMdcAttributes(LogRecordBuilder builder, Map mdcPrope } } - void captureArguments(LogRecordBuilder builder, String message, Object[] arguments) { - builder.setAttribute(LOG_BODY_TEMPLATE, message); + private static void captureTemplate(LogRecordBuilder builder, ILoggingEvent loggingEvent) { + builder.setAttribute(LOG_BODY_TEMPLATE, loggingEvent.getMessage()); + } + + private static void captureArguments(LogRecordBuilder builder, Object[] arguments) { builder.setAttribute( LOG_BODY_PARAMETERS, Arrays.stream(arguments).map(String::valueOf).collect(Collectors.toList())); @@ -679,6 +688,7 @@ public static final class Builder { private boolean captureMarkerAttribute; private boolean captureKeyValuePairAttributes; private boolean captureLoggerContext; + private boolean captureTemplate; private boolean captureArguments; private boolean captureLogstashMarkerAttributes; private boolean captureLogstashStructuredArguments; @@ -722,6 +732,12 @@ public Builder setCaptureLoggerContext(boolean captureLoggerContext) { return this; } + @CanIgnoreReturnValue + public Builder setCaptureTemplate(boolean captureTemplate) { + this.captureTemplate = captureTemplate; + return this; + } + @CanIgnoreReturnValue public Builder setCaptureArguments(boolean captureArguments) { this.captureArguments = captureArguments; 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 60be22315a59..2cf549acd3a9 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 @@ -60,7 +60,8 @@ void keyValue() { .hasResource(resource) .hasInstrumentationScope(instrumentationScopeInfo) .hasBody("log message 1") - .hasTotalAttributeCount(codeAttributesLogCount() + 8) // 8 key value pairs + .hasTotalAttributeCount( + codeAttributesLogCount() + 9) // 8 key value pairs + 1 template .hasEventName("MyEventName") .hasAttributesSatisfying( equalTo(AttributeKey.stringKey("string key"), "string value"), @@ -70,7 +71,8 @@ void keyValue() { equalTo(AttributeKey.longKey("int key"), 3), equalTo(AttributeKey.longKey("long key"), 4), equalTo(AttributeKey.doubleKey("float key"), 5.0), - equalTo(AttributeKey.doubleKey("double key"), 6.0))); + equalTo(AttributeKey.doubleKey("double key"), 6.0), + equalTo(AttributeKey.stringKey("log.body.template"), "log message 1"))); } @Test @@ -90,15 +92,16 @@ void multipleMarkers() { .hasResource(resource) .hasInstrumentationScope(instrumentationScopeInfo) .hasBody("log message 1") - .hasTotalAttributeCount(codeAttributesLogCount() + 1) // 1 marker + .hasTotalAttributeCount(codeAttributesLogCount() + 2) // 1 marker + 1 template .hasAttributesSatisfying( equalTo( AttributeKey.stringArrayKey("logback.marker"), - Arrays.asList(markerName1, markerName2)))); + Arrays.asList(markerName1, markerName2)), + equalTo(AttributeKey.stringKey("log.body.template"), "log message 1"))); } @Test - void arguments() { + void argumentsAndTemplate() { logger .atInfo() .setMessage("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 92b2ddefc859..e9195480ba5c 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 true true true From f518d9aaf51d8a4721e9c117aaa7f3a12e170f97 Mon Sep 17 00:00:00 2001 From: Phil Clay Date: Tue, 25 Nov 2025 10:03:33 -0700 Subject: [PATCH 2/2] Only capture log body template if arguments are provided Reduces log volume when message templates are not being used. --- .../logback/logback-appender-1.0/javaagent/README.md | 2 +- .../logback/logback-appender-1.0/library/README.md | 2 +- .../logback/appender/v1_0/OpenTelemetryAppender.java | 5 +++-- .../appender/v1_0/internal/LoggingEventMapper.java | 4 +++- .../logback/appender/v1_0/Slf4j2Test.java | 11 ++++------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/instrumentation/logback/logback-appender-1.0/javaagent/README.md b/instrumentation/logback/logback-appender-1.0/javaagent/README.md index 7cedc6154d45..e2dde2060490 100644 --- a/instrumentation/logback/logback-appender-1.0/javaagent/README.md +++ b/instrumentation/logback/logback-appender-1.0/javaagent/README.md @@ -7,7 +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-template` | Boolean | `false` | Enable the capture of Logback log event message template. | +| `otel.instrumentation.logback-appender.experimental.capture-template` | Boolean | `false` | Enable the capture of Logback log event message template (if arguments are provided). | | `otel.instrumentation.logback-appender.experimental.capture-arguments` | Boolean | `false` | Enable the capture of Logback log event arguments. | | `otel.instrumentation.logback-appender.experimental.capture-logstash-marker-attributes` | Boolean | `false` | Enable the capture of Logstash markers, supported are those added to logs via `Markers.append()`, `Markers.appendEntries()`, `Markers.appendArray()` and `Markers.appendRaw()` methods. | | `otel.instrumentation.logback-appender.experimental.capture-logstash-structured-arguments` | Boolean | `false` | Enable the capture of Logstash StructuredArguments as attributes (e.g., `StructuredArguments.v()` and `StructuredArguments.keyValue()`). | diff --git a/instrumentation/logback/logback-appender-1.0/library/README.md b/instrumentation/logback/logback-appender-1.0/library/README.md index db16133335f8..d4c3f6a90485 100644 --- a/instrumentation/logback/logback-appender-1.0/library/README.md +++ b/instrumentation/logback/logback-appender-1.0/library/README.md @@ -100,7 +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. | -| `captureTemplate` | Boolean | `false` | Enable the capture of Logback log event message template. | +| `captureTemplate` | Boolean | `false` | Enable the capture of Logback log event message template (if arguments are provided). | | `captureArguments` | Boolean | `false` | Enable the capture of Logback log event arguments. | | `captureLogstashMarkerAttributes` | Boolean | `false` | Enable the capture of Logstash markers, supported are those added to logs via `Markers.append()`, `Markers.appendEntries()`, `Markers.appendArray()` and `Markers.appendRaw()` methods. | | `captureLogstashStructuredArguments` | Boolean | `false` | Enable the capture of Logstash StructuredArguments as attributes (e.g., `StructuredArguments.v()` and `StructuredArguments.keyValue()`). | 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 690fdf1b41c9..e73c6a080a9e 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 @@ -180,9 +180,10 @@ public void setCaptureLoggerContext(boolean captureLoggerContext) { } /** - * Sets whether the message template should be captured in logs + * Sets whether the message template should be captured in logs if arguments are provided. * - * @param captureTemplate whether the message template should be captured in logs + * @param captureTemplate whether the message template should be captured in logs if arguments are + * provided */ public void setCaptureTemplate(boolean captureTemplate) { this.captureTemplate = captureTemplate; 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 024be91df42c..c8d52c454555 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 @@ -217,7 +217,9 @@ private void mapLoggingEvent( captureLoggerContext(builder, loggingEvent.getLoggerContextVO().getPropertyMap()); } - if (captureTemplate) { + if (captureTemplate + && loggingEvent.getArgumentArray() != null + && loggingEvent.getArgumentArray().length > 0) { captureTemplate(builder, loggingEvent); } 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 2cf549acd3a9..a92610153a61 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 @@ -60,8 +60,7 @@ void keyValue() { .hasResource(resource) .hasInstrumentationScope(instrumentationScopeInfo) .hasBody("log message 1") - .hasTotalAttributeCount( - codeAttributesLogCount() + 9) // 8 key value pairs + 1 template + .hasTotalAttributeCount(codeAttributesLogCount() + 8) // 8 key value pairs .hasEventName("MyEventName") .hasAttributesSatisfying( equalTo(AttributeKey.stringKey("string key"), "string value"), @@ -71,8 +70,7 @@ void keyValue() { equalTo(AttributeKey.longKey("int key"), 3), equalTo(AttributeKey.longKey("long key"), 4), equalTo(AttributeKey.doubleKey("float key"), 5.0), - equalTo(AttributeKey.doubleKey("double key"), 6.0), - equalTo(AttributeKey.stringKey("log.body.template"), "log message 1"))); + equalTo(AttributeKey.doubleKey("double key"), 6.0))); } @Test @@ -92,12 +90,11 @@ void multipleMarkers() { .hasResource(resource) .hasInstrumentationScope(instrumentationScopeInfo) .hasBody("log message 1") - .hasTotalAttributeCount(codeAttributesLogCount() + 2) // 1 marker + 1 template + .hasTotalAttributeCount(codeAttributesLogCount() + 1) // 1 marker .hasAttributesSatisfying( equalTo( AttributeKey.stringArrayKey("logback.marker"), - Arrays.asList(markerName1, markerName2)), - equalTo(AttributeKey.stringKey("log.body.template"), "log message 1"))); + Arrays.asList(markerName1, markerName2)))); } @Test