From 21551f2f816e5640d8352e08ce99a31acba30ee1 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Mon, 11 Mar 2024 15:46:30 +0200 Subject: [PATCH 1/3] Fix spring boot starter failing without logback --- .../LogbackAppenderApplicationListener.java | 161 ++---------------- .../logging/LogbackAppenderInstaller.java | 156 +++++++++++++++++ 2 files changed, 172 insertions(+), 145 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackAppenderInstaller.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackAppenderApplicationListener.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackAppenderApplicationListener.java index ae45e546b286..646e8119bc01 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackAppenderApplicationListener.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackAppenderApplicationListener.java @@ -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; @@ -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) { @@ -53,144 +44,15 @@ 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 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 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 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> appenderIterator = logger.iteratorForAppenders(); - while (appenderIterator.hasNext()) { - Appender 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 @@ -198,4 +60,13 @@ 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; + } + } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackAppenderInstaller.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackAppenderInstaller.java new file mode 100644 index 000000000000..811dacb31bf7 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackAppenderInstaller.java @@ -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 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 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 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> appenderIterator = logger.iteratorForAppenders(); + while (appenderIterator.hasNext()) { + Appender appender = appenderIterator.next(); + if (appender instanceof OpenTelemetryAppender) { + OpenTelemetryAppender openTelemetryAppender = (OpenTelemetryAppender) appender; + return Optional.of(openTelemetryAppender); + } + } + } + return Optional.empty(); + } + + private LogbackAppenderInstaller() {} +} From 567ed4a74e2ce2f1fe52b719179e0a0379c792fa Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Mon, 11 Mar 2024 21:39:32 +0200 Subject: [PATCH 2/3] add test --- .../build.gradle.kts | 23 ++++++++++++++++ .../logging/LogbackMissingTest.java | 26 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testLogbackMissing/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackMissingTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index ffe05a2e8c9b..2a37d324e5c7 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -105,6 +105,29 @@ testing { } } } + + suites { + val testLogbackMissing by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-sdk-testing") + 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 { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackMissing/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackMissingTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackMissing/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackMissingTest.java new file mode 100644 index 000000000000..4afcc33f32aa --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackMissing/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/logging/LogbackMissingTest.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.logging; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class LogbackMissingTest { + + @Test + void applicationStartsWhenLogbackIsMissing() { + // verify that logback is not present + Assertions.assertThrows( + ClassNotFoundException.class, () -> Class.forName("ch.qos.logback.core.Appender")); + + SpringApplication app = new SpringApplication(OpenTelemetryAppenderAutoConfiguration.class); + try (ConfigurableApplicationContext ignore = app.run()) { + // do nothing + } + } +} From 79a83381a83f8617b7e79fdfed18f801e116de37 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Mon, 11 Mar 2024 21:42:23 +0200 Subject: [PATCH 3/3] remove unused dependencies --- .../spring/spring-boot-autoconfigure/build.gradle.kts | 2 -- 1 file changed, 2 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 2a37d324e5c7..58a6709dac9d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -110,8 +110,6 @@ testing { val testLogbackMissing by registering(JvmTestSuite::class) { dependencies { implementation(project()) - implementation("io.opentelemetry:opentelemetry-sdk") - implementation("io.opentelemetry:opentelemetry-sdk-testing") implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion") implementation("org.slf4j:slf4j-api") {