Save ILoggingEvent.getArgumentArray() arguments from Logback (#11865)
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
This commit is contained in:
parent
dee515d197
commit
023f30cf46
|
@ -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
|
||||
|
|
|
@ -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<String> 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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. |
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase<ILoggingEv
|
|||
private boolean captureMarkerAttribute = false;
|
||||
private boolean captureKeyValuePairAttributes = false;
|
||||
private boolean captureLoggerContext = false;
|
||||
private boolean captureArguments = true;
|
||||
private List<String> captureMdcAttributes = emptyList();
|
||||
|
||||
private volatile OpenTelemetry openTelemetry;
|
||||
|
@ -79,6 +80,7 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase<ILoggingEv
|
|||
.setCaptureMarkerAttribute(captureMarkerAttribute)
|
||||
.setCaptureKeyValuePairAttributes(captureKeyValuePairAttributes)
|
||||
.setCaptureLoggerContext(captureLoggerContext)
|
||||
.setCaptureArguments(captureArguments)
|
||||
.build();
|
||||
eventsToReplay = new ArrayBlockingQueue<>(numLogsCapturedBeforeOtelInstall);
|
||||
super.start();
|
||||
|
@ -164,6 +166,15 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase<ILoggingEv
|
|||
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) {
|
||||
|
|
|
@ -24,9 +24,11 @@ import io.opentelemetry.semconv.ExceptionAttributes;
|
|||
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<List<String>> LOG_MARKER =
|
||||
AttributeKey.stringArrayKey("logback.marker");
|
||||
private static final AttributeKey<String> LOG_BODY_TEMPLATE =
|
||||
AttributeKey.stringKey("log.body.template");
|
||||
private static final AttributeKey<List<String>> LOG_BODY_PARAMETERS =
|
||||
AttributeKey.stringArrayKey("log.body.parameters");
|
||||
|
||||
private final boolean captureExperimentalAttributes;
|
||||
private final List<String> 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<String> 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);
|
||||
}
|
||||
|
|
|
@ -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<LogRecordData> 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 {}"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<captureCodeAttributes>true</captureCodeAttributes>
|
||||
<captureMarkerAttribute>true</captureMarkerAttribute>
|
||||
<captureKeyValuePairAttributes>true</captureKeyValuePairAttributes>
|
||||
<captureArguments>true</captureArguments>
|
||||
<captureMdcAttributes>*</captureMdcAttributes>
|
||||
</appender>
|
||||
|
||||
|
|
Loading…
Reference in New Issue