Add `setLoggerConfigurator` support to `LoggerProvider` (#7332)

This commit is contained in:
Francesco Andreuzzi 2025-05-09 16:14:27 +02:00 committed by GitHub
parent 58acb531c5
commit 983133fd0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 171 additions and 21 deletions

View File

@ -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

View File

@ -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();
}
}

View File

@ -36,9 +36,12 @@ public final class SdkLoggerProvider implements LoggerProvider, Closeable {
private final LoggerSharedState sharedState;
private final ComponentRegistry<SdkLogger> loggerComponentRegistry;
private final ScopeConfigurator<LoggerConfig> loggerConfigurator;
private final boolean isNoopLogRecordProcessor;
// deliberately not volatile because of performance concerns
// - which means its eventually consistent
private ScopeConfigurator<LoggerConfig> 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}.
*
* <p>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<LoggerConfig> 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.
*

View File

@ -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<LoggerConfig> 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,

View File

@ -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<LoggerConfig> 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);
}
}

View File

@ -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();
}
}

View File

@ -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<LoggerConfig> 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<LoggerConfig> disableStartsWithD =
LoggerConfig.configuratorBuilder()
.addCondition(nameMatchesGlob("d*"), LoggerConfig.disabled())
.build();
LoggerConfig.configuratorBuilder().addCondition(nameMatchesGlob("d*"), disabled()).build();
ScopeConfigurator<LoggerConfig> 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<LoggerConfig> 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.<LoggerConfig>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.<LoggerConfig>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"));
}
}