Skip to content

Commit fa5548b

Browse files
authored
Fix spring boot starter failing without logback (#10802)
1 parent 2df9001 commit fa5548b

File tree

4 files changed

+219
-145
lines changed

4 files changed

+219
-145
lines changed

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

+21
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,27 @@ testing {
105105
}
106106
}
107107
}
108+
109+
suites {
110+
val testLogbackMissing by registering(JvmTestSuite::class) {
111+
dependencies {
112+
implementation(project())
113+
implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion")
114+
115+
implementation("org.slf4j:slf4j-api") {
116+
version {
117+
strictly("1.7.32")
118+
}
119+
}
120+
}
121+
}
122+
}
123+
}
124+
125+
configurations.configureEach {
126+
if (name.contains("testLogbackMissing")) {
127+
exclude("ch.qos.logback", "logback-classic")
128+
}
108129
}
109130

110131
tasks {

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

+16-145
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,6 @@
55

66
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.logging;
77

8-
import ch.qos.logback.classic.LoggerContext;
9-
import ch.qos.logback.classic.spi.ILoggingEvent;
10-
import ch.qos.logback.core.Appender;
11-
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
12-
import java.util.Iterator;
13-
import java.util.Optional;
14-
import org.slf4j.ILoggerFactory;
15-
import org.slf4j.Logger;
16-
import org.slf4j.LoggerFactory;
178
import org.springframework.boot.SpringApplication;
189
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
1910
import org.springframework.boot.context.logging.LoggingApplicationListener;
@@ -27,8 +18,8 @@ public class LogbackAppenderApplicationListener implements GenericApplicationLis
2718
private static final Class<?>[] SOURCE_TYPES = {
2819
SpringApplication.class, ApplicationContext.class
2920
};
30-
3121
private static final Class<?>[] EVENT_TYPES = {ApplicationEnvironmentPreparedEvent.class};
22+
private static final boolean LOGBACK_PRESENT = isLogbackPresent();
3223

3324
@Override
3425
public boolean supportsSourceType(Class<?> sourceType) {
@@ -53,149 +44,29 @@ private static boolean isAssignableFrom(Class<?> type, Class<?>... supportedType
5344

5445
@Override
5546
public void onApplicationEvent(ApplicationEvent event) {
56-
if (event instanceof ApplicationEnvironmentPreparedEvent // Event for which
57-
// org.springframework.boot.context.logging.LoggingApplicationListener
58-
// initializes logging
59-
) {
60-
Optional<OpenTelemetryAppender> existingOpenTelemetryAppender = findOpenTelemetryAppender();
61-
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent =
62-
(ApplicationEnvironmentPreparedEvent) event;
63-
if (existingOpenTelemetryAppender.isPresent()) {
64-
reInitializeOpenTelemetryAppender(
65-
existingOpenTelemetryAppender, applicationEnvironmentPreparedEvent);
66-
} else if (isLogbackAppenderAddable(applicationEnvironmentPreparedEvent)) {
67-
addOpenTelemetryAppender(applicationEnvironmentPreparedEvent);
68-
}
69-
}
70-
}
71-
72-
private static boolean isLogbackAppenderAddable(
73-
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
74-
Boolean otelSdkDisableProperty =
75-
evaluateBooleanProperty(applicationEnvironmentPreparedEvent, "otel.sdk.disabled");
76-
Boolean logbackInstrumentationEnabledProperty =
77-
evaluateBooleanProperty(
78-
applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-appender.enabled");
79-
return otelSdkDisableProperty == null
80-
|| !otelSdkDisableProperty.booleanValue()
81-
|| logbackInstrumentationEnabledProperty == null
82-
|| logbackInstrumentationEnabledProperty.booleanValue();
83-
}
84-
85-
private static void reInitializeOpenTelemetryAppender(
86-
Optional<OpenTelemetryAppender> existingOpenTelemetryAppender,
87-
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
88-
OpenTelemetryAppender openTelemetryAppender = existingOpenTelemetryAppender.get();
89-
// The OpenTelemetry appender is stopped and restarted from the
90-
// org.springframework.boot.context.logging.LoggingApplicationListener.initialize
91-
// method.
92-
// The OpenTelemetryAppender initializes the LoggingEventMapper in the start() method. So, here
93-
// we stop the OpenTelemetry appender before its re-initialization and its restart.
94-
openTelemetryAppender.stop();
95-
initializeOpenTelemetryAppenderFromProperties(
96-
applicationEnvironmentPreparedEvent, openTelemetryAppender);
97-
openTelemetryAppender.start();
98-
}
99-
100-
private static void addOpenTelemetryAppender(
101-
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
102-
ch.qos.logback.classic.Logger logger =
103-
(ch.qos.logback.classic.Logger)
104-
LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME);
105-
OpenTelemetryAppender openTelemetryAppender = new OpenTelemetryAppender();
106-
initializeOpenTelemetryAppenderFromProperties(
107-
applicationEnvironmentPreparedEvent, openTelemetryAppender);
108-
openTelemetryAppender.start();
109-
logger.addAppender(openTelemetryAppender);
110-
}
111-
112-
private static void initializeOpenTelemetryAppenderFromProperties(
113-
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent,
114-
OpenTelemetryAppender openTelemetryAppender) {
115-
116-
// Implemented in the same way as the
117-
// org.springframework.boot.context.logging.LoggingApplicationListener, config properties not
118-
// available
119-
Boolean codeAttribute =
120-
evaluateBooleanProperty(
121-
applicationEnvironmentPreparedEvent,
122-
"otel.instrumentation.logback-appender.experimental.capture-code-attributes");
123-
if (codeAttribute != null) {
124-
openTelemetryAppender.setCaptureCodeAttributes(codeAttribute.booleanValue());
125-
}
126-
127-
Boolean markerAttribute =
128-
evaluateBooleanProperty(
129-
applicationEnvironmentPreparedEvent,
130-
"otel.instrumentation.logback-appender.experimental.capture-marker-attribute");
131-
if (markerAttribute != null) {
132-
openTelemetryAppender.setCaptureMarkerAttribute(markerAttribute.booleanValue());
133-
}
134-
135-
Boolean keyValuePairAttributes =
136-
evaluateBooleanProperty(
137-
applicationEnvironmentPreparedEvent,
138-
"otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes");
139-
if (keyValuePairAttributes != null) {
140-
openTelemetryAppender.setCaptureKeyValuePairAttributes(keyValuePairAttributes.booleanValue());
141-
}
142-
143-
Boolean logAttributes =
144-
evaluateBooleanProperty(
145-
applicationEnvironmentPreparedEvent,
146-
"otel.instrumentation.logback-appender.experimental-log-attributes");
147-
if (logAttributes != null) {
148-
openTelemetryAppender.setCaptureExperimentalAttributes(logAttributes.booleanValue());
149-
}
150-
151-
Boolean loggerContextAttributes =
152-
evaluateBooleanProperty(
153-
applicationEnvironmentPreparedEvent,
154-
"otel.instrumentation.logback-appender.experimental.capture-logger-context-attributes");
155-
if (loggerContextAttributes != null) {
156-
openTelemetryAppender.setCaptureLoggerContext(loggerContextAttributes.booleanValue());
47+
if (!LOGBACK_PRESENT) {
48+
return;
15749
}
15850

159-
String mdcAttributeProperty =
160-
applicationEnvironmentPreparedEvent
161-
.getEnvironment()
162-
.getProperty(
163-
"otel.instrumentation.logback-appender.experimental.capture-mdc-attributes",
164-
String.class);
165-
if (mdcAttributeProperty != null) {
166-
openTelemetryAppender.setCaptureMdcAttributes(mdcAttributeProperty);
167-
}
168-
}
169-
170-
private static Boolean evaluateBooleanProperty(
171-
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, String property) {
172-
return applicationEnvironmentPreparedEvent
173-
.getEnvironment()
174-
.getProperty(property, Boolean.class);
175-
}
176-
177-
private static Optional<OpenTelemetryAppender> findOpenTelemetryAppender() {
178-
ILoggerFactory loggerFactorySpi = LoggerFactory.getILoggerFactory();
179-
if (!(loggerFactorySpi instanceof LoggerContext)) {
180-
return Optional.empty();
181-
}
182-
LoggerContext loggerContext = (LoggerContext) loggerFactorySpi;
183-
for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) {
184-
Iterator<Appender<ILoggingEvent>> appenderIterator = logger.iteratorForAppenders();
185-
while (appenderIterator.hasNext()) {
186-
Appender<ILoggingEvent> appender = appenderIterator.next();
187-
if (appender instanceof OpenTelemetryAppender) {
188-
OpenTelemetryAppender openTelemetryAppender = (OpenTelemetryAppender) appender;
189-
return Optional.of(openTelemetryAppender);
190-
}
191-
}
51+
// Event for which org.springframework.boot.context.logging.LoggingApplicationListener
52+
// initializes logging
53+
if (event instanceof ApplicationEnvironmentPreparedEvent) {
54+
LogbackAppenderInstaller.install((ApplicationEnvironmentPreparedEvent) event);
19255
}
193-
return Optional.empty();
19456
}
19557

19658
@Override
19759
public int getOrder() {
19860
return LoggingApplicationListener.DEFAULT_ORDER + 1; // To execute this listener just after
19961
// org.springframework.boot.context.logging.LoggingApplicationListener
20062
}
63+
64+
private static boolean isLogbackPresent() {
65+
try {
66+
Class.forName("ch.qos.logback.core.Appender");
67+
return true;
68+
} catch (ClassNotFoundException exception) {
69+
return false;
70+
}
71+
}
20172
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.logging;
7+
8+
import ch.qos.logback.classic.LoggerContext;
9+
import ch.qos.logback.classic.spi.ILoggingEvent;
10+
import ch.qos.logback.core.Appender;
11+
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
12+
import java.util.Iterator;
13+
import java.util.Optional;
14+
import org.slf4j.ILoggerFactory;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
18+
19+
class LogbackAppenderInstaller {
20+
21+
static void install(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
22+
Optional<OpenTelemetryAppender> existingOpenTelemetryAppender = findOpenTelemetryAppender();
23+
if (existingOpenTelemetryAppender.isPresent()) {
24+
reInitializeOpenTelemetryAppender(
25+
existingOpenTelemetryAppender, applicationEnvironmentPreparedEvent);
26+
} else if (isLogbackAppenderAddable(applicationEnvironmentPreparedEvent)) {
27+
addOpenTelemetryAppender(applicationEnvironmentPreparedEvent);
28+
}
29+
}
30+
31+
private static boolean isLogbackAppenderAddable(
32+
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
33+
Boolean otelSdkDisableProperty =
34+
evaluateBooleanProperty(applicationEnvironmentPreparedEvent, "otel.sdk.disabled");
35+
Boolean logbackInstrumentationEnabledProperty =
36+
evaluateBooleanProperty(
37+
applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-appender.enabled");
38+
return otelSdkDisableProperty == null
39+
|| !otelSdkDisableProperty.booleanValue()
40+
|| logbackInstrumentationEnabledProperty == null
41+
|| logbackInstrumentationEnabledProperty.booleanValue();
42+
}
43+
44+
private static void reInitializeOpenTelemetryAppender(
45+
Optional<OpenTelemetryAppender> existingOpenTelemetryAppender,
46+
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
47+
OpenTelemetryAppender openTelemetryAppender = existingOpenTelemetryAppender.get();
48+
// The OpenTelemetry appender is stopped and restarted from the
49+
// org.springframework.boot.context.logging.LoggingApplicationListener.initialize
50+
// method.
51+
// The OpenTelemetryAppender initializes the LoggingEventMapper in the start() method. So, here
52+
// we stop the OpenTelemetry appender before its re-initialization and its restart.
53+
openTelemetryAppender.stop();
54+
initializeOpenTelemetryAppenderFromProperties(
55+
applicationEnvironmentPreparedEvent, openTelemetryAppender);
56+
openTelemetryAppender.start();
57+
}
58+
59+
private static void addOpenTelemetryAppender(
60+
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
61+
ch.qos.logback.classic.Logger logger =
62+
(ch.qos.logback.classic.Logger)
63+
LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME);
64+
OpenTelemetryAppender openTelemetryAppender = new OpenTelemetryAppender();
65+
initializeOpenTelemetryAppenderFromProperties(
66+
applicationEnvironmentPreparedEvent, openTelemetryAppender);
67+
openTelemetryAppender.start();
68+
logger.addAppender(openTelemetryAppender);
69+
}
70+
71+
private static void initializeOpenTelemetryAppenderFromProperties(
72+
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent,
73+
OpenTelemetryAppender openTelemetryAppender) {
74+
75+
// Implemented in the same way as the
76+
// org.springframework.boot.context.logging.LoggingApplicationListener, config properties not
77+
// available
78+
Boolean codeAttribute =
79+
evaluateBooleanProperty(
80+
applicationEnvironmentPreparedEvent,
81+
"otel.instrumentation.logback-appender.experimental.capture-code-attributes");
82+
if (codeAttribute != null) {
83+
openTelemetryAppender.setCaptureCodeAttributes(codeAttribute.booleanValue());
84+
}
85+
86+
Boolean markerAttribute =
87+
evaluateBooleanProperty(
88+
applicationEnvironmentPreparedEvent,
89+
"otel.instrumentation.logback-appender.experimental.capture-marker-attribute");
90+
if (markerAttribute != null) {
91+
openTelemetryAppender.setCaptureMarkerAttribute(markerAttribute.booleanValue());
92+
}
93+
94+
Boolean keyValuePairAttributes =
95+
evaluateBooleanProperty(
96+
applicationEnvironmentPreparedEvent,
97+
"otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes");
98+
if (keyValuePairAttributes != null) {
99+
openTelemetryAppender.setCaptureKeyValuePairAttributes(keyValuePairAttributes.booleanValue());
100+
}
101+
102+
Boolean logAttributes =
103+
evaluateBooleanProperty(
104+
applicationEnvironmentPreparedEvent,
105+
"otel.instrumentation.logback-appender.experimental-log-attributes");
106+
if (logAttributes != null) {
107+
openTelemetryAppender.setCaptureExperimentalAttributes(logAttributes.booleanValue());
108+
}
109+
110+
Boolean loggerContextAttributes =
111+
evaluateBooleanProperty(
112+
applicationEnvironmentPreparedEvent,
113+
"otel.instrumentation.logback-appender.experimental.capture-logger-context-attributes");
114+
if (loggerContextAttributes != null) {
115+
openTelemetryAppender.setCaptureLoggerContext(loggerContextAttributes.booleanValue());
116+
}
117+
118+
String mdcAttributeProperty =
119+
applicationEnvironmentPreparedEvent
120+
.getEnvironment()
121+
.getProperty(
122+
"otel.instrumentation.logback-appender.experimental.capture-mdc-attributes",
123+
String.class);
124+
if (mdcAttributeProperty != null) {
125+
openTelemetryAppender.setCaptureMdcAttributes(mdcAttributeProperty);
126+
}
127+
}
128+
129+
private static Boolean evaluateBooleanProperty(
130+
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, String property) {
131+
return applicationEnvironmentPreparedEvent
132+
.getEnvironment()
133+
.getProperty(property, Boolean.class);
134+
}
135+
136+
private static Optional<OpenTelemetryAppender> findOpenTelemetryAppender() {
137+
ILoggerFactory loggerFactorySpi = LoggerFactory.getILoggerFactory();
138+
if (!(loggerFactorySpi instanceof LoggerContext)) {
139+
return Optional.empty();
140+
}
141+
LoggerContext loggerContext = (LoggerContext) loggerFactorySpi;
142+
for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) {
143+
Iterator<Appender<ILoggingEvent>> appenderIterator = logger.iteratorForAppenders();
144+
while (appenderIterator.hasNext()) {
145+
Appender<ILoggingEvent> appender = appenderIterator.next();
146+
if (appender instanceof OpenTelemetryAppender) {
147+
OpenTelemetryAppender openTelemetryAppender = (OpenTelemetryAppender) appender;
148+
return Optional.of(openTelemetryAppender);
149+
}
150+
}
151+
}
152+
return Optional.empty();
153+
}
154+
155+
private LogbackAppenderInstaller() {}
156+
}

0 commit comments

Comments
 (0)