Skip to content

Commit 0521ac1

Browse files
authored
Add logback mdc instrumentation to spring boot starter (#12515)
1 parent 86ee95c commit 0521ac1

File tree

7 files changed

+277
-9
lines changed

7 files changed

+277
-9
lines changed

instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,10 @@ public void setOpenTelemetry(OpenTelemetry openTelemetry) {
204204
try {
205205
// minimize scope of write lock
206206
this.openTelemetry = openTelemetry;
207-
this.eventsToReplay.drainTo(eventsToReplay);
207+
// tests set openTelemetry to null, ignore it
208+
if (openTelemetry != null) {
209+
this.eventsToReplay.drainTo(eventsToReplay);
210+
}
208211
} finally {
209212
writeLock.unlock();
210213
}

instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ dependencies {
5050
implementation(project(":instrumentation:log4j:log4j-appender-2.17:library"))
5151
compileOnly("org.apache.logging.log4j:log4j-core:2.17.0")
5252
implementation(project(":instrumentation:logback:logback-appender-1.0:library"))
53+
implementation(project(":instrumentation:logback:logback-mdc-1.0:library"))
5354
compileOnly("ch.qos.logback:logback-classic:1.0.0")
5455
implementation(project(":instrumentation:jdbc:library"))
5556

instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderInstaller.java

+81-8
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,19 @@
1919
class LogbackAppenderInstaller {
2020

2121
static void install(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
22-
Optional<OpenTelemetryAppender> existingOpenTelemetryAppender = findOpenTelemetryAppender();
22+
Optional<io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender>
23+
existingMdcAppender =
24+
findAppender(
25+
io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender.class);
26+
if (existingMdcAppender.isPresent()) {
27+
initializeMdcAppenderFromProperties(
28+
applicationEnvironmentPreparedEvent, existingMdcAppender.get());
29+
} else if (isLogbackMdcAppenderAddable(applicationEnvironmentPreparedEvent)) {
30+
addMdcAppender(applicationEnvironmentPreparedEvent);
31+
}
32+
33+
Optional<OpenTelemetryAppender> existingOpenTelemetryAppender =
34+
findAppender(OpenTelemetryAppender.class);
2335
if (existingOpenTelemetryAppender.isPresent()) {
2436
reInitializeOpenTelemetryAppender(
2537
existingOpenTelemetryAppender, applicationEnvironmentPreparedEvent);
@@ -30,13 +42,22 @@ static void install(ApplicationEnvironmentPreparedEvent applicationEnvironmentPr
3042

3143
private static boolean isLogbackAppenderAddable(
3244
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
45+
return isAppenderAddable(
46+
applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-appender.enabled");
47+
}
48+
49+
private static boolean isLogbackMdcAppenderAddable(
50+
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
51+
return isAppenderAddable(
52+
applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-mdc.enabled");
53+
}
54+
55+
private static boolean isAppenderAddable(
56+
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, String property) {
3357
boolean otelSdkDisabled =
3458
evaluateBooleanProperty(applicationEnvironmentPreparedEvent, "otel.sdk.disabled", false);
3559
boolean logbackInstrumentationEnabled =
36-
evaluateBooleanProperty(
37-
applicationEnvironmentPreparedEvent,
38-
"otel.instrumentation.logback-appender.enabled",
39-
true);
60+
evaluateBooleanProperty(applicationEnvironmentPreparedEvent, property, true);
4061
return !otelSdkDisabled && logbackInstrumentationEnabled;
4162
}
4263

@@ -133,6 +154,58 @@ private static void initializeOpenTelemetryAppenderFromProperties(
133154
}
134155
}
135156

157+
private static void addMdcAppender(
158+
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
159+
ch.qos.logback.classic.Logger logger =
160+
(ch.qos.logback.classic.Logger)
161+
LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME);
162+
io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender openTelemetryAppender =
163+
new io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender();
164+
initializeMdcAppenderFromProperties(applicationEnvironmentPreparedEvent, openTelemetryAppender);
165+
openTelemetryAppender.start();
166+
logger.addAppender(openTelemetryAppender);
167+
}
168+
169+
private static void initializeMdcAppenderFromProperties(
170+
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent,
171+
io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender
172+
openTelemetryAppender) {
173+
174+
// Implemented in the same way as the
175+
// org.springframework.boot.context.logging.LoggingApplicationListener, config properties not
176+
// available
177+
Boolean addBaggage =
178+
evaluateBooleanProperty(
179+
applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-mdc.add-baggage");
180+
if (addBaggage != null) {
181+
openTelemetryAppender.setAddBaggage(addBaggage);
182+
}
183+
184+
String traceIdKey =
185+
applicationEnvironmentPreparedEvent
186+
.getEnvironment()
187+
.getProperty("otel.instrumentation.common.logging.trace-id", String.class);
188+
if (traceIdKey != null) {
189+
openTelemetryAppender.setTraceIdKey(traceIdKey);
190+
}
191+
192+
String spanIdKey =
193+
applicationEnvironmentPreparedEvent
194+
.getEnvironment()
195+
.getProperty("otel.instrumentation.common.logging.span-id", String.class);
196+
if (spanIdKey != null) {
197+
openTelemetryAppender.setSpanIdKey(spanIdKey);
198+
}
199+
200+
String traceFlagsKey =
201+
applicationEnvironmentPreparedEvent
202+
.getEnvironment()
203+
.getProperty("otel.instrumentation.common.logging.trace-flags", String.class);
204+
if (traceFlagsKey != null) {
205+
openTelemetryAppender.setTraceFlagsKey(traceFlagsKey);
206+
}
207+
}
208+
136209
private static Boolean evaluateBooleanProperty(
137210
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, String property) {
138211
return applicationEnvironmentPreparedEvent
@@ -149,7 +222,7 @@ private static boolean evaluateBooleanProperty(
149222
.getProperty(property, Boolean.class, defaultValue);
150223
}
151224

152-
private static Optional<OpenTelemetryAppender> findOpenTelemetryAppender() {
225+
private static <T> Optional<T> findAppender(Class<T> appenderClass) {
153226
ILoggerFactory loggerFactorySpi = LoggerFactory.getILoggerFactory();
154227
if (!(loggerFactorySpi instanceof LoggerContext)) {
155228
return Optional.empty();
@@ -159,8 +232,8 @@ private static Optional<OpenTelemetryAppender> findOpenTelemetryAppender() {
159232
Iterator<Appender<ILoggingEvent>> appenderIterator = logger.iteratorForAppenders();
160233
while (appenderIterator.hasNext()) {
161234
Appender<ILoggingEvent> appender = appenderIterator.next();
162-
if (appender instanceof OpenTelemetryAppender) {
163-
OpenTelemetryAppender openTelemetryAppender = (OpenTelemetryAppender) appender;
235+
if (appenderClass.isInstance(appender)) {
236+
T openTelemetryAppender = appenderClass.cast(appender);
164237
return Optional.of(openTelemetryAppender);
165238
}
166239
}

instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

+30
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,36 @@
416416
"type": "java.util.List<java.lang.String>",
417417
"description": "MDC attributes to capture. Use the wildcard character <code>*</code> to capture all attributes."
418418
},
419+
{
420+
"name": "otel.instrumentation.logback-mdc.enabled",
421+
"type": "java.lang.Boolean",
422+
"description": "Enable the Logback MDC instrumentation.",
423+
"defaultValue": true
424+
},
425+
{
426+
"name": "otel.instrumentation.logback-mdc.add-baggage",
427+
"type": "java.lang.Boolean",
428+
"description": "Enable exposing baggage attributes through MDC.",
429+
"defaultValue": false
430+
},
431+
{
432+
"name": "otel.instrumentation.common.logging.trace-id",
433+
"type": "java.lang.String",
434+
"description": "Customize MDC key name for the trace id.",
435+
"defaultValue": "trace_id"
436+
},
437+
{
438+
"name": "otel.instrumentation.common.logging.span-id",
439+
"type": "java.lang.String",
440+
"description": "Customize MDC key name for the span id.",
441+
"defaultValue": "span_id"
442+
},
443+
{
444+
"name": "otel.instrumentation.common.logging.trace-flags",
445+
"type": "java.lang.String",
446+
"description": "Customize MDC key name for the trace flags.",
447+
"defaultValue": "trace_flags"
448+
},
419449
{
420450
"name": "otel.instrumentation.micrometer.enabled",
421451
"type": "java.lang.Boolean",

instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderTest.java

+137
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@
77

88
import static org.assertj.core.api.Assertions.assertThat;
99

10+
import ch.qos.logback.classic.spi.ILoggingEvent;
11+
import ch.qos.logback.core.read.ListAppender;
12+
import ch.qos.logback.core.spi.AppenderAttachable;
1013
import io.opentelemetry.api.OpenTelemetry;
14+
import io.opentelemetry.api.baggage.Baggage;
1115
import io.opentelemetry.api.common.AttributeKey;
1216
import io.opentelemetry.api.common.Attributes;
17+
import io.opentelemetry.api.trace.Span;
18+
import io.opentelemetry.context.Scope;
1319
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
1420
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
1521
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
@@ -21,6 +27,7 @@
2127
import org.junit.jupiter.api.BeforeEach;
2228
import org.junit.jupiter.api.Test;
2329
import org.junit.jupiter.api.extension.RegisterExtension;
30+
import org.slf4j.Logger;
2431
import org.slf4j.LoggerFactory;
2532
import org.slf4j.MDC;
2633
import org.springframework.boot.SpringApplication;
@@ -66,6 +73,9 @@ void shouldInitializeAppender() {
6673
ConfigurableApplicationContext context = app.run();
6774
cleanup.deferCleanup(context);
6875

76+
ListAppender<ILoggingEvent> listAppender = getListAppender();
77+
listAppender.list.clear();
78+
6979
MDC.put("key1", "val1");
7080
MDC.put("key2", "val2");
7181
try {
@@ -91,6 +101,14 @@ void shouldInitializeAppender() {
91101
.containsEntry(AttributeKey.stringKey("key1"), "val1")
92102
.containsEntry(AttributeKey.stringKey("key2"), "val2");
93103
});
104+
105+
assertThat(listAppender.list)
106+
.satisfiesExactly(
107+
event ->
108+
assertThat(event)
109+
.satisfies(
110+
e -> assertThat(e.getMessage()).isEqualTo("test log message"),
111+
e -> assertThat(e.getMDCPropertyMap()).containsOnlyKeys("key1", "key2")));
94112
}
95113

96114
@Test
@@ -110,4 +128,123 @@ void shouldNotInitializeAppenderWhenDisabled() {
110128

111129
assertThat(testing.logRecords()).isEmpty();
112130
}
131+
132+
@Test
133+
void mdcAppender() {
134+
Map<String, Object> properties = new HashMap<>();
135+
properties.put("logging.config", "classpath:logback-test.xml");
136+
properties.put("otel.instrumentation.logback-appender.enabled", "false");
137+
properties.put("otel.instrumentation.logback-mdc.add-baggage", "true");
138+
properties.put("otel.instrumentation.common.logging.trace-id", "traceid");
139+
properties.put("otel.instrumentation.common.logging.span-id", "spanid");
140+
properties.put("otel.instrumentation.common.logging.trace-flags", "traceflags");
141+
142+
SpringApplication app =
143+
new SpringApplication(
144+
TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class);
145+
app.setDefaultProperties(properties);
146+
ConfigurableApplicationContext context = app.run();
147+
cleanup.deferCleanup(context);
148+
149+
ListAppender<ILoggingEvent> listAppender = getListAppender();
150+
listAppender.list.clear();
151+
152+
try (Scope ignore = Baggage.current().toBuilder().put("key", "value").build().makeCurrent()) {
153+
Span span = testing.getOpenTelemetry().getTracer("test").spanBuilder("test").startSpan();
154+
try (Scope ignore2 = span.makeCurrent()) {
155+
LoggerFactory.getLogger("test").info("test log message");
156+
}
157+
}
158+
159+
assertThat(testing.logRecords()).isEmpty();
160+
assertThat(listAppender.list)
161+
.satisfiesExactly(
162+
event ->
163+
assertThat(event)
164+
.satisfies(
165+
e -> assertThat(e.getMessage()).isEqualTo("test log message"),
166+
e ->
167+
assertThat(e.getMDCPropertyMap())
168+
.containsOnlyKeys(
169+
"traceid", "spanid", "traceflags", "baggage.key")));
170+
}
171+
172+
@Test
173+
void shouldInitializeMdcAppender() {
174+
Map<String, Object> properties = new HashMap<>();
175+
properties.put("logging.config", "classpath:logback-no-otel-appenders.xml");
176+
properties.put("otel.instrumentation.logback-appender.enabled", "false");
177+
178+
SpringApplication app =
179+
new SpringApplication(
180+
TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class);
181+
app.setDefaultProperties(properties);
182+
ConfigurableApplicationContext context = app.run();
183+
cleanup.deferCleanup(context);
184+
185+
ListAppender<ILoggingEvent> listAppender = getListAppender();
186+
listAppender.list.clear();
187+
188+
Span span = testing.getOpenTelemetry().getTracer("test").spanBuilder("test").startSpan();
189+
try (Scope ignore = span.makeCurrent()) {
190+
LoggerFactory.getLogger("test").info("test log message");
191+
}
192+
193+
assertThat(testing.logRecords()).isEmpty();
194+
assertThat(listAppender.list)
195+
.satisfiesExactly(
196+
event ->
197+
assertThat(event)
198+
.satisfies(
199+
e -> assertThat(e.getMessage()).isEqualTo("test log message"),
200+
e ->
201+
assertThat(e.getMDCPropertyMap())
202+
.containsOnlyKeys("trace_id", "span_id", "trace_flags")));
203+
}
204+
205+
@Test
206+
void shouldNotInitializeMdcAppenderWhenDisabled() {
207+
Map<String, Object> properties = new HashMap<>();
208+
properties.put("logging.config", "classpath:logback-no-otel-appenders.xml");
209+
properties.put("otel.instrumentation.logback-appender.enabled", "false");
210+
properties.put("otel.instrumentation.logback-mdc.enabled", "false");
211+
212+
SpringApplication app =
213+
new SpringApplication(
214+
TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class);
215+
app.setDefaultProperties(properties);
216+
ConfigurableApplicationContext context = app.run();
217+
cleanup.deferCleanup(context);
218+
219+
ListAppender<ILoggingEvent> listAppender = getListAppender();
220+
listAppender.list.clear();
221+
222+
Span span = testing.getOpenTelemetry().getTracer("test").spanBuilder("test").startSpan();
223+
try (Scope ignore = span.makeCurrent()) {
224+
LoggerFactory.getLogger("test").info("test log message");
225+
}
226+
227+
assertThat(testing.logRecords()).isEmpty();
228+
assertThat(listAppender.list)
229+
.satisfiesExactly(
230+
event ->
231+
assertThat(event)
232+
.satisfies(
233+
e -> assertThat(e.getMessage()).isEqualTo("test log message"),
234+
e -> assertThat(e.getMDCPropertyMap()).isEmpty()));
235+
}
236+
237+
@SuppressWarnings("unchecked")
238+
private static ListAppender<ILoggingEvent> getListAppender() {
239+
Logger logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
240+
ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) logger;
241+
ListAppender<ILoggingEvent> listAppender =
242+
(ListAppender<ILoggingEvent>) logbackLogger.getAppender("List");
243+
if (listAppender != null) {
244+
return listAppender;
245+
}
246+
AppenderAttachable<?> mdcAppender =
247+
(AppenderAttachable<?>) logbackLogger.getAppender("OpenTelemetryMdc");
248+
return (ListAppender<ILoggingEvent>) mdcAppender.getAppender("List");
249+
}
113250
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration>
3+
4+
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
5+
<encoder>
6+
<pattern>
7+
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
8+
</pattern>
9+
</encoder>
10+
</appender>
11+
<appender name="List" class="ch.qos.logback.core.read.ListAppender" />
12+
13+
<root level="INFO">
14+
<appender-ref ref="console"/>
15+
<appender-ref ref="List"/>
16+
</root>
17+
18+
</configuration>

instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/resources/logback-test.xml

+6
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,16 @@
1212
class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
1313
<captureCodeAttributes>true</captureCodeAttributes>
1414
</appender>
15+
<appender name="List" class="ch.qos.logback.core.read.ListAppender" />
16+
<appender name="OpenTelemetryMdc"
17+
class="io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender">
18+
<appender-ref ref="List"/>
19+
</appender>
1520

1621
<root level="INFO">
1722
<appender-ref ref="console"/>
1823
<appender-ref ref="OpenTelemetry"/>
24+
<appender-ref ref="OpenTelemetryMdc"/>
1925
</root>
2026

2127
</configuration>

0 commit comments

Comments
 (0)