diff --git a/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/LDClient.java b/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/LDClient.java index 939fb51f..18f314ca 100644 --- a/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/LDClient.java +++ b/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/LDClient.java @@ -40,6 +40,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import static com.google.common.base.Preconditions.checkNotNull; import static com.launchdarkly.sdk.server.DataModel.FEATURES; @@ -66,6 +67,8 @@ public final class LDClient implements LDClientInterface { private final ScheduledExecutorService sharedExecutor; private final LDLogger baseLogger; private final LDLogger evaluationLogger; + // isFlagKnown logs its cached-data warning once per client. This flag records that log. + private final AtomicBoolean isFlagKnownCachedDataWarned = new AtomicBoolean(false); private static final int EXCESSIVE_INIT_WAIT_MILLIS = 60000; @@ -429,7 +432,10 @@ public boolean isFlagKnown(String featureKey) { if (!isInitialized()) { if (store.isInitialized()) { - baseLogger.warn("isFlagKnown called before client initialized for feature flag \"{}\"; using last known values from data store", featureKey); + if (isFlagKnownCachedDataWarned.compareAndSet(false, true)) { + baseLogger.warn("isFlagKnown called before client initialized for feature flag \"{}\"; " + + "using last known values from data store. This message is logged once.", featureKey); + } } else { baseLogger.warn("isFlagKnown called before client initialized for feature flag \"{}\"; data store unavailable, returning false", featureKey); return false; diff --git a/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java b/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java index 1be860f0..e313aa68 100644 --- a/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java +++ b/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java @@ -1,5 +1,6 @@ package com.launchdarkly.sdk.server; +import com.launchdarkly.logging.LDLogLevel; import com.launchdarkly.sdk.LDContext; import com.launchdarkly.sdk.LDValue; import com.launchdarkly.sdk.server.integrations.MockPersistentDataStore; @@ -427,6 +428,30 @@ public void isFlagKnownReturnsFalseIfStoreAndClientAreNotInitialized() throws Ex mocks.verifyAll(); } + @Test + public void isFlagKnownLogsCachedDataWarningOnlyOnce() throws Exception { + DataStore testDataStore = initedDataStore(); + LDConfig.Builder config = new LDConfig.Builder() + .startWait(Duration.ZERO) + .dataStore(specificComponent(testDataStore)); + expect(dataSource.start()).andReturn(initFuture); + expect(dataSource.isInitialized()).andReturn(false).times(2); + mocks.replayAll(); + + client = createMockClient(config); + + upsertFlag(testDataStore, flagWithValue("key", LDValue.of(1))); + assertTrue(client.isFlagKnown("key")); + assertTrue(client.isFlagKnown("key")); + + long warningCount = logCapture.getMessages().stream() + .filter(m -> m.getLevel() == LDLogLevel.WARN + && m.getText().contains("using last known values from data store")) + .count(); + assertEquals(1, warningCount); + mocks.verifyAll(); + } + @Test public void isFlagKnownUsesStoreIfStoreIsInitializedButClientIsNot() throws Exception { DataStore testDataStore = initedDataStore();