diff --git a/products/feature-flagging/feature-flagging-agent/src/main/java/com/datadog/featureflag/FeatureFlaggingSystem.java b/products/feature-flagging/feature-flagging-agent/src/main/java/com/datadog/featureflag/FeatureFlaggingSystem.java index 0b971d3f6f0..8bdd58f2be8 100644 --- a/products/feature-flagging/feature-flagging-agent/src/main/java/com/datadog/featureflag/FeatureFlaggingSystem.java +++ b/products/feature-flagging/feature-flagging-agent/src/main/java/com/datadog/featureflag/FeatureFlaggingSystem.java @@ -33,10 +33,13 @@ public static synchronized void start(final SharedCommunicationObjects sco) { LOGGER.debug("Feature Flagging system disabled"); return; } + LOGGER.debug("Feature Flagging enabled; env={}", config.getEnv()); final FeatureFlagsBackend backend = new FeatureFlagsBackend(sco, config); try { backend.start(); } catch (final RuntimeException | Error e) { + LOGGER.debug( + "Feature Flagging system initialization failed: {}", e.getClass().getSimpleName()); backend.close(); throw e; } diff --git a/products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/RemoteConfigServiceImpl.java b/products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/RemoteConfigServiceImpl.java index 837e377dec4..a0aae50ed9a 100644 --- a/products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/RemoteConfigServiceImpl.java +++ b/products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/RemoteConfigServiceImpl.java @@ -39,6 +39,7 @@ public synchronized void start() { return; } this.started = true; + LOGGER.debug("Feature Flagging starting remote_config configuration source"); this.configurationPoller.addCapabilities(CAPABILITY_FFE_FLAG_CONFIGURATION_RULES); // Listen as serialized event to prevent depending on the deserializer from agent this.configurationPoller.addListener(FFE_FLAGS, content -> content, this); @@ -70,6 +71,7 @@ public synchronized void close() { return; } this.started = false; + LOGGER.debug("Feature Flagging remote_config configuration source stopped"); this.configurationPoller.removeCapabilities(CAPABILITY_FFE_FLAG_CONFIGURATION_RULES); this.configurationPoller.removeListeners(FFE_FLAGS); this.subscribers.clear(); @@ -94,5 +96,14 @@ public void accept( LOGGER.debug("Feature Flags configuration subscriber failed", e); } } + // Logged last: a diagnostic must not get in the way of the delivery. + if (content == null) { + LOGGER.debug("Feature Flagging remote_config configuration removed: key={}", configKey); + } else { + LOGGER.debug( + "Feature Flagging remote_config configuration received: key={}, bytes={}", + configKey, + content.length); + } } } diff --git a/products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/RemoteConfigServiceImplTest.java b/products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/RemoteConfigServiceImplTest.java index 6bac29d44f9..ec70db6d9f8 100644 --- a/products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/RemoteConfigServiceImplTest.java +++ b/products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/RemoteConfigServiceImplTest.java @@ -5,6 +5,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -15,6 +16,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; import datadog.remoteconfig.Capabilities; import datadog.remoteconfig.ConfigurationDeserializer; import datadog.remoteconfig.ConfigurationPoller; @@ -28,8 +33,13 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.slf4j.LoggerFactory; class RemoteConfigServiceImplTest { private static final byte[] DOCUMENT = "{\"flags\":{}}".getBytes(UTF_8); @@ -178,4 +188,72 @@ void closeUnregistersTheProductAndDropsSubscribers() { verify(poller, never()).stop(); assertEquals(0, received.size()); } + + @Nested + class Diagnostics { + private final Logger logger = (Logger) LoggerFactory.getLogger(RemoteConfigServiceImpl.class); + private final ListAppender appender = new ListAppender<>(); + private Level previousLevel; + private RemoteConfigServiceImpl service; + + @BeforeEach + void captureLogs() { + previousLevel = logger.getLevel(); + logger.setLevel(Level.DEBUG); + appender.start(); + logger.addAppender(appender); + service = new RemoteConfigServiceImpl(mock(ConfigurationPoller.class)); + } + + @AfterEach + void restoreLogging() { + logger.detachAppender(appender); + appender.stop(); + logger.setLevel(previousLevel); + } + + @Test + void reportsLifecycle() { + service.start(); + service.close(); + + assertLogged("starting remote_config configuration source"); + assertLogged("remote_config configuration source stopped"); + } + + @Test + void reportsReceivedConfiguration() { + service.accept("config", DOCUMENT, mock(PollingRateHinter.class)); + + assertLogged("configuration received: key=config, bytes=" + DOCUMENT.length); + } + + @Test + void reportsRemovedConfiguration() { + service.accept("config", null, mock(PollingRateHinter.class)); + + assertLogged("configuration removed: key=config"); + } + + @Test + void reportsTheDocumentOnlyOnceItIsDelivered() { + final AtomicBoolean loggedBeforeDelivery = new AtomicBoolean(true); + service.subscribe( + content -> + loggedBeforeDelivery.set( + appender.list.stream() + .anyMatch(e -> e.getFormattedMessage().contains("configuration received")))); + + service.accept("config", DOCUMENT, mock(PollingRateHinter.class)); + + assertFalse(loggedBeforeDelivery.get(), "The log must not delay the delivery"); + assertLogged("configuration received: key=config, bytes=" + DOCUMENT.length); + } + + private void assertLogged(final String text) { + assertTrue( + appender.list.stream().anyMatch(e -> e.getFormattedMessage().contains(text)), + "Missing log: " + text); + } + } }