Allow the Spring starter to configure the OTel Logback appender from system properties (#10355)
This commit is contained in:
parent
5f6232fca8
commit
80e5bac4a4
|
@ -10,6 +10,7 @@ import ch.qos.logback.classic.spi.ILoggingEvent;
|
|||
import ch.qos.logback.core.Appender;
|
||||
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import org.slf4j.ILoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -53,23 +54,117 @@ public class LogbackAppenderApplicationListener implements GenericApplicationLis
|
|||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event instanceof ApplicationEnvironmentPreparedEvent // Event for which
|
||||
// org.springframework.boot.context.logging.LoggingApplicationListener
|
||||
// initializes logging
|
||||
&& !isOpenTelemetryAppenderAlreadyConfigured()) {
|
||||
ch.qos.logback.classic.Logger logger =
|
||||
(ch.qos.logback.classic.Logger)
|
||||
LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
|
||||
OpenTelemetryAppender appender = new OpenTelemetryAppender();
|
||||
appender.start();
|
||||
logger.addAppender(appender);
|
||||
// org.springframework.boot.context.logging.LoggingApplicationListener
|
||||
// initializes logging
|
||||
) {
|
||||
Optional<OpenTelemetryAppender> existingOpenTelemetryAppender = findOpenTelemetryAppender();
|
||||
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent =
|
||||
(ApplicationEnvironmentPreparedEvent) event;
|
||||
if (existingOpenTelemetryAppender.isPresent()) {
|
||||
reInitializeOpenTelemetryAppender(
|
||||
existingOpenTelemetryAppender, applicationEnvironmentPreparedEvent);
|
||||
} else {
|
||||
addOpenTelemetryAppender(applicationEnvironmentPreparedEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isOpenTelemetryAppenderAlreadyConfigured() {
|
||||
private static void reInitializeOpenTelemetryAppender(
|
||||
Optional<OpenTelemetryAppender> existingOpenTelemetryAppender,
|
||||
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
|
||||
OpenTelemetryAppender openTelemetryAppender = existingOpenTelemetryAppender.get();
|
||||
// The OpenTelemetry appender is stopped and restarted from the
|
||||
// org.springframework.boot.context.logging.LoggingApplicationListener.initialize
|
||||
// method.
|
||||
// The OpenTelemetryAppender initializes the LoggingEventMapper in the start() method. So, here
|
||||
// we stop the OpenTelemetry appender before its re-initialization and its restart.
|
||||
openTelemetryAppender.stop();
|
||||
initializeOpenTelemetryAppenderFromProperties(
|
||||
applicationEnvironmentPreparedEvent, openTelemetryAppender);
|
||||
openTelemetryAppender.start();
|
||||
}
|
||||
|
||||
private static void addOpenTelemetryAppender(
|
||||
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
|
||||
ch.qos.logback.classic.Logger logger =
|
||||
(ch.qos.logback.classic.Logger)
|
||||
LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
OpenTelemetryAppender openTelemetryAppender = new OpenTelemetryAppender();
|
||||
initializeOpenTelemetryAppenderFromProperties(
|
||||
applicationEnvironmentPreparedEvent, openTelemetryAppender);
|
||||
openTelemetryAppender.start();
|
||||
logger.addAppender(openTelemetryAppender);
|
||||
}
|
||||
|
||||
private static void initializeOpenTelemetryAppenderFromProperties(
|
||||
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent,
|
||||
OpenTelemetryAppender openTelemetryAppender) {
|
||||
|
||||
// Implemented in the same way as the
|
||||
// org.springframework.boot.context.logging.LoggingApplicationListener, config properties not
|
||||
// available
|
||||
Boolean codeAttribute =
|
||||
evaluateBooleanProperty(
|
||||
applicationEnvironmentPreparedEvent,
|
||||
"otel.instrumentation.logback-appender.experimental.capture-code-attributes");
|
||||
if (codeAttribute != null) {
|
||||
openTelemetryAppender.setCaptureCodeAttributes(codeAttribute.booleanValue());
|
||||
}
|
||||
|
||||
Boolean markerAttribute =
|
||||
evaluateBooleanProperty(
|
||||
applicationEnvironmentPreparedEvent,
|
||||
"otel.instrumentation.logback-appender.experimental.capture-marker-attribute");
|
||||
if (markerAttribute != null) {
|
||||
openTelemetryAppender.setCaptureMarkerAttribute(markerAttribute.booleanValue());
|
||||
}
|
||||
|
||||
Boolean keyValuePairAttributes =
|
||||
evaluateBooleanProperty(
|
||||
applicationEnvironmentPreparedEvent,
|
||||
"otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes");
|
||||
if (keyValuePairAttributes != null) {
|
||||
openTelemetryAppender.setCaptureKeyValuePairAttributes(keyValuePairAttributes.booleanValue());
|
||||
}
|
||||
|
||||
Boolean logAttributes =
|
||||
evaluateBooleanProperty(
|
||||
applicationEnvironmentPreparedEvent,
|
||||
"otel.instrumentation.logback-appender.experimental-log-attributes");
|
||||
if (logAttributes != null) {
|
||||
openTelemetryAppender.setCaptureExperimentalAttributes(logAttributes.booleanValue());
|
||||
}
|
||||
|
||||
Boolean loggerContextAttributes =
|
||||
evaluateBooleanProperty(
|
||||
applicationEnvironmentPreparedEvent,
|
||||
"otel.instrumentation.logback-appender.experimental.capture-logger-context-attributes");
|
||||
if (loggerContextAttributes != null) {
|
||||
openTelemetryAppender.setCaptureLoggerContext(loggerContextAttributes.booleanValue());
|
||||
}
|
||||
|
||||
String mdcAttributeProperty =
|
||||
applicationEnvironmentPreparedEvent
|
||||
.getEnvironment()
|
||||
.getProperty(
|
||||
"otel.instrumentation.logback-appender.experimental.capture-mdc-attributes",
|
||||
String.class);
|
||||
if (mdcAttributeProperty != null) {
|
||||
openTelemetryAppender.setCaptureMdcAttributes(mdcAttributeProperty);
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean evaluateBooleanProperty(
|
||||
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, String property) {
|
||||
return applicationEnvironmentPreparedEvent
|
||||
.getEnvironment()
|
||||
.getProperty(property, Boolean.class);
|
||||
}
|
||||
|
||||
private static Optional<OpenTelemetryAppender> findOpenTelemetryAppender() {
|
||||
ILoggerFactory loggerFactorySpi = LoggerFactory.getILoggerFactory();
|
||||
if (!(loggerFactorySpi instanceof LoggerContext)) {
|
||||
return false;
|
||||
return Optional.empty();
|
||||
}
|
||||
LoggerContext loggerContext = (LoggerContext) loggerFactorySpi;
|
||||
for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) {
|
||||
|
@ -77,11 +172,12 @@ public class LogbackAppenderApplicationListener implements GenericApplicationLis
|
|||
while (appenderIterator.hasNext()) {
|
||||
Appender<ILoggingEvent> appender = appenderIterator.next();
|
||||
if (appender instanceof OpenTelemetryAppender) {
|
||||
return true;
|
||||
OpenTelemetryAppender openTelemetryAppender = (OpenTelemetryAppender) appender;
|
||||
return Optional.of(openTelemetryAppender);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "otel"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "otel.instrumentation.logback-appender.experimental.capture-code-attributes",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enable the capture of source code attributes. Note that capturing source code attributes at logging sites might add a performance overhead.",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "otel.instrumentation.logback-appender.experimental.capture-marker-attribute",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enable the capture of Logback markers as attributes.",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enable the capture of Logback key value pairs as attributes.",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "otel.instrumentation.logback-appender.experimental-log-attributes",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enable the capture of experimental log attributes thread.name and thread.id.",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "otel.instrumentation.logback-appender.experimental.capture-logger-context-attributes",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enable the capture of Logback logger context properties as attributes.",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "otel.instrumentation.logback-appender.experimental.capture-mdc-attributes",
|
||||
"type": "java.lang.String",
|
||||
"description": "Comma separated list of MDC attributes to capture. Use the wildcard character * to capture all attributes."
|
||||
}
|
||||
]
|
||||
}
|
|
@ -8,16 +8,21 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.lo
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import io.opentelemetry.api.common.AttributeKey;
|
||||
import io.opentelemetry.api.common.Attributes;
|
||||
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
|
||||
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
|
||||
import io.opentelemetry.sdk.logs.data.LogRecordData;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -49,6 +54,10 @@ class LogbackAppenderTest {
|
|||
void shouldInitializeAppender() {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("logging.config", "classpath:logback-test.xml");
|
||||
properties.put(
|
||||
"otel.instrumentation.logback-appender.experimental.capture-mdc-attributes", "*");
|
||||
properties.put(
|
||||
"otel.instrumentation.logback-appender.experimental.capture-code-attributes", false);
|
||||
|
||||
SpringApplication app =
|
||||
new SpringApplication(
|
||||
|
@ -57,15 +66,30 @@ class LogbackAppenderTest {
|
|||
ConfigurableApplicationContext context = app.run();
|
||||
cleanup.deferCleanup(context);
|
||||
|
||||
LoggerFactory.getLogger("test").info("test log message");
|
||||
MDC.put("key1", "val1");
|
||||
MDC.put("key2", "val2");
|
||||
try {
|
||||
LoggerFactory.getLogger("test").info("test log message");
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
|
||||
assertThat(testing.logRecords())
|
||||
List<LogRecordData> logRecords = testing.logRecords();
|
||||
assertThat(logRecords)
|
||||
.satisfiesOnlyOnce(
|
||||
// OTel appender automatically added or from an XML file, it should not
|
||||
// be added a second time by LogbackAppenderApplicationListener
|
||||
logRecord -> {
|
||||
assertThat(logRecord.getInstrumentationScopeInfo().getName()).isEqualTo("test");
|
||||
assertThat(logRecord.getBody().asString()).contains("test log message");
|
||||
|
||||
Attributes attributes = logRecord.getAttributes();
|
||||
// key1 and key2, the code attributes should not be present because they are enabled
|
||||
// in the logback.xml file but are disabled with a property
|
||||
assertThat(attributes.size()).isEqualTo(2);
|
||||
assertThat(attributes.asMap())
|
||||
.containsEntry(AttributeKey.stringKey("key1"), "val1")
|
||||
.containsEntry(AttributeKey.stringKey("key2"), "val2");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
</encoder>
|
||||
</appender>
|
||||
<appender name="OpenTelemetry"
|
||||
class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"/>
|
||||
class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
|
||||
<captureCodeAttributes>true</captureCodeAttributes>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="console"/>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
otel.instrumentation.logback-appender.experimental.capture-code-attributes=true
|
|
@ -121,5 +121,9 @@ class OtelSpringStarterSmokeTest {
|
|||
.as("Should instrument logs")
|
||||
.startsWith("Starting ")
|
||||
.contains(this.getClass().getSimpleName());
|
||||
assertThat(firstLog.getAttributes().asMap())
|
||||
.as("Should capture code attributes")
|
||||
.containsEntry(
|
||||
SemanticAttributes.CODE_NAMESPACE, "org.springframework.boot.StartupInfoLogger");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue