Skip to content

Commit

Permalink
Fix spring boot starter failing without logback (#10802)
Browse files Browse the repository at this point in the history
  • Loading branch information
laurit authored Mar 12, 2024
1 parent 2df9001 commit fa5548b
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 145 deletions.
21 changes: 21 additions & 0 deletions instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,27 @@ testing {
}
}
}

suites {
val testLogbackMissing by registering(JvmTestSuite::class) {
dependencies {
implementation(project())
implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion")

implementation("org.slf4j:slf4j-api") {
version {
strictly("1.7.32")
}
}
}
}
}
}

configurations.configureEach {
if (name.contains("testLogbackMissing")) {
exclude("ch.qos.logback", "logback-classic")
}
}

tasks {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@

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

import ch.qos.logback.classic.LoggerContext;
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;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.logging.LoggingApplicationListener;
Expand All @@ -27,8 +18,8 @@ public class LogbackAppenderApplicationListener implements GenericApplicationLis
private static final Class<?>[] SOURCE_TYPES = {
SpringApplication.class, ApplicationContext.class
};

private static final Class<?>[] EVENT_TYPES = {ApplicationEnvironmentPreparedEvent.class};
private static final boolean LOGBACK_PRESENT = isLogbackPresent();

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

@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent // Event for which
// org.springframework.boot.context.logging.LoggingApplicationListener
// initializes logging
) {
Optional<OpenTelemetryAppender> existingOpenTelemetryAppender = findOpenTelemetryAppender();
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent =
(ApplicationEnvironmentPreparedEvent) event;
if (existingOpenTelemetryAppender.isPresent()) {
reInitializeOpenTelemetryAppender(
existingOpenTelemetryAppender, applicationEnvironmentPreparedEvent);
} else if (isLogbackAppenderAddable(applicationEnvironmentPreparedEvent)) {
addOpenTelemetryAppender(applicationEnvironmentPreparedEvent);
}
}
}

private static boolean isLogbackAppenderAddable(
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
Boolean otelSdkDisableProperty =
evaluateBooleanProperty(applicationEnvironmentPreparedEvent, "otel.sdk.disabled");
Boolean logbackInstrumentationEnabledProperty =
evaluateBooleanProperty(
applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-appender.enabled");
return otelSdkDisableProperty == null
|| !otelSdkDisableProperty.booleanValue()
|| logbackInstrumentationEnabledProperty == null
|| logbackInstrumentationEnabledProperty.booleanValue();
}

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());
if (!LOGBACK_PRESENT) {
return;
}

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 Optional.empty();
}
LoggerContext loggerContext = (LoggerContext) loggerFactorySpi;
for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) {
Iterator<Appender<ILoggingEvent>> appenderIterator = logger.iteratorForAppenders();
while (appenderIterator.hasNext()) {
Appender<ILoggingEvent> appender = appenderIterator.next();
if (appender instanceof OpenTelemetryAppender) {
OpenTelemetryAppender openTelemetryAppender = (OpenTelemetryAppender) appender;
return Optional.of(openTelemetryAppender);
}
}
// Event for which org.springframework.boot.context.logging.LoggingApplicationListener
// initializes logging
if (event instanceof ApplicationEnvironmentPreparedEvent) {
LogbackAppenderInstaller.install((ApplicationEnvironmentPreparedEvent) event);
}
return Optional.empty();
}

@Override
public int getOrder() {
return LoggingApplicationListener.DEFAULT_ORDER + 1; // To execute this listener just after
// org.springframework.boot.context.logging.LoggingApplicationListener
}

private static boolean isLogbackPresent() {
try {
Class.forName("ch.qos.logback.core.Appender");
return true;
} catch (ClassNotFoundException exception) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

import ch.qos.logback.classic.LoggerContext;
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;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;

class LogbackAppenderInstaller {

static void install(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
Optional<OpenTelemetryAppender> existingOpenTelemetryAppender = findOpenTelemetryAppender();
if (existingOpenTelemetryAppender.isPresent()) {
reInitializeOpenTelemetryAppender(
existingOpenTelemetryAppender, applicationEnvironmentPreparedEvent);
} else if (isLogbackAppenderAddable(applicationEnvironmentPreparedEvent)) {
addOpenTelemetryAppender(applicationEnvironmentPreparedEvent);
}
}

private static boolean isLogbackAppenderAddable(
ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
Boolean otelSdkDisableProperty =
evaluateBooleanProperty(applicationEnvironmentPreparedEvent, "otel.sdk.disabled");
Boolean logbackInstrumentationEnabledProperty =
evaluateBooleanProperty(
applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-appender.enabled");
return otelSdkDisableProperty == null
|| !otelSdkDisableProperty.booleanValue()
|| logbackInstrumentationEnabledProperty == null
|| logbackInstrumentationEnabledProperty.booleanValue();
}

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 Optional.empty();
}
LoggerContext loggerContext = (LoggerContext) loggerFactorySpi;
for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) {
Iterator<Appender<ILoggingEvent>> appenderIterator = logger.iteratorForAppenders();
while (appenderIterator.hasNext()) {
Appender<ILoggingEvent> appender = appenderIterator.next();
if (appender instanceof OpenTelemetryAppender) {
OpenTelemetryAppender openTelemetryAppender = (OpenTelemetryAppender) appender;
return Optional.of(openTelemetryAppender);
}
}
}
return Optional.empty();
}

private LogbackAppenderInstaller() {}
}
Loading

0 comments on commit fa5548b

Please sign in to comment.