diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogger.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogger.java index e3ab57a971..cab91c25a5 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogger.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogger.java @@ -13,19 +13,17 @@ import io.opentelemetry.sdk.logs.internal.LoggerConfig; /** SDK implementation of {@link ExtendedLogger}. */ final class ExtendedSdkLogger extends SdkLogger implements ExtendedLogger { - private final boolean loggerEnabled; - ExtendedSdkLogger( LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo, LoggerConfig loggerConfig) { super(loggerSharedState, instrumentationScopeInfo, loggerConfig); - this.loggerEnabled = loggerConfig.isEnabled(); } @Override + @SuppressWarnings("RedundantOverride") public boolean isEnabled() { - return loggerEnabled; + return super.isEnabled(); } @Override diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogger.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogger.java index 8ac5160307..2f8b950674 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogger.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogger.java @@ -30,7 +30,10 @@ class SdkLogger implements Logger { private final LoggerSharedState loggerSharedState; private final InstrumentationScopeInfo instrumentationScopeInfo; - private final boolean loggerEnabled; + + // deliberately not volatile because of performance concerns + // - which means its eventually consistent + protected boolean loggerEnabled; SdkLogger( LoggerSharedState loggerSharedState, @@ -65,4 +68,12 @@ class SdkLogger implements Logger { InstrumentationScopeInfo getInstrumentationScopeInfo() { return instrumentationScopeInfo; } + + public boolean isEnabled() { + return loggerEnabled; + } + + void updateLoggerConfig(LoggerConfig loggerConfig) { + loggerEnabled = loggerConfig.isEnabled(); + } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java index af999c8e43..12f7baa201 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java @@ -36,9 +36,12 @@ public final class SdkLoggerProvider implements LoggerProvider, Closeable { private final LoggerSharedState sharedState; private final ComponentRegistry loggerComponentRegistry; - private final ScopeConfigurator loggerConfigurator; private final boolean isNoopLogRecordProcessor; + // deliberately not volatile because of performance concerns + // - which means its eventually consistent + private ScopeConfigurator loggerConfigurator; + /** * Returns a new {@link SdkLoggerProviderBuilder} for {@link SdkLoggerProvider}. * @@ -96,6 +99,26 @@ public final class SdkLoggerProvider implements LoggerProvider, Closeable { return instrumentationScopeName; } + /** + * Updates the logger configurator, which computes {@link LoggerConfig} for each {@link + * InstrumentationScopeInfo}. + * + *

This method is experimental so not public. You may reflectively call it using {@link + * io.opentelemetry.sdk.logs.internal.SdkLoggerProviderUtil#setLoggerConfigurator(SdkLoggerProvider, + * ScopeConfigurator)}. + * + * @see LoggerConfig#configuratorBuilder() + */ + void setLoggerConfigurator(ScopeConfigurator loggerConfigurator) { + this.loggerConfigurator = loggerConfigurator; + this.loggerComponentRegistry + .getComponents() + .forEach( + sdkLogger -> + sdkLogger.updateLoggerConfig( + getLoggerConfig(sdkLogger.getInstrumentationScopeInfo()))); + } + /** * Request the active log processor to process all logs that have not yet been processed. * diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java index e44aa07fd4..fa659a747e 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java @@ -7,6 +7,7 @@ package io.opentelemetry.sdk.logs.internal; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.internal.ScopeConfigurator; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -23,6 +24,21 @@ public final class SdkLoggerProviderUtil { private SdkLoggerProviderUtil() {} + /** Reflectively set the {@link ScopeConfigurator} to the {@link SdkLoggerProvider}. */ + public static void setLoggerConfigurator( + SdkLoggerProvider sdkLoggerProvider, ScopeConfigurator scopeConfigurator) { + try { + Method method = + SdkLoggerProvider.class.getDeclaredMethod( + "setLoggerConfigurator", ScopeConfigurator.class); + method.setAccessible(true); + method.invoke(sdkLoggerProvider, scopeConfigurator); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException( + "Error calling setLoggerConfigurator on SdkLoggerProvider", e); + } + } + /** Reflectively set the {@link ScopeConfigurator} to the {@link SdkLoggerProviderBuilder}. */ public static SdkLoggerProviderBuilder setLoggerConfigurator( SdkLoggerProviderBuilder sdkLoggerProviderBuilder, diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java index dd3d94048c..cb0f08a86b 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java @@ -28,7 +28,10 @@ import io.opentelemetry.internal.testing.slf4j.SuppressLogger; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.internal.LoggerConfig; +import io.opentelemetry.sdk.logs.internal.SdkLoggerProviderUtil; import io.opentelemetry.sdk.resources.Resource; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -350,4 +353,28 @@ class SdkLoggerProviderTest { + "loggerConfigurator=ScopeConfiguratorImpl{conditions=[]}" + "}"); } + + private static ScopeConfigurator flipConfigurator(boolean enabled) { + return scopeInfo -> enabled ? LoggerConfig.disabled() : LoggerConfig.enabled(); + } + + @Test + void propagatesEnablementToLoggerDirectly() { + SdkLogger logger = (SdkLogger) sdkLoggerProvider.get("test"); + boolean isEnabled = logger.isEnabled(); + + sdkLoggerProvider.setLoggerConfigurator(flipConfigurator(isEnabled)); + + assertThat(logger.isEnabled()).isEqualTo(!isEnabled); + } + + @Test + void propagatesEnablementToLoggerByUtil() { + SdkLogger logger = (SdkLogger) sdkLoggerProvider.get("test"); + boolean isEnabled = logger.isEnabled(); + + SdkLoggerProviderUtil.setLoggerConfigurator(sdkLoggerProvider, flipConfigurator(isEnabled)); + + assertThat(logger.isEnabled()).isEqualTo(!isEnabled); + } } diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java index 2ea1ee1929..01aa76feb1 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java @@ -133,4 +133,17 @@ class SdkLoggerTest { verify(logRecordProcessor, never()).onEmit(any(), any()); } + + @Test + void updateEnabled() { + LogRecordProcessor logRecordProcessor = mock(LogRecordProcessor.class); + SdkLoggerProvider loggerProvider = + SdkLoggerProvider.builder().addLogRecordProcessor(logRecordProcessor).build(); + SdkLogger logger = (SdkLogger) loggerProvider.get("test"); + + logger.updateLoggerConfig(LoggerConfig.disabled()); + assertThat(logger.isEnabled()).isFalse(); + logger.updateLoggerConfig(LoggerConfig.enabled()); + assertThat(logger.isEnabled()).isTrue(); + } } diff --git a/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/LoggerConfigTest.java b/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/LoggerConfigTest.java index f94b97388b..e58f97c0a1 100644 --- a/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/LoggerConfigTest.java +++ b/sdk/logs/src/testIncubating/java/io/opentelemetry/sdk/logs/LoggerConfigTest.java @@ -8,6 +8,7 @@ package io.opentelemetry.sdk.logs; import static io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder.nameEquals; import static io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder.nameMatchesGlob; import static io.opentelemetry.sdk.logs.internal.LoggerConfig.defaultConfig; +import static io.opentelemetry.sdk.logs.internal.LoggerConfig.disabled; import static io.opentelemetry.sdk.logs.internal.LoggerConfig.enabled; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; @@ -37,7 +38,7 @@ class LoggerConfigTest { SdkLoggerProvider.builder() // Disable loggerB. Since loggers are enabled by default, loggerA and loggerC are // enabled. - .addLoggerConfiguratorCondition(nameEquals("loggerB"), LoggerConfig.disabled()) + .addLoggerConfiguratorCondition(nameEquals("loggerB"), disabled()) .addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter)) .build(); @@ -86,25 +87,23 @@ class LoggerConfigTest { LoggerConfig.configuratorBuilder().build(); ScopeConfigurator disableCat = LoggerConfig.configuratorBuilder() - .addCondition(nameEquals("cat"), LoggerConfig.disabled()) + .addCondition(nameEquals("cat"), disabled()) // Second matching rule for cat should be ignored .addCondition(nameEquals("cat"), enabled()) .build(); ScopeConfigurator disableStartsWithD = - LoggerConfig.configuratorBuilder() - .addCondition(nameMatchesGlob("d*"), LoggerConfig.disabled()) - .build(); + LoggerConfig.configuratorBuilder().addCondition(nameMatchesGlob("d*"), disabled()).build(); ScopeConfigurator enableCat = LoggerConfig.configuratorBuilder() - .setDefault(LoggerConfig.disabled()) + .setDefault(disabled()) .addCondition(nameEquals("cat"), enabled()) // Second matching rule for cat should be ignored - .addCondition(nameEquals("cat"), LoggerConfig.disabled()) + .addCondition(nameEquals("cat"), disabled()) .build(); ScopeConfigurator enableStartsWithD = LoggerConfig.configuratorBuilder() - .setDefault(LoggerConfig.disabled()) - .addCondition(nameMatchesGlob("d*"), LoggerConfig.enabled()) + .setDefault(disabled()) + .addCondition(nameMatchesGlob("d*"), enabled()) .build(); return Stream.of( @@ -113,20 +112,83 @@ class LoggerConfigTest { Arguments.of(defaultConfigurator, scopeDog, defaultConfig()), Arguments.of(defaultConfigurator, scopeDuck, defaultConfig()), // default enabled, disable cat - Arguments.of(disableCat, scopeCat, LoggerConfig.disabled()), + Arguments.of(disableCat, scopeCat, disabled()), Arguments.of(disableCat, scopeDog, enabled()), Arguments.of(disableCat, scopeDuck, enabled()), // default enabled, disable pattern Arguments.of(disableStartsWithD, scopeCat, enabled()), - Arguments.of(disableStartsWithD, scopeDog, LoggerConfig.disabled()), - Arguments.of(disableStartsWithD, scopeDuck, LoggerConfig.disabled()), + Arguments.of(disableStartsWithD, scopeDog, disabled()), + Arguments.of(disableStartsWithD, scopeDuck, disabled()), // default disabled, enable cat Arguments.of(enableCat, scopeCat, enabled()), - Arguments.of(enableCat, scopeDog, LoggerConfig.disabled()), - Arguments.of(enableCat, scopeDuck, LoggerConfig.disabled()), + Arguments.of(enableCat, scopeDog, disabled()), + Arguments.of(enableCat, scopeDuck, disabled()), // default disabled, enable pattern - Arguments.of(enableStartsWithD, scopeCat, LoggerConfig.disabled()), + Arguments.of(enableStartsWithD, scopeCat, disabled()), Arguments.of(enableStartsWithD, scopeDog, enabled()), Arguments.of(enableStartsWithD, scopeDuck, enabled())); } + + @Test + void setScopeConfigurator() { + // 1. Initially, configure all loggers to be enabled except loggerB + InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create(); + SdkLoggerProvider loggerProvider = + SdkLoggerProvider.builder() + .addLoggerConfiguratorCondition(nameEquals("loggerB"), disabled()) + .addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter)) + .build(); + + ExtendedSdkLogger loggerA = (ExtendedSdkLogger) loggerProvider.get("loggerA"); + ExtendedSdkLogger loggerB = (ExtendedSdkLogger) loggerProvider.get("loggerB"); + ExtendedSdkLogger loggerC = (ExtendedSdkLogger) loggerProvider.get("loggerC"); + + // verify isEnabled() + assertThat(loggerA.isEnabled()).isTrue(); + assertThat(loggerB.isEnabled()).isFalse(); + assertThat(loggerC.isEnabled()).isTrue(); + + // verify logs are emitted as expected + loggerA.logRecordBuilder().setBody("logA").emit(); + loggerB.logRecordBuilder().setBody("logB").emit(); + loggerC.logRecordBuilder().setBody("logC").emit(); + assertThat(exporter.getFinishedLogRecordItems()) + .satisfiesExactlyInAnyOrder( + log -> assertThat(log).hasBody("logA"), log -> assertThat(log).hasBody("logC")); + exporter.reset(); + + // 2. Update config to disable all loggers + loggerProvider.setLoggerConfigurator( + ScopeConfigurator.builder().setDefault(disabled()).build()); + + // verify isEnabled() + assertThat(loggerA.isEnabled()).isFalse(); + assertThat(loggerB.isEnabled()).isFalse(); + assertThat(loggerC.isEnabled()).isFalse(); + + // verify logs are emitted as expected + loggerA.logRecordBuilder().setBody("logA").emit(); + loggerB.logRecordBuilder().setBody("logB").emit(); + loggerC.logRecordBuilder().setBody("logC").emit(); + assertThat(exporter.getFinishedLogRecordItems()).isEmpty(); + + // 3. Update config to restore original + loggerProvider.setLoggerConfigurator( + ScopeConfigurator.builder() + .addCondition(nameEquals("loggerB"), disabled()) + .build()); + + // verify isEnabled() + assertThat(loggerA.isEnabled()).isTrue(); + assertThat(loggerB.isEnabled()).isFalse(); + assertThat(loggerC.isEnabled()).isTrue(); + + // verify logs are emitted as expected + loggerA.logRecordBuilder().setBody("logA").emit(); + loggerB.logRecordBuilder().setBody("logB").emit(); + loggerC.logRecordBuilder().setBody("logC").emit(); + assertThat(exporter.getFinishedLogRecordItems()) + .satisfiesExactly( + log -> assertThat(log).hasBody("logA"), log -> assertThat(log).hasBody("logC")); + } }