diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3bacac93b..3dfa4e2e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,13 +56,15 @@ The project in the `benchmarks` subdirectory uses [JMH](https://openjdk.java.net ### Logging -Currently the SDK uses SLF4J for all log output. Here some things to keep in mind for good logging behavior: +The SDK uses a LaunchDarkly logging facade, [`com.launchdarkly.logging`](https://github.com/launchdarkly/java-logging). By default, this facade sends output to SLF4J. + +Here some things to keep in mind for good logging behavior: 1. Stick to the standardized logger name scheme defined in `Loggers.java`, preferably for all log output, but definitely for all log output above `DEBUG` level. Logger names can be useful for filtering log output, so it is desirable for users to be able to reference a clear, stable logger name like `com.launchdarkly.sdk.server.LDClient.Events` rather than a class name like `com.launchdarkly.sdk.server.EventSummarizer` which is an implementation detail. The text of a log message should be distinctive enough that we can easily find which class generated the message. -2. Use parameterized messages (`Logger.MAIN.info("The value is {}", someValue)`) rather than string concatenation (`Logger.MAIN.info("The value is " + someValue)`). This avoids the overhead of string concatenation if the logger is not enabled for that level. If computing the value is an expensive operation, and it is _only_ relevant for logging, consider implementing that computation via a custom `toString()` method on some wrapper type so that it will be done lazily only if the log level is enabled. +2. Use parameterized messages (`logger.info("The value is {}", someValue)`) rather than string concatenation (`logger.info("The value is " + someValue)`). This avoids the overhead of string concatenation if the logger is not enabled for that level. If computing the value is an expensive operation, and it is _only_ relevant for logging, consider implementing that computation via a custom `toString()` method on some wrapper type so that it will be done lazily only if the log level is enabled. -3. Exception stacktraces should only be logged at debug level. For instance: `Logger.MAIN.warn("An error happened: {}", ex.toString()); Logger.MAIN.debug(ex.toString(), ex)`. Also, consider whether the stacktrace would be at all meaningful in this particular context; for instance, in a `try` block around a network I/O operation, the stacktrace would only tell us (a) some internal location in Java standard libraries and (b) the location in our own code where we tried to do the operation; (a) is very unlikely to tell us anything that the exception's type and message doesn't already tell us, and (b) could be more clearly communicated by just writing a specific log message. +3. There is a standard pattern for logging exceptions, using the `com.launchdarkly.logging.LogValues` helpers. First, log the basic description of the exception at whatever level is appropriate (`WARN` or `ERROR`): `logger.warn("An error happened: {}", LogValues.exceptionSummary(ex))`. Then, log a stack at debug level: `logger.debug(LogValues.exceptionTrace(ex))`. The `exceptionTrace` helper is lazily evaluated so that the stacktrace will only be computed if debug logging is actually enabled. However, consider whether the stacktrace would be at all meaningful in this particular context; for instance, in a `try` block around a network I/O operation, the stacktrace would only tell us (a) some internal location in Java standard libraries and (b) the location in our own code where we tried to do the operation; (a) is very unlikely to tell us anything that the exception's type and message doesn't already tell us, and (b) could be more clearly communicated by just writing a specific log message. ### Code coverage diff --git a/README.md b/README.md index 48d0beb15..402fa37a2 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,13 @@ Refer to the [SDK reference guide](https://docs.launchdarkly.com/sdk/server-side ## Logging -The LaunchDarkly SDK uses [SLF4J](https://www.slf4j.org/). All loggers are namespaced under `com.launchdarkly`. For an example configuration check out the [hello-java](https://github.com/launchdarkly/hello-java) project. +By default, the LaunchDarkly SDK uses [SLF4J](https://www.slf4j.org/). SLF4J has its own configuration mechanisms for determining where output will go, and filtering by level and/or logger name. + +The SDK can also be configured to use other adapters from the [com.launchdarkly.logging](https://github.com/launchdarkly/java-logging) facade instead of SLF4J. See `LoggingConfigurationBuilder`. This allows the logging behavior to be completely determined by the application, rather than by external SLF4J configuration. + +For an example of using the default SLF4J behavior with a simple console logging configuration, check out the [`slf4j-logging` branch](https://github.com/launchdarkly/hello-java/tree/slf4j-logging) of the [`hello-java`](https://github.com/launchdarkly/hello-java) project. The [main branch](https://github.com/launchdarkly/hello-java) of `hello-java` uses console logging that is programmatically configured without SLF4J. + +All loggers are namespaced under `com.launchdarkly`, if you are using name-based filtering. Be aware of two considerations when enabling the DEBUG log level: 1. Debug-level logs can be very verbose. It is not recommended that you turn on debug logging in high-volume environments. diff --git a/benchmarks/src/jmh/java/com/launchdarkly/sdk/server/LDClientEvaluationBenchmarks.java b/benchmarks/src/jmh/java/com/launchdarkly/sdk/server/LDClientEvaluationBenchmarks.java index a8d1f5e44..8f389caa6 100644 --- a/benchmarks/src/jmh/java/com/launchdarkly/sdk/server/LDClientEvaluationBenchmarks.java +++ b/benchmarks/src/jmh/java/com/launchdarkly/sdk/server/LDClientEvaluationBenchmarks.java @@ -54,6 +54,7 @@ public BenchmarkInputs() { .dataStore(specificDataStore(dataStore)) .events(Components.noEvents()) .dataSource(Components.externalUpdatesOnly()) + .logging(Components.noLogging()) .build(); client = new LDClient(SDK_KEY, config); diff --git a/build.gradle b/build.gradle index 1a4a467d2..606309d07 100644 --- a/build.gradle +++ b/build.gradle @@ -73,6 +73,7 @@ ext.versions = [ "guava": "30.1-jre", "jackson": "2.11.2", "launchdarklyJavaSdkCommon": "1.3.0", + "launchdarklyLogging": "1.1.0", "okhttp": "4.9.3", // specify this for the SDK build instead of relying on the transitive dependency from okhttp-eventsource "okhttpEventsource": "2.6.2", "slf4j": "1.7.21", @@ -131,6 +132,7 @@ libraries.internal = [ // also as package exports (i.e. it provides them if a newer version is not available // from an import). libraries.external = [ + "com.launchdarkly:launchdarkly-logging:${versions.launchdarklyLogging}", "org.slf4j:slf4j-api:${versions.slf4j}" ] @@ -322,6 +324,14 @@ javadoc { // Use test classpath so Javadoc won't complain about java-sdk-common classes that internally // reference stuff we don't use directly, like Jackson classpath = sourceSets.test.compileClasspath + + // The following should allow hyperlinks to com.launchdarkly.logging classes to go to + // the correct external URLs + if (options instanceof StandardJavadocDocletOptions) { + (options as StandardJavadocDocletOptions).links( + "https://javadoc.io/doc/com.launchdarkly/launchdarkly-logging/${versions.launchdarklyLogging}" + ) + } } // Force the Javadoc build to fail if there are any Javadoc warnings. See: https://discuss.gradle.org/t/javadoc-fail-on-warning/18141/3 diff --git a/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java b/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java index 8e620e0ce..89658be12 100644 --- a/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java +++ b/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java @@ -42,7 +42,7 @@ public SdkClientEntity(TestService owner, CreateInstanceParams params) { this.logger = LoggerFactory.getLogger(params.tag); logger.info("Starting SDK client"); - LDConfig config = buildSdkConfig(params.configuration); + LDConfig config = buildSdkConfig(params.configuration, params.tag); this.client = new LDClient(params.configuration.credential, config); if (!client.isInitialized() && !params.configuration.initCanFail) { throw new RuntimeException("client initialization failed or timed out"); @@ -184,9 +184,11 @@ public void close() { logger.info("Test ended"); } - private LDConfig buildSdkConfig(SdkConfigParams params) { + private LDConfig buildSdkConfig(SdkConfigParams params, String tag) { LDConfig.Builder builder = new LDConfig.Builder(); + builder.logging(Components.logging().baseLoggerName(tag + ".sdk")); + if (params.startWaitTimeMs != null) { builder.startWait(Duration.ofMillis(params.startWaitTimeMs.longValue())); } diff --git a/src/main/java/com/launchdarkly/sdk/server/BigSegmentStoreWrapper.java b/src/main/java/com/launchdarkly/sdk/server/BigSegmentStoreWrapper.java index bfd01ddac..3a6d14451 100644 --- a/src/main/java/com/launchdarkly/sdk/server/BigSegmentStoreWrapper.java +++ b/src/main/java/com/launchdarkly/sdk/server/BigSegmentStoreWrapper.java @@ -5,6 +5,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.launchdarkly.logging.LDLogger; import com.launchdarkly.sdk.EvaluationReason.BigSegmentsStatus; import com.launchdarkly.sdk.server.interfaces.BigSegmentStore; import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreStatusProvider.Status; @@ -15,7 +16,6 @@ import org.apache.commons.codec.digest.DigestUtils; import org.checkerframework.checker.nullness.qual.NonNull; -import org.slf4j.Logger; import java.io.Closeable; import java.io.IOException; @@ -27,23 +27,23 @@ import java.util.concurrent.TimeUnit; class BigSegmentStoreWrapper implements Closeable { - private static final Logger logger = Loggers.BIG_SEGMENTS; - private final BigSegmentStore store; private final Duration staleAfter; private final ScheduledFuture pollFuture; private final LoadingCache cache; private final EventBroadcasterImpl statusProvider; - + private final LDLogger logger; private final Object statusLock = new Object(); private Status lastStatus; BigSegmentStoreWrapper(BigSegmentsConfiguration config, EventBroadcasterImpl statusProvider, - ScheduledExecutorService sharedExecutor) { + ScheduledExecutorService sharedExecutor, + LDLogger logger) { this.store = config.getStore(); this.staleAfter = config.getStaleAfter(); this.statusProvider = statusProvider; + this.logger = logger; CacheLoader loader = new CacheLoader() { @Override diff --git a/src/main/java/com/launchdarkly/sdk/server/ClientContextImpl.java b/src/main/java/com/launchdarkly/sdk/server/ClientContextImpl.java index 96f9e9ea4..c3c7ded4f 100644 --- a/src/main/java/com/launchdarkly/sdk/server/ClientContextImpl.java +++ b/src/main/java/com/launchdarkly/sdk/server/ClientContextImpl.java @@ -1,6 +1,7 @@ package com.launchdarkly.sdk.server; -import com.launchdarkly.sdk.server.interfaces.ApplicationInfo; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.Logs; import com.launchdarkly.sdk.server.interfaces.BasicConfiguration; import com.launchdarkly.sdk.server.interfaces.ClientContext; import com.launchdarkly.sdk.server.interfaces.HttpConfiguration; @@ -52,11 +53,35 @@ private ClientContextImpl( ScheduledExecutorService sharedExecutor, DiagnosticAccumulator diagnosticAccumulator ) { - this.basicConfiguration = new BasicConfiguration(sdkKey, configuration.offline, configuration.threadPriority, configuration.applicationInfo, configuration.serviceEndpoints); + // There is some temporarily over-elaborate logic here because the component factory interfaces can't + // be updated to make the dependencies more sensible till the next major version. + BasicConfiguration tempBasic = new BasicConfiguration(sdkKey, configuration.offline, configuration.threadPriority, + configuration.applicationInfo, configuration.serviceEndpoints, LDLogger.none()); + this.loggingConfiguration = configuration.loggingConfigFactory.createLoggingConfiguration(tempBasic); + LDLogger baseLogger = LDLogger.withAdapter( + loggingConfiguration.getLogAdapter() == null ? Logs.none() : loggingConfiguration.getLogAdapter(), + loggingConfiguration.getBaseLoggerName() == null ? Loggers.BASE_LOGGER_NAME : + loggingConfiguration.getBaseLoggerName() + ); + + this.basicConfiguration = new BasicConfiguration( + sdkKey, + configuration.offline, + configuration.threadPriority, + configuration.applicationInfo, + configuration.serviceEndpoints, + baseLogger + ); this.httpConfiguration = configuration.httpConfigFactory.createHttpConfiguration(basicConfiguration); - this.loggingConfiguration = configuration.loggingConfigFactory.createLoggingConfiguration(basicConfiguration); - + + + if (this.httpConfiguration.getProxy() != null) { + baseLogger.info("Using proxy: {} {} authentication.", + this.httpConfiguration.getProxy(), + this.httpConfiguration.getProxyAuthentication() == null ? "without" : "with"); + } + this.sharedExecutor = sharedExecutor; if (!configuration.diagnosticOptOut && diagnosticAccumulator != null) { diff --git a/src/main/java/com/launchdarkly/sdk/server/Components.java b/src/main/java/com/launchdarkly/sdk/server/Components.java index 54bf82523..0b43e7531 100644 --- a/src/main/java/com/launchdarkly/sdk/server/Components.java +++ b/src/main/java/com/launchdarkly/sdk/server/Components.java @@ -2,6 +2,8 @@ import static com.launchdarkly.sdk.server.ComponentsImpl.NULL_EVENT_PROCESSOR_FACTORY; +import com.launchdarkly.logging.LDLogAdapter; +import com.launchdarkly.logging.Logs; import com.launchdarkly.sdk.server.ComponentsImpl.EventProcessorBuilderImpl; import com.launchdarkly.sdk.server.ComponentsImpl.HttpBasicAuthentication; import com.launchdarkly.sdk.server.ComponentsImpl.HttpConfigurationBuilderImpl; @@ -316,7 +318,7 @@ public static HttpAuthentication httpBasicAuthentication(String username, String * .build(); * * - * @return a factory object + * @return a configuration builder * @since 5.0.0 * @see LDConfig.Builder#logging(com.launchdarkly.sdk.server.interfaces.LoggingConfigurationFactory) */ @@ -324,6 +326,55 @@ public static LoggingConfigurationBuilder logging() { return new LoggingConfigurationBuilderImpl(); } + /** + * Returns a configuration builder for the SDK's logging configuration, specifying the + * implementation of logging to use. + *

+ * This is a shortcut for Components.logging().adapter(logAdapter). The + * com.launchdarkly.logging + * API defines the {@link LDLogAdapter} interface to specify where log output should be sent. By default, + * it is set to {@link com.launchdarkly.logging.LDSLF4J#adapter()}, meaning that output will be sent to + * SLF4J and controlled by the SLF4J configuration. You may use + * the {@link com.launchdarkly.logging.Logs} factory methods, or a custom implementation, to handle log + * output differently. For instance, you may specify {@link com.launchdarkly.logging.Logs#basic()} for + * simple console output, or {@link com.launchdarkly.logging.Logs#toJavaUtilLogging()} to use the + * java.util.logging framework. + *

+ * Passing this to {@link LDConfig.Builder#logging(com.launchdarkly.sdk.server.interfaces.LoggingConfigurationFactory)}, + * after setting any desired properties on the builder, applies this configuration to the SDK. + *


+   *     LDConfig config = new LDConfig.Builder()
+   *         .logging(
+   *              Components.logging(Logs.basic())
+   *         )
+   *         .build();
+   * 
+ * + * @param logAdapter the log adapter + * @return a configuration builder + * @since 5.10.0 + * @see LDConfig.Builder#logging(com.launchdarkly.sdk.server.interfaces.LoggingConfigurationFactory) + * @see LoggingConfigurationBuilder#adapter(LDLogAdapter) + */ + public static LoggingConfigurationBuilder logging(LDLogAdapter logAdapter) { + return logging().adapter(logAdapter); + } + + /** + * Returns a configuration builder that turns off SDK logging. + *

+ * Passing this to {@link LDConfig.Builder#logging(com.launchdarkly.sdk.server.interfaces.LoggingConfigurationFactory)} + * applies this configuration to the SDK. + *

+ * It is equivalent to Components.logging(com.launchdarkly.logging.Logs.none()). + * + * @return a configuration builder + * @since 5.10.0 + */ + public static LoggingConfigurationBuilder noLogging() { + return logging().adapter(Logs.none()); + } + /** * Returns a configuration builder for the SDK's application metadata. *

diff --git a/src/main/java/com/launchdarkly/sdk/server/ComponentsImpl.java b/src/main/java/com/launchdarkly/sdk/server/ComponentsImpl.java index 9eb03fa00..3e87d7103 100644 --- a/src/main/java/com/launchdarkly/sdk/server/ComponentsImpl.java +++ b/src/main/java/com/launchdarkly/sdk/server/ComponentsImpl.java @@ -1,6 +1,11 @@ package com.launchdarkly.sdk.server; import com.google.common.collect.ImmutableMap; +import com.launchdarkly.logging.LDLogAdapter; +import com.launchdarkly.logging.LDLogLevel; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LDSLF4J; +import com.launchdarkly.logging.Logs; import com.launchdarkly.sdk.LDValue; import com.launchdarkly.sdk.server.DiagnosticEvent.ConfigProperty; import com.launchdarkly.sdk.server.integrations.EventProcessorBuilder; @@ -24,6 +29,7 @@ import com.launchdarkly.sdk.server.interfaces.EventProcessor; import com.launchdarkly.sdk.server.interfaces.EventProcessorFactory; import com.launchdarkly.sdk.server.interfaces.EventSender; +import com.launchdarkly.sdk.server.interfaces.EventSenderFactory; import com.launchdarkly.sdk.server.interfaces.HttpAuthentication; import com.launchdarkly.sdk.server.interfaces.HttpConfiguration; import com.launchdarkly.sdk.server.interfaces.LoggingConfiguration; @@ -89,12 +95,13 @@ static final class NullDataSourceFactory implements DataSourceFactory, Diagnosti @Override public DataSource createDataSource(ClientContext context, DataSourceUpdates dataSourceUpdates) { + LDLogger logger = context.getBasic().getBaseLogger(); if (context.getBasic().isOffline()) { // If they have explicitly called offline(true) to disable everything, we'll log this slightly // more specific message. - Loggers.MAIN.info("Starting LaunchDarkly client in offline mode"); + logger.info("Starting LaunchDarkly client in offline mode"); } else { - Loggers.MAIN.info("LaunchDarkly client will not connect to Launchdarkly for feature flag data"); + logger.info("LaunchDarkly client will not connect to Launchdarkly for feature flag data"); } dataSourceUpdates.updateStatus(DataSourceStatusProvider.State.VALID, null); return NullDataSource.INSTANCE; @@ -137,16 +144,16 @@ static final class StreamingDataSourceBuilderImpl extends StreamingDataSourceBui implements DiagnosticDescription { @Override public DataSource createDataSource(ClientContext context, DataSourceUpdates dataSourceUpdates) { - // Note, we log startup messages under the LDClient class to keep logs more readable - - Loggers.DATA_SOURCE.info("Enabling streaming API"); + LDLogger baseLogger = context.getBasic().getBaseLogger(); + LDLogger logger = baseLogger.subLogger(Loggers.DATA_SOURCE_LOGGER_NAME); + logger.info("Enabling streaming API"); URI streamUri = StandardEndpoints.selectBaseUri( context.getBasic().getServiceEndpoints().getStreamingBaseUri(), baseURI, StandardEndpoints.DEFAULT_STREAMING_BASE_URI, "streaming", - Loggers.MAIN + baseLogger ); return new StreamProcessor( @@ -155,7 +162,8 @@ public DataSource createDataSource(ClientContext context, DataSourceUpdates data context.getBasic().getThreadPriority(), ClientContextImpl.get(context).diagnosticAccumulator, streamUri, - initialReconnectDelay + initialReconnectDelay, + logger ); } @@ -184,25 +192,27 @@ PollingDataSourceBuilderImpl pollIntervalWithNoMinimum(Duration pollInterval) { @Override public DataSource createDataSource(ClientContext context, DataSourceUpdates dataSourceUpdates) { - // Note, we log startup messages under the LDClient class to keep logs more readable + LDLogger baseLogger = context.getBasic().getBaseLogger(); + LDLogger logger = baseLogger.subLogger(Loggers.DATA_SOURCE_LOGGER_NAME); + + logger.info("Disabling streaming API"); + logger.warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support"); - Loggers.DATA_SOURCE.info("Disabling streaming API"); - Loggers.DATA_SOURCE.warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support"); - URI pollUri = StandardEndpoints.selectBaseUri( context.getBasic().getServiceEndpoints().getPollingBaseUri(), baseURI, StandardEndpoints.DEFAULT_POLLING_BASE_URI, "polling", - Loggers.MAIN + baseLogger ); - DefaultFeatureRequestor requestor = new DefaultFeatureRequestor(context.getHttp(), pollUri); + DefaultFeatureRequestor requestor = new DefaultFeatureRequestor(context.getHttp(), pollUri, logger); return new PollingProcessor( requestor, dataSourceUpdates, ClientContextImpl.get(context).sharedExecutor, - pollInterval + pollInterval, + logger ); } @@ -226,15 +236,21 @@ static final class EventProcessorBuilderImpl extends EventProcessorBuilder implements DiagnosticDescription { @Override public EventProcessor createEventProcessor(ClientContext context) { - EventSender eventSender = - (eventSenderFactory == null ? new DefaultEventSender.Factory() : eventSenderFactory) - .createEventSender(context.getBasic(), context.getHttp()); + LDLogger baseLogger = context.getBasic().getBaseLogger(); + LDLogger logger = baseLogger.subLogger(Loggers.EVENTS_LOGGER_NAME); + EventSenderFactory senderFactory = + eventSenderFactory == null ? new DefaultEventSender.Factory() : eventSenderFactory; + EventSender eventSender = senderFactory.createEventSender( + context.getBasic(), + context.getHttp(), + logger + ); URI eventsUri = StandardEndpoints.selectBaseUri( context.getBasic().getServiceEndpoints().getEventsBaseUri(), baseURI, StandardEndpoints.DEFAULT_EVENTS_BASE_URI, "events", - Loggers.MAIN + baseLogger ); return new DefaultEventProcessor( new EventsConfiguration( @@ -252,7 +268,8 @@ public EventProcessor createEventProcessor(ClientContext context) { ClientContextImpl.get(context).sharedExecutor, context.getBasic().getThreadPriority(), ClientContextImpl.get(context).diagnosticAccumulator, - ClientContextImpl.get(context).diagnosticInitEvent + ClientContextImpl.get(context).diagnosticInitEvent, + logger ); } @@ -279,12 +296,13 @@ public LDValue describeConfiguration(BasicConfiguration basicConfiguration) { static final class HttpConfigurationBuilderImpl extends HttpConfigurationBuilder { @Override public HttpConfiguration createHttpConfiguration(BasicConfiguration basicConfiguration) { + LDLogger logger = basicConfiguration.getBaseLogger(); // Build the default headers ImmutableMap.Builder headers = ImmutableMap.builder(); headers.put("Authorization", basicConfiguration.getSdkKey()); headers.put("User-Agent", "JavaClient/" + Version.SDK_VERSION); if (basicConfiguration.getApplicationInfo() != null) { - String tagHeader = Util.applicationTagHeader(basicConfiguration.getApplicationInfo()); + String tagHeader = Util.applicationTagHeader(basicConfiguration.getApplicationInfo(), logger); if (!tagHeader.isEmpty()) { headers.put("X-LaunchDarkly-Tags", tagHeader); } @@ -295,9 +313,6 @@ public HttpConfiguration createHttpConfiguration(BasicConfiguration basicConfigu } Proxy proxy = proxyHost == null ? null : new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); - if (proxy != null) { - Loggers.MAIN.info("Using proxy: {} {} authentication.", proxy, proxyAuth == null ? "without" : "with"); - } return new HttpConfigurationImpl( connectTimeout, @@ -352,7 +367,8 @@ public DataStore createDataStore(ClientContext context, DataStoreUpdates dataSto staleValuesPolicy, recordCacheStats, dataStoreUpdates, - ClientContextImpl.get(context).sharedExecutor + ClientContextImpl.get(context).sharedExecutor, + context.getBasic().getBaseLogger().subLogger(Loggers.DATA_STORE_LOGGER_NAME) ); } } @@ -360,7 +376,14 @@ public DataStore createDataStore(ClientContext context, DataStoreUpdates dataSto static final class LoggingConfigurationBuilderImpl extends LoggingConfigurationBuilder { @Override public LoggingConfiguration createLoggingConfiguration(BasicConfiguration basicConfiguration) { - return new LoggingConfigurationImpl(logDataSourceOutageAsErrorAfter); + LDLogAdapter adapter = logAdapter == null ? LDSLF4J.adapter() : logAdapter; + LDLogAdapter filteredAdapter = Logs.level(adapter, + minimumLevel == null ? LDLogLevel.INFO : minimumLevel); + // If the adapter is for a framework like SLF4J or java.util.logging that has its own external + // configuration system, then calling Logs.level here has no effect and filteredAdapter will be + // just the same as adapter. + String name = baseName == null ? Loggers.BASE_LOGGER_NAME : baseName; + return new LoggingConfigurationImpl(name, filteredAdapter, logDataSourceOutageAsErrorAfter); } } diff --git a/src/main/java/com/launchdarkly/sdk/server/DataSourceUpdatesImpl.java b/src/main/java/com/launchdarkly/sdk/server/DataSourceUpdatesImpl.java index 9d95b2708..e681e5147 100644 --- a/src/main/java/com/launchdarkly/sdk/server/DataSourceUpdatesImpl.java +++ b/src/main/java/com/launchdarkly/sdk/server/DataSourceUpdatesImpl.java @@ -3,6 +3,8 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LogValues; import com.launchdarkly.sdk.server.DataModelDependencies.KindAndKey; import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider.ErrorInfo; import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider.ErrorKind; @@ -54,6 +56,7 @@ final class DataSourceUpdatesImpl implements DataSourceUpdates { private final DataStoreStatusProvider dataStoreStatusProvider; private final OutageTracker outageTracker; private final Object stateLock = new Object(); + private final LDLogger logger; private volatile Status currentStatus; private volatile boolean lastStoreUpdateFailed = false; @@ -65,13 +68,15 @@ final class DataSourceUpdatesImpl implements DataSourceUpdates { EventBroadcasterImpl flagChangeEventNotifier, EventBroadcasterImpl dataSourceStatusNotifier, ScheduledExecutorService sharedExecutor, - Duration outageLoggingTimeout + Duration outageLoggingTimeout, + LDLogger baseLogger ) { this.store = store; this.flagChangeEventNotifier = flagChangeEventNotifier; this.dataSourceStatusNotifier = dataSourceStatusNotifier; this.dataStoreStatusProvider = dataStoreStatusProvider; this.outageTracker = new OutageTracker(sharedExecutor, outageLoggingTimeout); + this.logger = baseLogger.subLogger(Loggers.DATA_SOURCE_LOGGER_NAME); currentStatus = new Status(State.INITIALIZING, Instant.now(), null); } @@ -267,10 +272,11 @@ private Set computeChangedItemsForFullDataSet(Map inbox; private final ScheduledExecutorService scheduler; private final AtomicBoolean closed = new AtomicBoolean(false); private final List> scheduledTasks = new ArrayList<>(); private volatile boolean inputCapacityExceeded = false; + private final LDLogger logger; DefaultEventProcessor( EventsConfiguration eventsConfig, ScheduledExecutorService sharedExecutor, int threadPriority, DiagnosticAccumulator diagnosticAccumulator, - DiagnosticEvent.Init diagnosticInitEvent + DiagnosticEvent.Init diagnosticInitEvent, + LDLogger logger ) { inbox = new ArrayBlockingQueue<>(eventsConfig.capacity); scheduler = sharedExecutor; - + this.logger = logger; + dispatcher = new EventDispatcher( eventsConfig, sharedExecutor, @@ -55,7 +56,8 @@ final class DefaultEventProcessor implements EventProcessor { inbox, closed, diagnosticAccumulator, - diagnosticInitEvent + diagnosticInitEvent, + logger ); Runnable flusher = () -> { @@ -205,7 +207,8 @@ static final class EventDispatcher { @VisibleForTesting final DiagnosticAccumulator diagnosticAccumulator; private final ExecutorService sharedExecutor; private final SendDiagnosticTaskFactory sendDiagnosticTaskFactory; - + private final LDLogger logger; + private long deduplicatedUsers = 0; private EventDispatcher( @@ -215,7 +218,8 @@ private EventDispatcher( BlockingQueue inbox, AtomicBoolean closed, DiagnosticAccumulator diagnosticAccumulator, - DiagnosticEvent.Init diagnosticInitEvent + DiagnosticEvent.Init diagnosticInitEvent, + LDLogger logger ) { this.eventsConfig = eventsConfig; this.inbox = inbox; @@ -223,7 +227,8 @@ private EventDispatcher( this.sharedExecutor = sharedExecutor; this.diagnosticAccumulator = diagnosticAccumulator; this.busyFlushWorkersCount = new AtomicInteger(0); - + this.logger = logger; + ThreadFactory threadFactory = new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("LaunchDarkly-event-delivery-%d") @@ -235,7 +240,7 @@ private EventDispatcher( // all the workers are busy. final BlockingQueue payloadQueue = new ArrayBlockingQueue<>(1); - final EventBuffer outbox = new EventBuffer(eventsConfig.capacity); + final EventBuffer outbox = new EventBuffer(eventsConfig.capacity, logger); final SimpleLRUCache userKeys = new SimpleLRUCache(eventsConfig.userKeysCapacity); Thread mainThread = threadFactory.newThread(() -> { @@ -255,7 +260,8 @@ private EventDispatcher( listener, payloadQueue, busyFlushWorkersCount, - threadFactory + threadFactory, + logger ); flushWorkers.add(task); } @@ -276,7 +282,12 @@ private void onUncaughtException(Thread thread, Throwable e) { // application won't end up blocking on a queue that's no longer being consumed. // COVERAGE: there is no way to make this happen from test code. - logger.error("Event processor thread was terminated by an unrecoverable error. No more analytics events will be sent.", e); + logger.error("Event processor thread was terminated by an unrecoverable error. No more analytics events will be sent. {} {}", + LogValues.exceptionSummary(e), LogValues.exceptionTrace(e)); + // Note that this is a rare case where we always log the exception stacktrace, instead of only + // logging it at debug level. That's because an exception of this kind should never happen and, + // if it happens, may be difficult to debug. + // Flip the switch to prevent DefaultEventProcessor from putting any more messages on the queue closed.set(true); // Now discard everything that was on the queue, but also make sure no one was blocking on a message @@ -353,8 +364,8 @@ private void doShutdown() { try { eventsConfig.eventSender.close(); } catch (IOException e) { - logger.error("Unexpected error when closing event sender: {}", e.toString()); - logger.debug(e.toString(), e); + logger.error("Unexpected error when closing event sender: {}", LogValues.exceptionSummary(e)); + logger.debug(LogValues.exceptionTrace(e)); } } @@ -482,11 +493,13 @@ private static final class EventBuffer { final List events = new ArrayList<>(); final EventSummarizer summarizer = new EventSummarizer(); private final int capacity; + private final LDLogger logger; private boolean capacityExceeded = false; private long droppedEventCount = 0; - EventBuffer(int capacity) { + EventBuffer(int capacity, LDLogger logger) { this.capacity = capacity; + this.logger = logger; } void add(Event e) { @@ -550,13 +563,15 @@ private static final class SendEventsTask implements Runnable { private final AtomicBoolean stopping; private final EventOutputFormatter formatter; private final Thread thread; + private final LDLogger logger; SendEventsTask( EventsConfiguration eventsConfig, EventResponseListener responseListener, BlockingQueue payloadQueue, AtomicInteger activeFlushWorkersCount, - ThreadFactory threadFactory + ThreadFactory threadFactory, + LDLogger logger ) { this.eventsConfig = eventsConfig; this.formatter = new EventOutputFormatter(eventsConfig); @@ -564,6 +579,7 @@ private static final class SendEventsTask implements Runnable { this.payloadQueue = payloadQueue; this.activeFlushWorkersCount = activeFlushWorkersCount; this.stopping = new AtomicBoolean(false); + this.logger = logger; thread = threadFactory.newThread(this); thread.setDaemon(true); thread.start(); @@ -588,8 +604,8 @@ public void run() { ); responseListener.handleResponse(result); } catch (Exception e) { - logger.error("Unexpected error in event processor: {}", e.toString()); - logger.debug(e.toString(), e); + logger.error("Unexpected error in event processor: {}", LogValues.exceptionSummary(e)); + logger.debug(LogValues.exceptionTrace(e)); } synchronized (activeFlushWorkersCount) { activeFlushWorkersCount.decrementAndGet(); diff --git a/src/main/java/com/launchdarkly/sdk/server/DefaultEventSender.java b/src/main/java/com/launchdarkly/sdk/server/DefaultEventSender.java index c3b03cc54..7c2e4569e 100644 --- a/src/main/java/com/launchdarkly/sdk/server/DefaultEventSender.java +++ b/src/main/java/com/launchdarkly/sdk/server/DefaultEventSender.java @@ -1,12 +1,11 @@ package com.launchdarkly.sdk.server; +import com.launchdarkly.logging.LDLogger; import com.launchdarkly.sdk.server.interfaces.BasicConfiguration; import com.launchdarkly.sdk.server.interfaces.EventSender; import com.launchdarkly.sdk.server.interfaces.EventSenderFactory; import com.launchdarkly.sdk.server.interfaces.HttpConfiguration; -import org.slf4j.Logger; - import java.io.IOException; import java.net.URI; import java.text.ParseException; @@ -32,8 +31,6 @@ import okhttp3.Response; final class DefaultEventSender implements EventSender { - private static final Logger logger = Loggers.EVENTS; - static final Duration DEFAULT_RETRY_DELAY = Duration.ofSeconds(1); private static final String EVENT_SCHEMA_HEADER = "X-LaunchDarkly-Event-Schema"; private static final String EVENT_SCHEMA_VERSION = "3"; @@ -46,14 +43,17 @@ final class DefaultEventSender implements EventSender { private final OkHttpClient httpClient; private final Headers baseHeaders; final Duration retryDelay; // visible for testing - + private final LDLogger logger; + DefaultEventSender( HttpConfiguration httpConfiguration, - Duration retryDelay + Duration retryDelay, + LDLogger logger ) { OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder(); configureHttpClientBuilder(httpConfiguration, httpBuilder); this.httpClient = httpBuilder.build(); + this.logger = logger; this.baseHeaders = getHeadersBuilderFor(httpConfiguration) .add("Content-Type", "application/json") @@ -148,7 +148,7 @@ public Result sendEventData(EventDataKind kind, String data, int eventCount, URI return new Result(false, mustShutDown, null); } - private static final Date parseResponseDate(Response response) { + private final Date parseResponseDate(Response response) { String dateStr = response.header("Date"); if (dateStr != null) { try { @@ -166,7 +166,16 @@ private static final Date parseResponseDate(Response response) { static final class Factory implements EventSenderFactory { @Override public EventSender createEventSender(BasicConfiguration basicConfiguration, HttpConfiguration httpConfiguration) { - return new DefaultEventSender(httpConfiguration, DefaultEventSender.DEFAULT_RETRY_DELAY); + return new DefaultEventSender(httpConfiguration, DefaultEventSender.DEFAULT_RETRY_DELAY, + LDLogger.none()); + } + + @Override + public EventSender createEventSender( + BasicConfiguration basicConfiguration, + HttpConfiguration httpConfiguration, + LDLogger logger) { + return new DefaultEventSender(httpConfiguration, DefaultEventSender.DEFAULT_RETRY_DELAY, logger); } } } diff --git a/src/main/java/com/launchdarkly/sdk/server/DefaultFeatureRequestor.java b/src/main/java/com/launchdarkly/sdk/server/DefaultFeatureRequestor.java index d97bc2fb7..7fb7f1223 100644 --- a/src/main/java/com/launchdarkly/sdk/server/DefaultFeatureRequestor.java +++ b/src/main/java/com/launchdarkly/sdk/server/DefaultFeatureRequestor.java @@ -2,13 +2,12 @@ import com.google.common.annotations.VisibleForTesting; import com.google.gson.stream.JsonReader; +import com.launchdarkly.logging.LDLogger; import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.FullDataSet; import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.ItemDescriptor; import com.launchdarkly.sdk.server.interfaces.HttpConfiguration; import com.launchdarkly.sdk.server.interfaces.SerializationException; -import org.slf4j.Logger; - import java.io.IOException; import java.net.URI; import java.nio.file.Files; @@ -30,7 +29,6 @@ * Implementation of getting flag data via a polling request. */ final class DefaultFeatureRequestor implements FeatureRequestor { - private static final Logger logger = Loggers.DATA_SOURCE; private static final long MAX_HTTP_CACHE_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB @VisibleForTesting final URI baseUri; @@ -38,10 +36,12 @@ final class DefaultFeatureRequestor implements FeatureRequestor { private final URI pollingUri; private final Headers headers; private final Path cacheDir; + private final LDLogger logger; - DefaultFeatureRequestor(HttpConfiguration httpConfig, URI baseUri) { + DefaultFeatureRequestor(HttpConfiguration httpConfig, URI baseUri, LDLogger logger) { this.baseUri = baseUri; this.pollingUri = concatenateUriPath(baseUri, StandardEndpoints.POLLING_REQUEST_PATH); + this.logger = logger; OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder(); configureHttpClientBuilder(httpConfig, httpBuilder); @@ -77,7 +77,8 @@ public FullDataSet getAllData(boolean returnDataEvenIfCached) boolean wasCached = response.networkResponse() == null || response.networkResponse().code() == 304; if (wasCached && !returnDataEvenIfCached) { logger.debug("Get flag(s) got cached response, will not parse"); - logger.debug("Cache hit count: " + httpClient.cache().hitCount() + " Cache network Count: " + httpClient.cache().networkCount()); + logger.debug("Cache hit count: {} Cache network count: {} ", + httpClient.cache().hitCount(), httpClient.cache().networkCount()); return null; } diff --git a/src/main/java/com/launchdarkly/sdk/server/Evaluator.java b/src/main/java/com/launchdarkly/sdk/server/Evaluator.java index 3459ab23b..3c410f5a2 100644 --- a/src/main/java/com/launchdarkly/sdk/server/Evaluator.java +++ b/src/main/java/com/launchdarkly/sdk/server/Evaluator.java @@ -1,5 +1,6 @@ package com.launchdarkly.sdk.server; +import com.launchdarkly.logging.LDLogger; import com.launchdarkly.sdk.EvaluationReason; import com.launchdarkly.sdk.EvaluationReason.Kind; import com.launchdarkly.sdk.LDUser; @@ -19,8 +20,6 @@ import com.launchdarkly.sdk.server.DataModelPreprocessing.ClausePreprocessed; import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreTypes; -import org.slf4j.Logger; - import java.util.List; import java.util.Set; @@ -57,8 +56,6 @@ class Evaluator { // variables captured in the closure each time they are used. // - private final static Logger logger = Loggers.EVALUATION; - /** * This key cannot exist in LaunchDarkly because it contains invalid characters. We use it in tests as a way to * simulate an unexpected RuntimeException during flag evaluations. We check for it by reference equality, so @@ -68,6 +65,7 @@ class Evaluator { static final RuntimeException EXPECTED_EXCEPTION_FROM_INVALID_FLAG = new RuntimeException("deliberate test error"); private final Getters getters; + private final LDLogger logger; /** * An abstraction of getting flags or segments by key. This ensures that Evaluator cannot modify the data store, @@ -101,8 +99,9 @@ private static class EvaluatorState { private EvaluationReason.BigSegmentsStatus bigSegmentsStatus = null; } - Evaluator(Getters getters) { + Evaluator(Getters getters, LDLogger logger) { this.getters = getters; + this.logger = logger; } /** @@ -203,7 +202,7 @@ private EvalResult checkPrerequisites(FeatureFlag flag, LDUser user, return null; } - private static EvalResult getValueForVariationOrRollout( + private EvalResult getValueForVariationOrRollout( FeatureFlag flag, VariationOrRollout vr, LDUser user, @@ -428,7 +427,7 @@ private boolean segmentRuleMatchesUser(SegmentRule segmentRule, LDUser user, Str return bucket < weight; } - private static EvalResult computeRuleMatch(FeatureFlag flag, LDUser user, Rule rule, int ruleIndex) { + private EvalResult computeRuleMatch(FeatureFlag flag, LDUser user, Rule rule, int ruleIndex) { if (rule.preprocessed != null) { return getValueForVariationOrRollout(flag, rule, user, rule.preprocessed.allPossibleResults, null); } diff --git a/src/main/java/com/launchdarkly/sdk/server/EventBroadcasterImpl.java b/src/main/java/com/launchdarkly/sdk/server/EventBroadcasterImpl.java index 786afa5c0..c76463f18 100644 --- a/src/main/java/com/launchdarkly/sdk/server/EventBroadcasterImpl.java +++ b/src/main/java/com/launchdarkly/sdk/server/EventBroadcasterImpl.java @@ -1,5 +1,7 @@ package com.launchdarkly.sdk.server; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LogValues; import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreStatusProvider; import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider; import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider; @@ -23,7 +25,8 @@ class EventBroadcasterImpl { private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>(); private final BiConsumer broadcastAction; private final ExecutorService executor; - + private final LDLogger logger; + /** * Creates an instance. * @@ -31,28 +34,37 @@ class EventBroadcasterImpl { * @param executor the executor to use for running notification tasks on a worker thread; if this * is null (which should only be the case in test code) then broadcasting an event will be a no-op */ - EventBroadcasterImpl(BiConsumer broadcastAction, ExecutorService executor) { + EventBroadcasterImpl( + BiConsumer broadcastAction, + ExecutorService executor, + LDLogger logger + ) { this.broadcastAction = broadcastAction; this.executor = executor; + this.logger = logger; } - static EventBroadcasterImpl forFlagChangeEvents(ExecutorService executor) { - return new EventBroadcasterImpl<>(FlagChangeListener::onFlagChange, executor); + static EventBroadcasterImpl forFlagChangeEvents( + ExecutorService executor, LDLogger logger) { + return new EventBroadcasterImpl<>(FlagChangeListener::onFlagChange, executor, logger); } static EventBroadcasterImpl - forDataSourceStatus(ExecutorService executor) { - return new EventBroadcasterImpl<>(DataSourceStatusProvider.StatusListener::dataSourceStatusChanged, executor); + forDataSourceStatus(ExecutorService executor, LDLogger logger) { + return new EventBroadcasterImpl<>(DataSourceStatusProvider.StatusListener::dataSourceStatusChanged, + executor, logger); } static EventBroadcasterImpl - forDataStoreStatus(ExecutorService executor) { - return new EventBroadcasterImpl<>(DataStoreStatusProvider.StatusListener::dataStoreStatusChanged, executor); + forDataStoreStatus(ExecutorService executor, LDLogger logger) { + return new EventBroadcasterImpl<>(DataStoreStatusProvider.StatusListener::dataStoreStatusChanged, + executor, logger); } static EventBroadcasterImpl - forBigSegmentStoreStatus(ExecutorService executor) { - return new EventBroadcasterImpl<>(BigSegmentStoreStatusProvider.StatusListener::bigSegmentStoreStatusChanged, executor); + forBigSegmentStoreStatus(ExecutorService executor, LDLogger logger) { + return new EventBroadcasterImpl<>(BigSegmentStoreStatusProvider.StatusListener::bigSegmentStoreStatusChanged, + executor, logger); } /** @@ -96,8 +108,8 @@ void broadcast(EventT event) { try { broadcastAction.accept(l, event); } catch (Exception e) { - Loggers.MAIN.warn("Unexpected error from listener ({}): {}", l.getClass(), e.toString()); - Loggers.MAIN.debug(e.toString(), e); + logger.warn("Unexpected error from listener ({}): {}", l.getClass(), LogValues.exceptionSummary(e)); + logger.debug("{}", LogValues.exceptionTrace(e)); } }); } diff --git a/src/main/java/com/launchdarkly/sdk/server/LDClient.java b/src/main/java/com/launchdarkly/sdk/server/LDClient.java index 48a3aa49b..db7a2bba0 100644 --- a/src/main/java/com/launchdarkly/sdk/server/LDClient.java +++ b/src/main/java/com/launchdarkly/sdk/server/LDClient.java @@ -1,12 +1,8 @@ package com.launchdarkly.sdk.server; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.launchdarkly.sdk.EvaluationDetail.NO_VARIATION; -import static com.launchdarkly.sdk.server.DataModel.FEATURES; -import static com.launchdarkly.sdk.server.DataModel.SEGMENTS; -import static com.launchdarkly.sdk.server.Util.isAsciiHeaderValue; - import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LogValues; import com.launchdarkly.sdk.EvaluationDetail; import com.launchdarkly.sdk.EvaluationReason; import com.launchdarkly.sdk.LDUser; @@ -47,6 +43,12 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.launchdarkly.sdk.EvaluationDetail.NO_VARIATION; +import static com.launchdarkly.sdk.server.DataModel.FEATURES; +import static com.launchdarkly.sdk.server.DataModel.SEGMENTS; +import static com.launchdarkly.sdk.server.Util.isAsciiHeaderValue; + /** * A client for the LaunchDarkly API. Client instances are thread-safe. Applications should instantiate * a single {@code LDClient} for the lifetime of their application. @@ -70,6 +72,8 @@ public final class LDClient implements LDClientInterface { private final ScheduledExecutorService sharedExecutor; private final EventFactory eventFactoryDefault; private final EventFactory eventFactoryWithReasons; + private final LDLogger baseLogger; + private final LDLogger evaluationLogger; private final Evaluator.PrerequisiteEvaluationSink prereqEvalsDefault; private final Evaluator.PrerequisiteEvaluationSink prereqEvalsWithReasons; @@ -200,21 +204,24 @@ public LDClient(String sdkKey, LDConfig config) { sharedExecutor, useDiagnostics ? new DiagnosticAccumulator(new DiagnosticId(sdkKey)) : null ); - + this.baseLogger = context.getBasic().getBaseLogger(); + this.evaluationLogger = this.baseLogger.subLogger(Loggers.EVALUATION_LOGGER_NAME); + this.eventProcessor = config.eventProcessorFactory.createEventProcessor(context); EventBroadcasterImpl bigSegmentStoreStatusNotifier = - EventBroadcasterImpl.forBigSegmentStoreStatus(sharedExecutor); + EventBroadcasterImpl.forBigSegmentStoreStatus(sharedExecutor, baseLogger); BigSegmentsConfiguration bigSegmentsConfig = config.bigSegmentsConfigBuilder.createBigSegmentsConfiguration(context); if (bigSegmentsConfig.getStore() != null) { - bigSegmentStoreWrapper = new BigSegmentStoreWrapper(bigSegmentsConfig, bigSegmentStoreStatusNotifier, sharedExecutor); + bigSegmentStoreWrapper = new BigSegmentStoreWrapper(bigSegmentsConfig, bigSegmentStoreStatusNotifier, sharedExecutor, + this.baseLogger.subLogger(Loggers.BIG_SEGMENTS_LOGGER_NAME)); } else { bigSegmentStoreWrapper = null; } bigSegmentStoreStatusProvider = new BigSegmentStoreStatusProviderImpl(bigSegmentStoreStatusNotifier, bigSegmentStoreWrapper); EventBroadcasterImpl dataStoreStatusNotifier = - EventBroadcasterImpl.forDataStoreStatus(sharedExecutor); + EventBroadcasterImpl.forDataStoreStatus(sharedExecutor, baseLogger); DataStoreUpdatesImpl dataStoreUpdates = new DataStoreUpdatesImpl(dataStoreStatusNotifier); this.dataStore = config.dataStoreFactory.createDataStore(context, dataStoreUpdates); @@ -231,23 +238,24 @@ public BigSegmentStoreWrapper.BigSegmentsQueryResult getBigSegments(String key) BigSegmentStoreWrapper wrapper = LDClient.this.bigSegmentStoreWrapper; return wrapper == null ? null : wrapper.getUserMembership(key); } - }); + }, evaluationLogger); - this.flagChangeBroadcaster = EventBroadcasterImpl.forFlagChangeEvents(sharedExecutor); + this.flagChangeBroadcaster = EventBroadcasterImpl.forFlagChangeEvents(sharedExecutor, baseLogger); this.flagTracker = new FlagTrackerImpl(flagChangeBroadcaster, (key, user) -> jsonValueVariation(key, user, LDValue.ofNull())); this.dataStoreStatusProvider = new DataStoreStatusProviderImpl(this.dataStore, dataStoreUpdates); EventBroadcasterImpl dataSourceStatusNotifier = - EventBroadcasterImpl.forDataSourceStatus(sharedExecutor); + EventBroadcasterImpl.forDataSourceStatus(sharedExecutor, baseLogger); DataSourceUpdatesImpl dataSourceUpdates = new DataSourceUpdatesImpl( dataStore, dataStoreStatusProvider, flagChangeBroadcaster, dataSourceStatusNotifier, sharedExecutor, - context.getLogging().getLogDataSourceOutageAsErrorAfter() + context.getLogging().getLogDataSourceOutageAsErrorAfter(), + baseLogger ); this.dataSourceUpdates = dataSourceUpdates; this.dataSource = config.dataSourceFactory.createDataSource(context, dataSourceUpdates); @@ -261,18 +269,20 @@ public BigSegmentStoreWrapper.BigSegmentsQueryResult getBigSegments(String key) Future startFuture = dataSource.start(); if (!config.startWait.isZero() && !config.startWait.isNegative()) { if (!(dataSource instanceof ComponentsImpl.NullDataSource)) { - Loggers.MAIN.info("Waiting up to " + config.startWait.toMillis() + " milliseconds for LaunchDarkly client to start..."); + baseLogger.info("Waiting up to {} milliseconds for LaunchDarkly client to start...", + config.startWait.toMillis()); } try { startFuture.get(config.startWait.toMillis(), TimeUnit.MILLISECONDS); } catch (TimeoutException e) { - Loggers.MAIN.error("Timeout encountered waiting for LaunchDarkly client initialization"); + baseLogger.error("Timeout encountered waiting for LaunchDarkly client initialization"); } catch (Exception e) { - Loggers.MAIN.error("Exception encountered waiting for LaunchDarkly client initialization: {}", e.toString()); - Loggers.MAIN.debug(e.toString(), e); + baseLogger.error("Exception encountered waiting for LaunchDarkly client initialization: {}", + LogValues.exceptionSummary(e)); + baseLogger.debug("{}", LogValues.exceptionTrace(e)); } if (!dataSource.isInitialized()) { - Loggers.MAIN.warn("LaunchDarkly client was not successfully initialized"); + baseLogger.warn("LaunchDarkly client was not successfully initialized"); } } } @@ -290,7 +300,7 @@ public void track(String eventName, LDUser user) { @Override public void trackData(String eventName, LDUser user, LDValue data) { if (user == null || user.getKey() == null || user.getKey().isEmpty()) { - Loggers.MAIN.warn("Track called with null user or null/empty user key!"); + baseLogger.warn("Track called with null user or null/empty user key!"); } else { eventProcessor.sendEvent(eventFactoryDefault.newCustomEvent(eventName, user, data, null)); } @@ -299,7 +309,7 @@ public void trackData(String eventName, LDUser user, LDValue data) { @Override public void trackMetric(String eventName, LDUser user, LDValue data, double metricValue) { if (user == null || user.getKey() == null || user.getKey().isEmpty()) { - Loggers.MAIN.warn("Track called with null user or null/empty user key!"); + baseLogger.warn("Track called with null user or null/empty user key!"); } else { eventProcessor.sendEvent(eventFactoryDefault.newCustomEvent(eventName, user, data, metricValue)); } @@ -308,7 +318,7 @@ public void trackMetric(String eventName, LDUser user, LDValue data, double metr @Override public void identify(LDUser user) { if (user == null || user.getKey() == null || user.getKey().isEmpty()) { - Loggers.MAIN.warn("Identify called with null user or null/empty user key!"); + baseLogger.warn("Identify called with null user or null/empty user key!"); } else { eventProcessor.sendEvent(eventFactoryDefault.newIdentifyEvent(user)); } @@ -325,20 +335,20 @@ public FeatureFlagsState allFlagsState(LDUser user, FlagsStateOption... options) FeatureFlagsState.Builder builder = FeatureFlagsState.builder(options); if (isOffline()) { - Loggers.EVALUATION.debug("allFlagsState() was called when client is in offline mode."); + evaluationLogger.debug("allFlagsState() was called when client is in offline mode."); } if (!isInitialized()) { if (dataStore.isInitialized()) { - Loggers.EVALUATION.warn("allFlagsState() was called before client initialized; using last known values from data store"); + evaluationLogger.warn("allFlagsState() was called before client initialized; using last known values from data store"); } else { - Loggers.EVALUATION.warn("allFlagsState() was called before client initialized; data store unavailable, returning no data"); + evaluationLogger.warn("allFlagsState() was called before client initialized; data store unavailable, returning no data"); return builder.valid(false).build(); } } if (user == null || user.getKey() == null) { - Loggers.EVALUATION.warn("allFlagsState() was called with null user or null user key! returning no data"); + evaluationLogger.warn("allFlagsState() was called with null user or null user key! returning no data"); return builder.valid(false).build(); } @@ -347,8 +357,8 @@ public FeatureFlagsState allFlagsState(LDUser user, FlagsStateOption... options) try { flags = dataStore.getAll(FEATURES); } catch (Exception e) { - Loggers.EVALUATION.error("Exception from data store when evaluating all flags: {}", e.toString()); - Loggers.EVALUATION.debug(e.toString(), e); + evaluationLogger.error("Exception from data store when evaluating all flags: {}", LogValues.exceptionSummary(e)); + evaluationLogger.debug(e.toString(), LogValues.exceptionTrace(e)); return builder.valid(false).build(); } @@ -367,8 +377,9 @@ public FeatureFlagsState allFlagsState(LDUser user, FlagsStateOption... options) // events either. builder.addFlag(flag, result); } catch (Exception e) { - Loggers.EVALUATION.error("Exception caught for feature flag \"{}\" when evaluating all flags: {}", entry.getKey(), e.toString()); - Loggers.EVALUATION.debug(e.toString(), e); + evaluationLogger.error("Exception caught for feature flag \"{}\" when evaluating all flags: {}", entry.getKey(), + LogValues.exceptionSummary(e)); + evaluationLogger.debug(e.toString(), LogValues.exceptionTrace(e)); builder.addFlag(flag, EvalResult.of(LDValue.ofNull(), NO_VARIATION, EvaluationReason.exception(e))); } } @@ -439,9 +450,9 @@ public EvaluationDetail jsonValueVariationDetail(String featureKey, LDU public boolean isFlagKnown(String featureKey) { if (!isInitialized()) { if (dataStore.isInitialized()) { - Loggers.MAIN.warn("isFlagKnown called before client initialized for feature flag \"{}\"; using last known values from data store", featureKey); + baseLogger.warn("isFlagKnown called before client initialized for feature flag \"{}\"; using last known values from data store", featureKey); } else { - Loggers.MAIN.warn("isFlagKnown called before client initialized for feature flag \"{}\"; data store unavailable, returning false", featureKey); + baseLogger.warn("isFlagKnown called before client initialized for feature flag \"{}\"; data store unavailable, returning false", featureKey); return false; } } @@ -451,8 +462,9 @@ public boolean isFlagKnown(String featureKey) { return true; } } catch (Exception e) { - Loggers.MAIN.error("Encountered exception while calling isFlagKnown for feature flag \"{}\": {}", featureKey, e.toString()); - Loggers.MAIN.debug(e.toString(), e); + baseLogger.error("Encountered exception while calling isFlagKnown for feature flag \"{}\": {}", featureKey, + LogValues.exceptionSummary(e)); + baseLogger.debug("{}", LogValues.exceptionTrace(e)); } return false; @@ -471,9 +483,9 @@ private EvalResult evaluateInternal(String featureKey, LDUser user, LDValue defa EventFactory eventFactory = withDetail ? eventFactoryWithReasons : eventFactoryDefault; if (!isInitialized()) { if (dataStore.isInitialized()) { - Loggers.EVALUATION.warn("Evaluation called before client initialized for feature flag \"{}\"; using last known values from data store", featureKey); + evaluationLogger.warn("Evaluation called before client initialized for feature flag \"{}\"; using last known values from data store", featureKey); } else { - Loggers.EVALUATION.warn("Evaluation called before client initialized for feature flag \"{}\"; data store unavailable, returning default value", featureKey); + evaluationLogger.warn("Evaluation called before client initialized for feature flag \"{}\"; data store unavailable, returning default value", featureKey); sendFlagRequestEvent(eventFactory.newUnknownFeatureRequestEvent(featureKey, user, defaultValue, EvaluationReason.ErrorKind.CLIENT_NOT_READY)); return errorResult(EvaluationReason.ErrorKind.CLIENT_NOT_READY, defaultValue); @@ -484,19 +496,19 @@ private EvalResult evaluateInternal(String featureKey, LDUser user, LDValue defa try { featureFlag = getFlag(dataStore, featureKey); if (featureFlag == null) { - Loggers.EVALUATION.info("Unknown feature flag \"{}\"; returning default value", featureKey); + evaluationLogger.info("Unknown feature flag \"{}\"; returning default value", featureKey); sendFlagRequestEvent(eventFactory.newUnknownFeatureRequestEvent(featureKey, user, defaultValue, EvaluationReason.ErrorKind.FLAG_NOT_FOUND)); return errorResult(EvaluationReason.ErrorKind.FLAG_NOT_FOUND, defaultValue); } if (user == null || user.getKey() == null) { - Loggers.EVALUATION.warn("Null user or null user key when evaluating flag \"{}\"; returning default value", featureKey); + evaluationLogger.warn("Null user or null user key when evaluating flag \"{}\"; returning default value", featureKey); sendFlagRequestEvent(eventFactory.newDefaultFeatureRequestEvent(featureFlag, user, defaultValue, EvaluationReason.ErrorKind.USER_NOT_SPECIFIED)); return errorResult(EvaluationReason.ErrorKind.USER_NOT_SPECIFIED, defaultValue); } if (user.getKey().isEmpty()) { - Loggers.EVALUATION.warn("User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly"); + evaluationLogger.warn("User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly"); } EvalResult evalResult = evaluator.evaluate(featureFlag, user, withDetail ? prereqEvalsWithReasons : prereqEvalsDefault); @@ -507,7 +519,7 @@ private EvalResult evaluateInternal(String featureKey, LDUser user, LDValue defa if (requireType != null && !value.isNull() && value.getType() != requireType) { - Loggers.EVALUATION.error("Feature flag evaluation expected result as {}, but got {}", defaultValue.getType(), value.getType()); + evaluationLogger.error("Feature flag evaluation expected result as {}, but got {}", defaultValue.getType(), value.getType()); sendFlagRequestEvent(eventFactory.newUnknownFeatureRequestEvent(featureKey, user, defaultValue, EvaluationReason.ErrorKind.WRONG_TYPE)); return errorResult(EvaluationReason.ErrorKind.WRONG_TYPE, defaultValue); @@ -516,8 +528,9 @@ private EvalResult evaluateInternal(String featureKey, LDUser user, LDValue defa sendFlagRequestEvent(eventFactory.newFeatureRequestEvent(featureFlag, user, evalResult, defaultValue)); return evalResult; } catch (Exception e) { - Loggers.EVALUATION.error("Encountered exception while evaluating feature flag \"{}\": {}", featureKey, e.toString()); - Loggers.EVALUATION.debug(e.toString(), e); + evaluationLogger.error("Encountered exception while evaluating feature flag \"{}\": {}", featureKey, + LogValues.exceptionSummary(e)); + evaluationLogger.debug("{}", LogValues.exceptionTrace(e)); if (featureFlag == null) { sendFlagRequestEvent(eventFactory.newUnknownFeatureRequestEvent(featureKey, user, defaultValue, EvaluationReason.ErrorKind.EXCEPTION)); @@ -551,7 +564,7 @@ public DataSourceStatusProvider getDataSourceStatusProvider() { @Override public void close() throws IOException { - Loggers.MAIN.info("Closing LaunchDarkly Client"); + baseLogger.info("Closing LaunchDarkly Client"); this.dataStore.close(); this.eventProcessor.close(); this.dataSource.close(); @@ -583,8 +596,8 @@ public String secureModeHash(LDUser user) { return Hex.encodeHexString(mac.doFinal(user.getKey().getBytes("UTF8"))); } catch (InvalidKeyException | UnsupportedEncodingException | NoSuchAlgorithmException e) { // COVERAGE: there is no way to cause these errors in a unit test. - Loggers.MAIN.error("Could not generate secure mode hash: {}", e.toString()); - Loggers.MAIN.debug(e.toString(), e); + baseLogger.error("Could not generate secure mode hash: {}", LogValues.exceptionSummary(e)); + baseLogger.debug("{}", LogValues.exceptionTrace(e)); } return null; } diff --git a/src/main/java/com/launchdarkly/sdk/server/LDConfig.java b/src/main/java/com/launchdarkly/sdk/server/LDConfig.java index 0a7fc32fa..aa26e93fb 100644 --- a/src/main/java/com/launchdarkly/sdk/server/LDConfig.java +++ b/src/main/java/com/launchdarkly/sdk/server/LDConfig.java @@ -248,7 +248,7 @@ public Builder logging(LoggingConfigurationFactory factory) { this.loggingConfigFactory = factory; return this; } - + /** * Set whether this client is offline. *

diff --git a/src/main/java/com/launchdarkly/sdk/server/Loggers.java b/src/main/java/com/launchdarkly/sdk/server/Loggers.java index abcd9bcb3..0881f0d43 100644 --- a/src/main/java/com/launchdarkly/sdk/server/Loggers.java +++ b/src/main/java/com/launchdarkly/sdk/server/Loggers.java @@ -1,8 +1,5 @@ package com.launchdarkly.sdk.server; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Static logger instances to be shared by implementation code in the main {@code com.launchdarkly.sdk.server} * package. @@ -21,39 +18,9 @@ abstract class Loggers { private Loggers() {} static final String BASE_LOGGER_NAME = LDClient.class.getName(); - static final String BIG_SEGMENTS_LOGGER_NAME = BASE_LOGGER_NAME + ".BigSegments"; - static final String DATA_SOURCE_LOGGER_NAME = BASE_LOGGER_NAME + ".DataSource"; - static final String DATA_STORE_LOGGER_NAME = BASE_LOGGER_NAME + ".DataStore"; - static final String EVALUATION_LOGGER_NAME = BASE_LOGGER_NAME + ".Evaluation"; - static final String EVENTS_LOGGER_NAME = BASE_LOGGER_NAME + ".Events"; - - /** - * The default logger instance to use for SDK messages: "com.launchdarkly.sdk.server.LDClient" - */ - static final Logger MAIN = LoggerFactory.getLogger(BASE_LOGGER_NAME); - - /** - * The logger instance to use for messages related to the Big Segments implementation: "com.launchdarkly.sdk.server.LDClient.BigSegments" - */ - static final Logger BIG_SEGMENTS = LoggerFactory.getLogger(BIG_SEGMENTS_LOGGER_NAME); - - /** - * The logger instance to use for messages related to polling, streaming, etc.: "com.launchdarkly.sdk.server.LDClient.DataSource" - */ - static final Logger DATA_SOURCE = LoggerFactory.getLogger(DATA_SOURCE_LOGGER_NAME); - - /** - * The logger instance to use for messages related to data store problems: "com.launchdarkly.sdk.server.LDClient.DataStore" - */ - static final Logger DATA_STORE = LoggerFactory.getLogger(DATA_STORE_LOGGER_NAME); - - /** - * The logger instance to use for messages related to flag evaluation: "com.launchdarkly.sdk.server.LDClient.Evaluation" - */ - static final Logger EVALUATION = LoggerFactory.getLogger(EVALUATION_LOGGER_NAME); - - /** - * The logger instance to use for messages from the event processor: "com.launchdarkly.sdk.server.LDClient.Events" - */ - static final Logger EVENTS = LoggerFactory.getLogger(EVENTS_LOGGER_NAME); + static final String BIG_SEGMENTS_LOGGER_NAME = "BigSegments"; + static final String DATA_SOURCE_LOGGER_NAME = "DataSource"; + static final String DATA_STORE_LOGGER_NAME = "DataStore"; + static final String EVALUATION_LOGGER_NAME = "Evaluation"; + static final String EVENTS_LOGGER_NAME = "Events"; } diff --git a/src/main/java/com/launchdarkly/sdk/server/LoggingConfigurationImpl.java b/src/main/java/com/launchdarkly/sdk/server/LoggingConfigurationImpl.java index f3ce2c872..81b7f1904 100644 --- a/src/main/java/com/launchdarkly/sdk/server/LoggingConfigurationImpl.java +++ b/src/main/java/com/launchdarkly/sdk/server/LoggingConfigurationImpl.java @@ -1,15 +1,34 @@ package com.launchdarkly.sdk.server; +import com.launchdarkly.logging.LDLogAdapter; import com.launchdarkly.sdk.server.interfaces.LoggingConfiguration; import java.time.Duration; final class LoggingConfigurationImpl implements LoggingConfiguration { + private final String baseLoggerName; + private final LDLogAdapter logAdapter; private final Duration logDataSourceOutageAsErrorAfter; - LoggingConfigurationImpl(Duration logDataSourceOutageAsErrorAfter) { + LoggingConfigurationImpl( + String baseLoggerName, + LDLogAdapter logAdapter, + Duration logDataSourceOutageAsErrorAfter + ) { + this.baseLoggerName = baseLoggerName; + this.logAdapter = logAdapter; this.logDataSourceOutageAsErrorAfter = logDataSourceOutageAsErrorAfter; } + + @Override + public String getBaseLoggerName() { + return baseLoggerName; + } + + @Override + public LDLogAdapter getLogAdapter() { + return logAdapter; + } @Override public Duration getLogDataSourceOutageAsErrorAfter() { diff --git a/src/main/java/com/launchdarkly/sdk/server/PersistentDataStoreStatusManager.java b/src/main/java/com/launchdarkly/sdk/server/PersistentDataStoreStatusManager.java index 211f57468..2d8d4cc0a 100644 --- a/src/main/java/com/launchdarkly/sdk/server/PersistentDataStoreStatusManager.java +++ b/src/main/java/com/launchdarkly/sdk/server/PersistentDataStoreStatusManager.java @@ -1,10 +1,10 @@ package com.launchdarkly.sdk.server; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LogValues; import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider; import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider.Status; -import org.slf4j.Logger; - import java.io.Closeable; import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; @@ -19,13 +19,13 @@ * clarity and also lets us reuse this logic in tests. */ final class PersistentDataStoreStatusManager implements Closeable { - private static final Logger logger = Loggers.DATA_STORE; static final int POLL_INTERVAL_MS = 500; // visible for testing private final Consumer statusUpdater; private final ScheduledExecutorService scheduler; private final Callable statusPollFn; private final boolean refreshOnRecovery; + private final LDLogger logger; private volatile boolean lastAvailable; private volatile ScheduledFuture pollerFuture; @@ -34,13 +34,15 @@ final class PersistentDataStoreStatusManager implements Closeable { boolean availableNow, Callable statusPollFn, Consumer statusUpdater, - ScheduledExecutorService sharedExecutor + ScheduledExecutorService sharedExecutor, + LDLogger logger ) { this.refreshOnRecovery = refreshOnRecovery; this.lastAvailable = availableNow; this.statusPollFn = statusPollFn; this.statusUpdater = statusUpdater; this.scheduler = sharedExecutor; + this.logger = logger; } public void close() { @@ -88,8 +90,8 @@ public void run() { updateAvailability(true); } } catch (Exception e) { - logger.error("Unexpected error from data store status function: {}", e.toString()); - logger.debug(e.toString(), e); + logger.error("Unexpected error from data store status function: {}", LogValues.exceptionSummary(e)); + logger.debug(LogValues.exceptionTrace(e)); } } }; diff --git a/src/main/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapper.java b/src/main/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapper.java index ab2f27d8a..836070293 100644 --- a/src/main/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapper.java +++ b/src/main/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapper.java @@ -8,6 +8,8 @@ import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.UncheckedExecutionException; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LogValues; import com.launchdarkly.sdk.server.integrations.PersistentDataStoreBuilder; import com.launchdarkly.sdk.server.interfaces.DataStore; import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider.CacheStats; @@ -19,8 +21,6 @@ import com.launchdarkly.sdk.server.interfaces.DataStoreUpdates; import com.launchdarkly.sdk.server.interfaces.PersistentDataStore; -import org.slf4j.Logger; - import java.io.IOException; import java.time.Duration; import java.util.AbstractMap; @@ -44,8 +44,6 @@ * This class is only constructed by {@link PersistentDataStoreBuilder}. */ final class PersistentDataStoreWrapper implements DataStore { - private static final Logger logger = Loggers.DATA_STORE; - private final PersistentDataStore core; private final LoadingCache> itemCache; private final LoadingCache> allCache; @@ -55,6 +53,7 @@ final class PersistentDataStoreWrapper implements DataStore { private final Set cachedDataKinds = new HashSet<>(); // this map is used in pollForAvailability() private final AtomicBoolean inited = new AtomicBoolean(false); private final ListeningExecutorService cacheExecutor; + private final LDLogger logger; PersistentDataStoreWrapper( final PersistentDataStore core, @@ -62,9 +61,11 @@ final class PersistentDataStoreWrapper implements DataStore { PersistentDataStoreBuilder.StaleValuesPolicy staleValuesPolicy, boolean recordCacheStats, DataStoreUpdates dataStoreUpdates, - ScheduledExecutorService sharedExecutor + ScheduledExecutorService sharedExecutor, + LDLogger logger ) { this.core = core; + this.logger = logger; if (cacheTtl.isZero()) { itemCache = null; @@ -112,7 +113,8 @@ public Boolean load(String key) throws Exception { true, this::pollAvailabilityAfterOutage, dataStoreUpdates::updateStatus, - sharedExecutor + sharedExecutor, + logger ); } @@ -421,8 +423,9 @@ private boolean pollAvailabilityAfterOutage() { } else { // We failed to write the cached data to the underlying store. In this case, we should not // return to a recovered state, but just try this all again next time the poll task runs. - logger.error("Tried to write cached data to persistent store after a store outage, but failed: {}", e.toString()); - logger.debug(e.toString(), e); + logger.error("Tried to write cached data to persistent store after a store outage, but failed: {}", + LogValues.exceptionSummary(e)); + logger.debug(LogValues.exceptionTrace(e)); return false; } } diff --git a/src/main/java/com/launchdarkly/sdk/server/PollingProcessor.java b/src/main/java/com/launchdarkly/sdk/server/PollingProcessor.java index c0829302a..435712878 100644 --- a/src/main/java/com/launchdarkly/sdk/server/PollingProcessor.java +++ b/src/main/java/com/launchdarkly/sdk/server/PollingProcessor.java @@ -1,6 +1,7 @@ package com.launchdarkly.sdk.server; import com.google.common.annotations.VisibleForTesting; +import com.launchdarkly.logging.LDLogger; import com.launchdarkly.sdk.server.interfaces.DataSource; import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider.ErrorInfo; import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider.ErrorKind; @@ -10,8 +11,6 @@ import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.ItemDescriptor; import com.launchdarkly.sdk.server.interfaces.SerializationException; -import org.slf4j.Logger; - import java.io.IOException; import java.time.Duration; import java.util.concurrent.CompletableFuture; @@ -25,7 +24,6 @@ import static com.launchdarkly.sdk.server.Util.httpErrorDescription; final class PollingProcessor implements DataSource { - private static final Logger logger = Loggers.DATA_SOURCE; private static final String ERROR_CONTEXT_MESSAGE = "on polling request"; private static final String WILL_RETRY_MESSAGE = "will retry at next scheduled poll interval"; @@ -36,18 +34,21 @@ final class PollingProcessor implements DataSource { private final AtomicBoolean initialized = new AtomicBoolean(false); private final CompletableFuture initFuture; private volatile ScheduledFuture task; + private final LDLogger logger; PollingProcessor( FeatureRequestor requestor, DataSourceUpdates dataSourceUpdates, ScheduledExecutorService sharedExecutor, - Duration pollInterval + Duration pollInterval, + LDLogger logger ) { this.requestor = requestor; // note that HTTP configuration is applied to the requestor when it is created this.dataSourceUpdates = dataSourceUpdates; this.scheduler = sharedExecutor; this.pollInterval = pollInterval; this.initFuture = new CompletableFuture<>(); + this.logger = logger; } @Override @@ -73,8 +74,8 @@ public void close() throws IOException { @Override public Future start() { - logger.info("Starting LaunchDarkly polling client with interval: " - + pollInterval.toMillis() + " milliseconds"); + logger.info("Starting LaunchDarkly polling client with interval: {} milliseconds", + pollInterval.toMillis()); synchronized (this) { if (task == null) { diff --git a/src/main/java/com/launchdarkly/sdk/server/StandardEndpoints.java b/src/main/java/com/launchdarkly/sdk/server/StandardEndpoints.java index c540cf026..9e64ce884 100644 --- a/src/main/java/com/launchdarkly/sdk/server/StandardEndpoints.java +++ b/src/main/java/com/launchdarkly/sdk/server/StandardEndpoints.java @@ -2,7 +2,7 @@ import java.net.URI; -import org.slf4j.Logger; +import com.launchdarkly.logging.LDLogger; abstract class StandardEndpoints { private StandardEndpoints() {} @@ -29,7 +29,7 @@ private StandardEndpoints() {} * @param logger the logger to which we should print the warning, if needed * @return the base URI we should connect to */ - static URI selectBaseUri(URI serviceEndpointsValue, URI overrideValue, URI defaultValue, String description, Logger logger) { + static URI selectBaseUri(URI serviceEndpointsValue, URI overrideValue, URI defaultValue, String description, LDLogger logger) { if (overrideValue != null) { return overrideValue; } diff --git a/src/main/java/com/launchdarkly/sdk/server/StreamProcessor.java b/src/main/java/com/launchdarkly/sdk/server/StreamProcessor.java index 0602ca819..a5b731784 100644 --- a/src/main/java/com/launchdarkly/sdk/server/StreamProcessor.java +++ b/src/main/java/com/launchdarkly/sdk/server/StreamProcessor.java @@ -9,6 +9,8 @@ import com.launchdarkly.eventsource.EventSource; import com.launchdarkly.eventsource.MessageEvent; import com.launchdarkly.eventsource.UnsuccessfulResponseException; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LogValues; import com.launchdarkly.sdk.server.StreamProcessorEvents.DeleteData; import com.launchdarkly.sdk.server.StreamProcessorEvents.PatchData; import com.launchdarkly.sdk.server.StreamProcessorEvents.PutData; @@ -22,8 +24,6 @@ import com.launchdarkly.sdk.server.interfaces.HttpConfiguration; import com.launchdarkly.sdk.server.interfaces.SerializationException; -import org.slf4j.Logger; - import java.io.IOException; import java.io.Reader; import java.net.URI; @@ -71,7 +71,6 @@ final class StreamProcessor implements DataSource { private static final String PUT = "put"; private static final String PATCH = "patch"; private static final String DELETE = "delete"; - private static final Logger logger = Loggers.DATA_SOURCE; private static final Duration DEAD_CONNECTION_INTERVAL = Duration.ofSeconds(300); private static final String ERROR_CONTEXT_MESSAGE = "in stream connection"; private static final String WILL_RETRY_MESSAGE = "will retry"; @@ -88,6 +87,7 @@ final class StreamProcessor implements DataSource { private final AtomicBoolean initialized = new AtomicBoolean(false); private volatile long esStarted = 0; private volatile boolean lastStoreUpdateFailed = false; + private final LDLogger logger; ConnectionErrorHandler connectionErrorHandler = createDefaultConnectionErrorHandler(); // exposed for testing @@ -97,7 +97,8 @@ final class StreamProcessor implements DataSource { int threadPriority, DiagnosticAccumulator diagnosticAccumulator, URI streamUri, - Duration initialReconnectDelay + Duration initialReconnectDelay, + LDLogger logger ) { this.dataSourceUpdates = dataSourceUpdates; this.httpConfig = httpConfig; @@ -105,7 +106,8 @@ final class StreamProcessor implements DataSource { this.threadPriority = threadPriority; this.streamUri = streamUri; this.initialReconnectDelay = initialReconnectDelay; - + this.logger = logger; + this.headers = getHeadersBuilderFor(httpConfig) .add("Accept", "text/event-stream") .build(); @@ -189,10 +191,10 @@ public Future start() { EventSource.Builder builder = new EventSource.Builder(handler, endpointUri) .threadPriority(threadPriority) + .logger(new EventSourceLoggerAdapter()) .readBufferSize(5000) .streamEventData(true) .expectFields("event") - .loggerBaseName(Loggers.DATA_SOURCE_LOGGER_NAME) .clientBuilderActions(new EventSource.Builder.ClientConfigurer() { public void configure(OkHttpClient.Builder builder) { configureHttpClientBuilder(httpConfig, builder); @@ -264,15 +266,16 @@ public void onMessage(String eventName, MessageEvent event) throws Exception { break; default: - logger.warn("Unexpected event found in stream: " + eventName); + logger.warn("Unexpected event found in stream: {}", eventName); break; } lastStoreUpdateFailed = false; dataSourceUpdates.updateStatus(State.VALID, null); } catch (StreamInputException e) { - logger.error("LaunchDarkly service request failed or received invalid data: {}", e.toString()); - logger.debug(e.toString(), e); - + logger.error("LaunchDarkly service request failed or received invalid data: {}", + LogValues.exceptionSummary(e)); + logger.debug(LogValues.exceptionTrace(e)); + ErrorInfo errorInfo = new ErrorInfo( e.getCause() instanceof IOException ? ErrorKind.NETWORK_ERROR : ErrorKind.INVALID_DATA, 0, @@ -292,8 +295,8 @@ public void onMessage(String eventName, MessageEvent event) throws Exception { } lastStoreUpdateFailed = true; } catch (Exception e) { - logger.warn("Unexpected error from stream processor: {}", e.toString()); - logger.debug(e.toString(), e); + logger.warn("Unexpected error from stream processor: {}", LogValues.exceptionSummary(e)); + logger.debug(LogValues.exceptionTrace(e)); } } @@ -338,8 +341,8 @@ public void onComment(String comment) { @Override public void onError(Throwable throwable) { - logger.warn("Encountered EventSource error: {}", throwable.toString()); - logger.debug(throwable.toString(), throwable); + logger.warn("Encountered EventSource error: {}", LogValues.exceptionSummary(throwable)); + logger.debug(LogValues.exceptionTrace(throwable)); } } @@ -369,4 +372,31 @@ public StreamInputException(Throwable cause) { // This exception class indicates that the data store failed to persist an update. @SuppressWarnings("serial") private static final class StreamStoreException extends Exception {} + + private final class EventSourceLoggerAdapter implements com.launchdarkly.eventsource.Logger { + @Override + public void debug(String format, Object param) { + logger.debug(format, param); + } + + @Override + public void debug(String format, Object param1, Object param2) { + logger.debug(format, param1, param2); + } + + @Override + public void info(String message) { + logger.info(message); + } + + @Override + public void warn(String message) { + logger.warn(message); + } + + @Override + public void error(String message) { + logger.error(message); + } + } } diff --git a/src/main/java/com/launchdarkly/sdk/server/Util.java b/src/main/java/com/launchdarkly/sdk/server/Util.java index f199407b7..5c3126154 100644 --- a/src/main/java/com/launchdarkly/sdk/server/Util.java +++ b/src/main/java/com/launchdarkly/sdk/server/Util.java @@ -1,12 +1,10 @@ package com.launchdarkly.sdk.server; -import com.launchdarkly.sdk.server.Loggers; +import com.launchdarkly.logging.LDLogger; import com.launchdarkly.sdk.server.interfaces.ApplicationInfo; import com.launchdarkly.sdk.server.interfaces.HttpAuthentication; import com.launchdarkly.sdk.server.interfaces.HttpConfiguration; -import org.slf4j.Logger; - import java.io.IOException; import java.net.URI; import java.nio.file.FileVisitResult; @@ -153,7 +151,7 @@ static boolean isHttpErrorRecoverable(int statusCode) { * @return true if the error is recoverable */ static boolean checkIfErrorIsRecoverableAndLog( - Logger logger, + LDLogger logger, String errorDesc, String errorContext, int statusCode, @@ -222,7 +220,7 @@ static URI concatenateUriPath(URI baseUri, String path) { * @param applicationInfo the application metadata * @return a space-separated string of tags, e.g. "application-id/authentication-service application-version/1.0.0" */ - static String applicationTagHeader(ApplicationInfo applicationInfo) { + static String applicationTagHeader(ApplicationInfo applicationInfo, LDLogger logger) { String[][] tags = { {"applicationId", "application-id", applicationInfo.getApplicationId()}, {"applicationVersion", "application-version", applicationInfo.getApplicationVersion()}, @@ -236,11 +234,11 @@ static String applicationTagHeader(ApplicationInfo applicationInfo) { continue; } if (!TAG_VALUE_REGEX.matcher(tagVal).matches()) { - Loggers.MAIN.warn("Value of ApplicationInfo.{} contained invalid characters and was discarded", javaKey); + logger.warn("Value of ApplicationInfo.{} contained invalid characters and was discarded", javaKey); continue; } if (tagVal.length() > 64) { - Loggers.MAIN.warn("Value of ApplicationInfo.{} was longer than 64 characters and was discarded", javaKey); + logger.warn("Value of ApplicationInfo.{} was longer than 64 characters and was discarded", javaKey); continue; } parts.add(tagKey + "/" + tagVal); diff --git a/src/main/java/com/launchdarkly/sdk/server/integrations/FileDataSourceBuilder.java b/src/main/java/com/launchdarkly/sdk/server/integrations/FileDataSourceBuilder.java index af7838fe0..0ed450ca1 100644 --- a/src/main/java/com/launchdarkly/sdk/server/integrations/FileDataSourceBuilder.java +++ b/src/main/java/com/launchdarkly/sdk/server/integrations/FileDataSourceBuilder.java @@ -1,6 +1,7 @@ package com.launchdarkly.sdk.server.integrations; import com.google.common.io.ByteStreams; +import com.launchdarkly.logging.LDLogger; import com.launchdarkly.sdk.server.interfaces.ClientContext; import com.launchdarkly.sdk.server.interfaces.DataSource; import com.launchdarkly.sdk.server.interfaces.DataSourceFactory; @@ -122,7 +123,8 @@ public FileDataSourceBuilder duplicateKeysHandling(FileData.DuplicateKeysHandlin */ @Override public DataSource createDataSource(ClientContext context, DataSourceUpdates dataSourceUpdates) { - return new FileDataSourceImpl(dataSourceUpdates, sources, autoUpdate, duplicateKeysHandling); + LDLogger logger = context.getBasic().getBaseLogger().subLogger("DataSource"); + return new FileDataSourceImpl(dataSourceUpdates, sources, autoUpdate, duplicateKeysHandling, logger); } static abstract class SourceInfo { diff --git a/src/main/java/com/launchdarkly/sdk/server/integrations/FileDataSourceImpl.java b/src/main/java/com/launchdarkly/sdk/server/integrations/FileDataSourceImpl.java index e1ade6b6f..99fdfa98f 100644 --- a/src/main/java/com/launchdarkly/sdk/server/integrations/FileDataSourceImpl.java +++ b/src/main/java/com/launchdarkly/sdk/server/integrations/FileDataSourceImpl.java @@ -1,8 +1,9 @@ package com.launchdarkly.sdk.server.integrations; import com.google.common.collect.ImmutableList; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LogValues; import com.launchdarkly.sdk.LDValue; -import com.launchdarkly.sdk.server.LDClient; import com.launchdarkly.sdk.server.integrations.FileDataSourceBuilder.SourceInfo; import com.launchdarkly.sdk.server.integrations.FileDataSourceParsing.FileDataException; import com.launchdarkly.sdk.server.integrations.FileDataSourceParsing.FlagFactory; @@ -18,9 +19,6 @@ import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.ItemDescriptor; import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.KeyedItems; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.FileSystem; @@ -54,28 +52,29 @@ * optionally whenever files change. */ final class FileDataSourceImpl implements DataSource { - private static final Logger logger = LoggerFactory.getLogger(LDClient.class.getName() + ".DataSource"); - private final DataSourceUpdates dataSourceUpdates; private final DataLoader dataLoader; private final FileData.DuplicateKeysHandling duplicateKeysHandling; private final AtomicBoolean inited = new AtomicBoolean(false); private final FileWatcher fileWatcher; + private final LDLogger logger; FileDataSourceImpl( DataSourceUpdates dataSourceUpdates, List sources, boolean autoUpdate, - FileData.DuplicateKeysHandling duplicateKeysHandling + FileData.DuplicateKeysHandling duplicateKeysHandling, + LDLogger logger ) { this.dataSourceUpdates = dataSourceUpdates; this.dataLoader = new DataLoader(sources); this.duplicateKeysHandling = duplicateKeysHandling; + this.logger = logger; FileWatcher fw = null; if (autoUpdate) { try { - fw = FileWatcher.create(dataLoader.getSources()); + fw = FileWatcher.create(dataLoader.getSources(), logger); } catch (IOException e) { // COVERAGE: there is no way to simulate this condition in a unit test logger.error("Unable to watch files for auto-updating: {}", e.toString()); @@ -139,9 +138,10 @@ private static final class FileWatcher implements Runnable { private final Set watchedFilePaths; private Runnable fileModifiedAction; private final Thread thread; + private final LDLogger logger; private volatile boolean stopped; - private static FileWatcher create(Iterable sources) throws IOException { + private static FileWatcher create(Iterable sources, LDLogger logger) throws IOException { Set directoryPaths = new HashSet<>(); Set absoluteFilePaths = new HashSet<>(); FileSystem fs = FileSystems.getDefault(); @@ -159,12 +159,13 @@ private static FileWatcher create(Iterable sources) throws IOExcepti d.register(ws, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); } - return new FileWatcher(ws, absoluteFilePaths); + return new FileWatcher(ws, absoluteFilePaths, logger); } - private FileWatcher(WatchService watchService, Set watchedFilePaths) { + private FileWatcher(WatchService watchService, Set watchedFilePaths, LDLogger logger) { this.watchService = watchService; this.watchedFilePaths = watchedFilePaths; + this.logger = logger; thread = new Thread(this, FileDataSourceImpl.class.getName()); thread.setDaemon(true); @@ -193,7 +194,7 @@ public void run() { fileModifiedAction.run(); } catch (Exception e) { // COVERAGE: there is no way to simulate this condition in a unit test - logger.warn("Unexpected exception when reloading file data: " + e); + logger.warn("Unexpected exception when reloading file data: {}", LogValues.exceptionSummary(e)); } } key.reset(); // if we don't do this, the watch on this key stops working diff --git a/src/main/java/com/launchdarkly/sdk/server/integrations/LoggingConfigurationBuilder.java b/src/main/java/com/launchdarkly/sdk/server/integrations/LoggingConfigurationBuilder.java index 9816f8962..b321d7f7f 100644 --- a/src/main/java/com/launchdarkly/sdk/server/integrations/LoggingConfigurationBuilder.java +++ b/src/main/java/com/launchdarkly/sdk/server/integrations/LoggingConfigurationBuilder.java @@ -1,5 +1,8 @@ package com.launchdarkly.sdk.server.integrations; +import com.launchdarkly.logging.LDLogAdapter; +import com.launchdarkly.logging.LDLogLevel; +import com.launchdarkly.logging.Logs; import com.launchdarkly.sdk.server.Components; import com.launchdarkly.sdk.server.interfaces.LoggingConfigurationFactory; @@ -30,7 +33,105 @@ public abstract class LoggingConfigurationBuilder implements LoggingConfiguratio */ public static final Duration DEFAULT_LOG_DATA_SOURCE_OUTAGE_AS_ERROR_AFTER = Duration.ofMinutes(1); + protected String baseName = null; protected Duration logDataSourceOutageAsErrorAfter = DEFAULT_LOG_DATA_SOURCE_OUTAGE_AS_ERROR_AFTER; + protected LDLogAdapter logAdapter = null; + protected LDLogLevel minimumLevel = null; + + /** + * Specifies the implementation of logging to use. + *

+ * The com.launchdarkly.logging + * API defines the {@link LDLogAdapter} interface to specify where log output should be sent. By default, + * it is set to {@link com.launchdarkly.logging.LDSLF4J#adapter()}, meaning that output will be sent to + * SLF4J and controlled by the SLF4J configuration. You may use + * the {@link com.launchdarkly.logging.Logs} factory methods, or a custom implementation, to handle log + * output differently. For instance, you may specify {@link com.launchdarkly.logging.Logs#basic()} for + * simple console output, or {@link com.launchdarkly.logging.Logs#toJavaUtilLogging()} to use the + * java.util.logging framework. + *

+ * For more about logging adapters, + * see the SDK reference guide + * and the API documentation for + * com.launchdarkly.logging. + *

+ * If you don't need to customize any options other than the adapter, you can call + * {@link Components#logging(LDLogAdapter)} as a shortcut rather than using + * {@link LoggingConfigurationBuilder}. + * + * @param logAdapter an {@link LDLogAdapter} for the desired logging implementation + * @return the builder + * @since 5.10.0 + */ + public LoggingConfigurationBuilder adapter(LDLogAdapter logAdapter) { + this.logAdapter = logAdapter; + return this; + } + + /** + * Specifies a custom base logger name. + *

+ * Logger names are used to give context to the log output, indicating that it is from the + * LaunchDarkly SDK instead of another component, or indicating a more specific area of + * functionality within the SDK. Many logging implementations show the logger name in + * in brackets, for instance: + *


+   *     [com.launchdarkly.sdk.LDClient] INFO: Reconnected to LaunchDarkly stream
+   * 
+ *

+ * If you are using an adapter for a third-party logging framework such as SLF4J (see + * {@link #adapter(LDLogAdapter)}), most frameworks have a mechanism for filtering log + * output by the logger name. + *

+ * By default, the SDK uses a base logger name of com.launchdarkly.sdk.LDClient. + * Messages will be logged either under this name, or with a suffix to indicate what + * general area of functionality is involved: + *

    + *
  • .DataSource: problems or status messages regarding how the SDK gets + * feature flag data from LaunchDarkly.
  • + *
  • .DataStore: problems or status messages regarding how the SDK stores its + * feature flag data (for instance, if you are using a database).
  • + *
  • .Evaluation: problems in evaluating a feature flag or flags, which were + * caused by invalid flag data or incorrect usage of the SDK rather than for instance a + * database problem.
  • + *
  • .Events problems or status messages regarding the SDK's delivery of + * analytics event data to LaunchDarkly.
  • + *
+ *

+ * Setting {@link #baseLoggerName(String)} to a non-null value overrides the default. The + * SDK still adds the same suffixes to the name, so for instance if you set it to + * "LD", the example message above would show [LD.DataSource]. + * + * @param name the base logger name + * @return the builder + * @since 5.10.0 + */ + public LoggingConfigurationBuilder baseLoggerName(String name) { + this.baseName = name; + return this; + } + + /** + * Specifies the lowest level of logging to enable. + *

+ * This is only applicable when using an implementation of logging that does not have its own + * external configuration mechanism, such as {@link Logs#toConsole()}. It adds a log level filter + * so that log messages at lower levels are suppressed. For instance, setting the minimum level to + * {@link LDLogLevel#INFO} means that DEBUG-level output is disabled. If not specified, + * the default minimum level is {@link LDLogLevel#INFO}. + *

+ * When using a logging framework like SLF4J or {@code java.util.logging} that has its own + * separate mechanism for log filtering, you must use that framework's configuration options for + * log levels; calling {@link #level(LDLogLevel)} in that case has no effect. + * + * @param minimumLevel the lowest level of logging to enable + * @return the builder + * @since 5.10.0 + */ + public LoggingConfigurationBuilder level(LDLogLevel minimumLevel) { + this.minimumLevel = minimumLevel; + return this; + } /** * Sets the time threshold, if any, after which the SDK will log a data source outage at {@code ERROR} diff --git a/src/main/java/com/launchdarkly/sdk/server/integrations/TestData.java b/src/main/java/com/launchdarkly/sdk/server/integrations/TestData.java index e0caeb418..488ad74cf 100644 --- a/src/main/java/com/launchdarkly/sdk/server/integrations/TestData.java +++ b/src/main/java/com/launchdarkly/sdk/server/integrations/TestData.java @@ -323,17 +323,16 @@ public FlagBuilder offVariation(int variationIndex) { /** * Sets the flag to always return the specified boolean variation for all users. *

- * VariationForAllUsers sets the flag to return the specified boolean variation by default for all users. - *

* Targeting is switched on, any existing targets or rules are removed, and the flag's variations are * set to true and false. The fallthrough variation is set to the specified value. The off variation is * left unchanged. * * @param variation the desired true/false variation to be returned for all users * @return the builder + * @since 5.10.0 */ - public FlagBuilder variationForAllUsers(boolean variation) { - return booleanFlag().variationForAllUsers(variationForBoolean(variation)); + public FlagBuilder variationForAll(boolean variation) { + return booleanFlag().variationForAll(variationForBoolean(variation)); } /** @@ -345,11 +344,42 @@ public FlagBuilder variationForAllUsers(boolean variation) { * * @param variationIndex the desired variation: 0 for the first, 1 for the second, etc. * @return the builder + * @since 5.10.0 */ - public FlagBuilder variationForAllUsers(int variationIndex) { + public FlagBuilder variationForAll(int variationIndex) { return on(true).clearRules().clearUserTargets().fallthroughVariation(variationIndex); } + /** + * Deprecated name for {@link #variationForAll(boolean)}. + *

+ * This method name will be dropped in a future SDK version because "users" will not always be the + * only kind of input for an evaluation. + * + * @param variation the desired true/false variation to be returned for all users + * @return the builder + * @deprecated Use {@link #variationForAll(boolean)}. + */ + @Deprecated + public FlagBuilder variationForAllUsers(boolean variation) { + return variationForAll(variation); + } + + /** + * Deprecated name for {@link #variationForAll(int)}. + *

+ * This method name will be dropped in a future SDK version because "users" will not always be the + * only kind of input for an evaluation. + * + * @param variationIndex the desired variation: 0 for the first, 1 for the second, etc. + * @return the builder + * @deprecated Use {@link #variationForAll(int)}. + */ + @Deprecated + public FlagBuilder variationForAllUsers(int variationIndex) { + return variationForAll(variationIndex); + } + /** * Sets the flag to always return the specified variation value for all users. *

@@ -361,10 +391,25 @@ public FlagBuilder variationForAllUsers(int variationIndex) { * @param value the desired value to be returned for all users * @return the builder */ - public FlagBuilder valueForAllUsers(LDValue value) { + public FlagBuilder valueForAll(LDValue value) { variations.clear(); variations.add(value); - return variationForAllUsers(0); + return variationForAll(0); + } + + /** + * Deprecated name for {@link #valueForAll(LDValue)}. + *

+ * This method name will be dropped in a future SDK version because "users" will not always be the + * only kind of input for an evaluation. + * + * @param value the desired value to be returned for all users + * @return the builder' + * @deprecated Use {@link #valueForAll(LDValue)}. + */ + @Deprecated + public FlagBuilder valueForAllUsers(LDValue value) { + return valueForAll(value); } /** diff --git a/src/main/java/com/launchdarkly/sdk/server/interfaces/BasicConfiguration.java b/src/main/java/com/launchdarkly/sdk/server/interfaces/BasicConfiguration.java index 981d20255..238dd6a3a 100644 --- a/src/main/java/com/launchdarkly/sdk/server/interfaces/BasicConfiguration.java +++ b/src/main/java/com/launchdarkly/sdk/server/interfaces/BasicConfiguration.java @@ -1,5 +1,6 @@ package com.launchdarkly.sdk.server.interfaces; +import com.launchdarkly.logging.LDLogger; import com.launchdarkly.sdk.server.Components; /** @@ -13,6 +14,7 @@ public final class BasicConfiguration { private final int threadPriority; private final ApplicationInfo applicationInfo; private final ServiceEndpoints serviceEndpoints; + private final LDLogger baseLogger; /** * Constructs an instance. @@ -22,13 +24,42 @@ public final class BasicConfiguration { * @param threadPriority the thread priority that should be used for any worker threads created by SDK components * @param applicationInfo metadata about the application using this SDK * @param serviceEndpoints the SDK's service URIs + * @param baseLogger the base logger + * @since 5.10.0 */ - public BasicConfiguration(String sdkKey, boolean offline, int threadPriority, ApplicationInfo applicationInfo, ServiceEndpoints serviceEndpoints) { + public BasicConfiguration( + String sdkKey, + boolean offline, + int threadPriority, + ApplicationInfo applicationInfo, + ServiceEndpoints serviceEndpoints, + LDLogger baseLogger + ) { this.sdkKey = sdkKey; this.offline = offline; this.threadPriority = threadPriority; this.applicationInfo = applicationInfo; this.serviceEndpoints = serviceEndpoints != null ? serviceEndpoints : Components.serviceEndpoints().createServiceEndpoints(); + this.baseLogger = baseLogger != null ? baseLogger : LDLogger.none(); + } + + /** + * Constructs an instance. + * + * @param sdkKey the SDK key + * @param offline true if the SDK was configured to be completely offline + * @param threadPriority the thread priority that should be used for any worker threads created by SDK components + * @param applicationInfo metadata about the application using this SDK + * @param serviceEndpoints the SDK's service URIs + */ + public BasicConfiguration( + String sdkKey, + boolean offline, + int threadPriority, + ApplicationInfo applicationInfo, + ServiceEndpoints serviceEndpoints + ) { + this(sdkKey, offline, threadPriority, applicationInfo, serviceEndpoints, null); } /** @@ -42,7 +73,7 @@ public BasicConfiguration(String sdkKey, boolean offline, int threadPriority, Ap */ @Deprecated public BasicConfiguration(String sdkKey, boolean offline, int threadPriority, ApplicationInfo applicationInfo) { - this(sdkKey, offline, threadPriority, applicationInfo, null); + this(sdkKey, offline, threadPriority, applicationInfo, null, null); } /** @@ -106,4 +137,16 @@ public ApplicationInfo getApplicationInfo() { public ServiceEndpoints getServiceEndpoints() { return serviceEndpoints; } + + /** + * Returns the base logger used by SDK components. Suffixes may be added to the logger name for + * specific areas of functionality. + * + * @return the base logger + * @see com.launchdarkly.sdk.server.LDConfig.Builder#logging(LoggingConfigurationFactory) + * @since 5.10.0 + */ + public LDLogger getBaseLogger() { + return baseLogger; + } } diff --git a/src/main/java/com/launchdarkly/sdk/server/interfaces/ClientContext.java b/src/main/java/com/launchdarkly/sdk/server/interfaces/ClientContext.java index 558777047..e5db5fc5f 100644 --- a/src/main/java/com/launchdarkly/sdk/server/interfaces/ClientContext.java +++ b/src/main/java/com/launchdarkly/sdk/server/interfaces/ClientContext.java @@ -34,5 +34,4 @@ public interface ClientContext { * @return the logging configuration */ public LoggingConfiguration getLogging(); - } diff --git a/src/main/java/com/launchdarkly/sdk/server/interfaces/EventSenderFactory.java b/src/main/java/com/launchdarkly/sdk/server/interfaces/EventSenderFactory.java index 3625b0d0b..f576a530b 100644 --- a/src/main/java/com/launchdarkly/sdk/server/interfaces/EventSenderFactory.java +++ b/src/main/java/com/launchdarkly/sdk/server/interfaces/EventSenderFactory.java @@ -1,5 +1,7 @@ package com.launchdarkly.sdk.server.interfaces; +import com.launchdarkly.logging.LDLogger; + /** * Interface for a factory that creates some implementation of {@link EventSender}. * @@ -8,11 +10,30 @@ */ public interface EventSenderFactory { /** - * Called by the SDK to create the implementation object. + * Older method for creating the implementation object. This is superseded by the method that + * includes a logger instance. * * @param basicConfiguration the basic global SDK configuration properties * @param httpConfiguration HTTP configuration properties * @return an {@link EventSender} + * @deprecated use the overload that includes a logger */ + @Deprecated EventSender createEventSender(BasicConfiguration basicConfiguration, HttpConfiguration httpConfiguration); + + /** + * Called by the SDK to create the implementation object. + * + * @param basicConfiguration the basic global SDK configuration properties + * @param httpConfiguration HTTP configuration properties + * @param logger the configured logger + * @return an {@link EventSender} + * @since 5.10.0 + */ + default EventSender createEventSender( + BasicConfiguration basicConfiguration, + HttpConfiguration httpConfiguration, + LDLogger logger) { + return createEventSender(basicConfiguration, httpConfiguration); + } } diff --git a/src/main/java/com/launchdarkly/sdk/server/interfaces/LoggingConfiguration.java b/src/main/java/com/launchdarkly/sdk/server/interfaces/LoggingConfiguration.java index 695c02f7d..45403bcab 100644 --- a/src/main/java/com/launchdarkly/sdk/server/interfaces/LoggingConfiguration.java +++ b/src/main/java/com/launchdarkly/sdk/server/interfaces/LoggingConfiguration.java @@ -1,5 +1,6 @@ package com.launchdarkly.sdk.server.interfaces; +import com.launchdarkly.logging.LDLogAdapter; import com.launchdarkly.sdk.server.integrations.LoggingConfigurationBuilder; import java.time.Duration; @@ -20,4 +21,22 @@ public interface LoggingConfiguration { * @see LoggingConfigurationBuilder#logDataSourceOutageAsErrorAfter(java.time.Duration) */ Duration getLogDataSourceOutageAsErrorAfter(); + + /** + * Returns the configured base logger name. + * @return the logger name + * @since 5.10.0 + */ + default String getBaseLoggerName() { + return null; + } + + /** + * Returns the configured logging adapter. + * @return the logging adapter + * @since 5.10.0 + */ + default LDLogAdapter getLogAdapter() { + return null; + } } diff --git a/src/test/java/com/launchdarkly/sdk/server/BaseTest.java b/src/test/java/com/launchdarkly/sdk/server/BaseTest.java new file mode 100644 index 000000000..0aa5ac17b --- /dev/null +++ b/src/test/java/com/launchdarkly/sdk/server/BaseTest.java @@ -0,0 +1,50 @@ +package com.launchdarkly.sdk.server; + +import com.launchdarkly.logging.LDLogAdapter; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LogCapture; +import com.launchdarkly.logging.Logs; + +import org.junit.Rule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +@SuppressWarnings("javadoc") +public class BaseTest { + @Rule public DumpLogIfTestFails dumpLogIfTestFails; + + protected final LDLogAdapter testLogging; + protected final LDLogger testLogger; + protected final LogCapture logCapture; + + protected BaseTest() { + logCapture = Logs.capture(); + testLogging = logCapture; + testLogger = LDLogger.withAdapter(testLogging, ""); + dumpLogIfTestFails = new DumpLogIfTestFails(); + } + + /** + * Creates a configuration builder with the basic properties that we want for all tests unless + * otherwise specified: do not connect to an external data source, do not send events, and + * redirect all logging to the test logger for the current test (which will be printed to the + * console only if the test fails). + * + * @return a configuraiton builder + */ + protected LDConfig.Builder baseConfig() { + return new LDConfig.Builder() + .dataSource(Components.externalUpdatesOnly()) + .events(Components.noEvents()) + .logging(Components.logging(testLogging)); + } + + class DumpLogIfTestFails extends TestWatcher { + @Override + protected void failed(Throwable e, Description description) { + for (LogCapture.Message message: logCapture.getMessages()) { + System.out.println("LOG {" + description.getDisplayName() + "} >>> " + message.toStringWithTimestamp()); + } + } + } +} diff --git a/src/test/java/com/launchdarkly/sdk/server/BigSegmentStoreStatusProviderImplTest.java b/src/test/java/com/launchdarkly/sdk/server/BigSegmentStoreStatusProviderImplTest.java index 7d0f96540..923b1ab83 100644 --- a/src/test/java/com/launchdarkly/sdk/server/BigSegmentStoreStatusProviderImplTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/BigSegmentStoreStatusProviderImplTest.java @@ -12,49 +12,50 @@ import org.junit.Test; @SuppressWarnings("javadoc") -public class BigSegmentStoreStatusProviderImplTest extends EasyMockSupport { +public class BigSegmentStoreStatusProviderImplTest extends BaseTest { // We don't need to extensively test status broadcasting behavior, just that the implementation // delegates to the BigSegmentStoreWrapper and EventBroadcasterImpl. private StatusListener mockStatusListener; private EventBroadcasterImpl mockEventBroadcaster; + private final EasyMockSupport mocks = new EasyMockSupport(); @Before @SuppressWarnings("unchecked") public void setup() { - mockEventBroadcaster = strictMock(EventBroadcasterImpl.class); - mockStatusListener = strictMock(StatusListener.class); + mockEventBroadcaster = mocks.strictMock(EventBroadcasterImpl.class); + mockStatusListener = mocks.strictMock(StatusListener.class); } @Test public void statusUnavailableWithNullWrapper() { - replayAll(); + mocks.replayAll(); BigSegmentStoreStatusProviderImpl statusProvider = new BigSegmentStoreStatusProviderImpl(mockEventBroadcaster, null); assertEquals(statusProvider.getStatus(), new Status(false, false)); - verifyAll(); + mocks.verifyAll(); } @Test public void statusDelegatedToWrapper() { - BigSegmentStoreWrapper storeWrapper = strictMock(BigSegmentStoreWrapper.class); + BigSegmentStoreWrapper storeWrapper = mocks.strictMock(BigSegmentStoreWrapper.class); expect(storeWrapper.getStatus()).andReturn(new Status(true, false)).once(); - replayAll(); + mocks.replayAll(); BigSegmentStoreStatusProviderImpl statusProvider = new BigSegmentStoreStatusProviderImpl(mockEventBroadcaster, storeWrapper); assertEquals(statusProvider.getStatus(), new Status(true, false)); - verifyAll(); + mocks.verifyAll(); } @Test public void listenersDelegatedToEventBroadcaster() { mockEventBroadcaster.register(same(mockStatusListener)); mockEventBroadcaster.unregister(same(mockStatusListener)); - replayAll(); + mocks.replayAll(); BigSegmentStoreStatusProviderImpl statusProvider = new BigSegmentStoreStatusProviderImpl(mockEventBroadcaster, null); statusProvider.addStatusListener(mockStatusListener); statusProvider.removeStatusListener(mockStatusListener); - verifyAll(); + mocks.verifyAll(); } } diff --git a/src/test/java/com/launchdarkly/sdk/server/BigSegmentStoreWrapperTest.java b/src/test/java/com/launchdarkly/sdk/server/BigSegmentStoreWrapperTest.java index c758966ba..bec8e1bba 100644 --- a/src/test/java/com/launchdarkly/sdk/server/BigSegmentStoreWrapperTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/BigSegmentStoreWrapperTest.java @@ -1,6 +1,7 @@ package com.launchdarkly.sdk.server; import static com.launchdarkly.sdk.server.TestComponents.clientContext; +import static com.launchdarkly.sdk.server.TestComponents.nullLogger; import static com.launchdarkly.sdk.server.TestComponents.sharedExecutor; import static com.launchdarkly.sdk.server.interfaces.BigSegmentStoreTypes.createMembershipFromSegmentRefs; import static org.easymock.EasyMock.expect; @@ -33,9 +34,10 @@ import java.util.concurrent.atomic.AtomicReference; @SuppressWarnings("javadoc") -public class BigSegmentStoreWrapperTest extends EasyMockSupport { +public class BigSegmentStoreWrapperTest extends BaseTest { private static final String SDK_KEY = "sdk-key"; + private final EasyMockSupport mocks = new EasyMockSupport(); private AtomicBoolean storeUnavailable; private AtomicReference storeMetadata; private BigSegmentStore storeMock; @@ -44,20 +46,24 @@ public class BigSegmentStoreWrapperTest extends EasyMockSupport { @Before public void setup() { - eventBroadcaster = EventBroadcasterImpl.forBigSegmentStoreStatus(sharedExecutor); + eventBroadcaster = EventBroadcasterImpl.forBigSegmentStoreStatus(sharedExecutor, nullLogger); storeUnavailable = new AtomicBoolean(false); storeMetadata = new AtomicReference<>(null); - storeMock = niceMock(BigSegmentStore.class); + storeMock = mocks.niceMock(BigSegmentStore.class); expect(storeMock.getMetadata()).andAnswer(() -> { if (storeUnavailable.get()) { throw new RuntimeException("sorry"); } return storeMetadata.get(); }).anyTimes(); - storeFactoryMock = strictMock(BigSegmentStoreFactory.class); + storeFactoryMock = mocks.strictMock(BigSegmentStoreFactory.class); expect(storeFactoryMock.createBigSegmentStore(isA(ClientContext.class))).andReturn(storeMock); } + private BigSegmentStoreWrapper makeWrapper(BigSegmentsConfiguration bsConfig) { + return new BigSegmentStoreWrapper(bsConfig, eventBroadcaster, sharedExecutor, testLogger); + } + private void setStoreMembership(String userKey, Membership membership) { expect(storeMock.getMembership(BigSegmentStoreWrapper.hashForUserKey(userKey))).andReturn(membership); } @@ -68,13 +74,13 @@ public void membershipQueryWithUncachedResultAndHealthyStatus() throws Exception String userKey = "userkey"; setStoreMembership(userKey, expectedMembership); - replayAll(); + mocks.replayAll(); storeMetadata.set(new StoreMetadata(System.currentTimeMillis())); BigSegmentsConfiguration bsConfig = Components.bigSegments(storeFactoryMock) .staleAfter(Duration.ofDays(1)) .createBigSegmentsConfiguration(clientContext(SDK_KEY, new LDConfig.Builder().build())); - try (BigSegmentStoreWrapper wrapper = new BigSegmentStoreWrapper(bsConfig, eventBroadcaster, sharedExecutor)) { + try (BigSegmentStoreWrapper wrapper = makeWrapper(bsConfig)) { BigSegmentsQueryResult res = wrapper.getUserMembership(userKey); assertEquals(expectedMembership, res.membership); assertEquals(BigSegmentsStatus.HEALTHY, res.status); @@ -85,13 +91,13 @@ public void membershipQueryWithUncachedResultAndHealthyStatus() throws Exception public void membershipQueryReturnsNull() throws Exception { String userKey = "userkey"; setStoreMembership(userKey, null); - replayAll(); + mocks.replayAll(); storeMetadata.set(new StoreMetadata(System.currentTimeMillis())); BigSegmentsConfiguration bsConfig = Components.bigSegments(storeFactoryMock) .staleAfter(Duration.ofDays(1)) .createBigSegmentsConfiguration(clientContext(SDK_KEY, new LDConfig.Builder().build())); - try (BigSegmentStoreWrapper wrapper = new BigSegmentStoreWrapper(bsConfig, eventBroadcaster, sharedExecutor)) { + try (BigSegmentStoreWrapper wrapper = makeWrapper(bsConfig)) { BigSegmentsQueryResult res = wrapper.getUserMembership(userKey); assertEquals(createMembershipFromSegmentRefs(null, null), res.membership); assertEquals(BigSegmentsStatus.HEALTHY, res.status); @@ -104,13 +110,13 @@ public void membershipQueryWithCachedResultAndHealthyStatus() throws Exception { String userKey = "userkey"; setStoreMembership(userKey, expectedMembership); - replayAll(); + mocks.replayAll(); storeMetadata.set(new StoreMetadata(System.currentTimeMillis())); BigSegmentsConfiguration bsConfig = Components.bigSegments(storeFactoryMock) .staleAfter(Duration.ofDays(1)) .createBigSegmentsConfiguration(clientContext(SDK_KEY, new LDConfig.Builder().build())); - try (BigSegmentStoreWrapper wrapper = new BigSegmentStoreWrapper(bsConfig, eventBroadcaster, sharedExecutor)) { + try (BigSegmentStoreWrapper wrapper = makeWrapper(bsConfig)) { BigSegmentsQueryResult res1 = wrapper.getUserMembership(userKey); assertEquals(expectedMembership, res1.membership); assertEquals(BigSegmentsStatus.HEALTHY, res1.status); @@ -127,13 +133,13 @@ public void membershipQueryWithStaleStatus() throws Exception { String userKey = "userkey"; setStoreMembership(userKey, expectedMembership); - replayAll(); + mocks.replayAll(); storeMetadata.set(new StoreMetadata(System.currentTimeMillis() - 1000)); BigSegmentsConfiguration bsConfig = Components.bigSegments(storeFactoryMock) .staleAfter(Duration.ofMillis(500)) .createBigSegmentsConfiguration(clientContext(SDK_KEY, new LDConfig.Builder().build())); - try (BigSegmentStoreWrapper wrapper = new BigSegmentStoreWrapper(bsConfig, eventBroadcaster, sharedExecutor)) { + try (BigSegmentStoreWrapper wrapper = makeWrapper(bsConfig)) { BigSegmentsQueryResult res = wrapper.getUserMembership(userKey); assertEquals(expectedMembership, res.membership); assertEquals(BigSegmentsStatus.STALE, res.status); @@ -146,13 +152,13 @@ public void membershipQueryWithStaleStatusDueToNoStoreMetadata() throws Exceptio String userKey = "userkey"; setStoreMembership(userKey, expectedMembership); - replayAll(); + mocks.replayAll(); storeMetadata.set(null); BigSegmentsConfiguration bsConfig = Components.bigSegments(storeFactoryMock) .staleAfter(Duration.ofMillis(500)) .createBigSegmentsConfiguration(clientContext(SDK_KEY, new LDConfig.Builder().build())); - try (BigSegmentStoreWrapper wrapper = new BigSegmentStoreWrapper(bsConfig, eventBroadcaster, sharedExecutor)) { + try (BigSegmentStoreWrapper wrapper = makeWrapper(bsConfig)) { BigSegmentsQueryResult res = wrapper.getUserMembership(userKey); assertEquals(expectedMembership, res.membership); assertEquals(BigSegmentsStatus.STALE, res.status); @@ -170,14 +176,14 @@ public void leastRecentUserIsEvictedFromCache() throws Exception { setStoreMembership(userKey3, expectedMembership3); setStoreMembership(userKey1, expectedMembership1); - replayAll(); + mocks.replayAll(); storeMetadata.set(new StoreMetadata(System.currentTimeMillis())); BigSegmentsConfiguration bsConfig = Components.bigSegments(storeFactoryMock) .userCacheSize(2) .staleAfter(Duration.ofDays(1)) .createBigSegmentsConfiguration(clientContext(SDK_KEY, new LDConfig.Builder().build())); - try (BigSegmentStoreWrapper wrapper = new BigSegmentStoreWrapper(bsConfig, eventBroadcaster, sharedExecutor)) { + try (BigSegmentStoreWrapper wrapper = makeWrapper(bsConfig)) { BigSegmentsQueryResult res1 = wrapper.getUserMembership(userKey1); assertEquals(expectedMembership1, res1.membership); assertEquals(BigSegmentsStatus.HEALTHY, res1.status); @@ -206,14 +212,14 @@ public void leastRecentUserIsEvictedFromCache() throws Exception { @Test public void pollingDetectsStoreUnavailability() throws Exception { - replayAll(); + mocks.replayAll(); storeMetadata.set(new StoreMetadata(System.currentTimeMillis())); BigSegmentsConfiguration bsConfig = Components.bigSegments(storeFactoryMock) .statusPollInterval(Duration.ofMillis(10)) .staleAfter(Duration.ofDays(1)) .createBigSegmentsConfiguration(clientContext(SDK_KEY, new LDConfig.Builder().build())); - try (BigSegmentStoreWrapper wrapper = new BigSegmentStoreWrapper(bsConfig, eventBroadcaster, sharedExecutor)) { + try (BigSegmentStoreWrapper wrapper = makeWrapper(bsConfig)) { assertTrue(wrapper.getStatus().isAvailable()); BlockingQueue statuses = new LinkedBlockingQueue<>(); @@ -233,14 +239,14 @@ public void pollingDetectsStoreUnavailability() throws Exception { @Test public void pollingDetectsStaleStatus() throws Exception { - replayAll(); + mocks.replayAll(); storeMetadata.set(new StoreMetadata(System.currentTimeMillis() + 10000)); BigSegmentsConfiguration bsConfig = Components.bigSegments(storeFactoryMock) .statusPollInterval(Duration.ofMillis(10)) .staleAfter(Duration.ofMillis(200)) .createBigSegmentsConfiguration(clientContext(SDK_KEY, new LDConfig.Builder().build())); - try (BigSegmentStoreWrapper wrapper = new BigSegmentStoreWrapper(bsConfig, eventBroadcaster, sharedExecutor)) { + try (BigSegmentStoreWrapper wrapper = makeWrapper(bsConfig)) { assertFalse(wrapper.getStatus().isStale()); BlockingQueue statuses = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/launchdarkly/sdk/server/DataSourceStatusProviderImplTest.java b/src/test/java/com/launchdarkly/sdk/server/DataSourceStatusProviderImplTest.java index f229b7424..d7648a00d 100644 --- a/src/test/java/com/launchdarkly/sdk/server/DataSourceStatusProviderImplTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/DataSourceStatusProviderImplTest.java @@ -24,16 +24,17 @@ import static org.hamcrest.Matchers.sameInstance; @SuppressWarnings("javadoc") -public class DataSourceStatusProviderImplTest { +public class DataSourceStatusProviderImplTest extends BaseTest { private EventBroadcasterImpl broadcaster = - EventBroadcasterImpl.forDataSourceStatus(sharedExecutor); + EventBroadcasterImpl.forDataSourceStatus(sharedExecutor, testLogger); private DataSourceUpdatesImpl updates = new DataSourceUpdatesImpl( TestComponents.inMemoryDataStore(), null, null, broadcaster, sharedExecutor, - null + null, + testLogger ); private DataSourceStatusProviderImpl statusProvider = new DataSourceStatusProviderImpl(broadcaster, updates); diff --git a/src/test/java/com/launchdarkly/sdk/server/DataSourceUpdatesImplTest.java b/src/test/java/com/launchdarkly/sdk/server/DataSourceUpdatesImplTest.java index 319dd8648..25ae6cee5 100644 --- a/src/test/java/com/launchdarkly/sdk/server/DataSourceUpdatesImplTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/DataSourceUpdatesImplTest.java @@ -33,6 +33,7 @@ import static com.launchdarkly.sdk.server.ModelBuilders.ruleBuilder; import static com.launchdarkly.sdk.server.ModelBuilders.segmentBuilder; import static com.launchdarkly.sdk.server.TestComponents.inMemoryDataStore; +import static com.launchdarkly.sdk.server.TestComponents.nullLogger; import static com.launchdarkly.sdk.server.TestComponents.sharedExecutor; import static com.launchdarkly.sdk.server.TestUtil.expectEvents; import static com.launchdarkly.testhelpers.ConcurrentHelpers.assertNoMoreValues; @@ -44,12 +45,13 @@ import static org.hamcrest.Matchers.is; @SuppressWarnings("javadoc") -public class DataSourceUpdatesImplTest extends EasyMockSupport { +public class DataSourceUpdatesImplTest { // Note that these tests must use the actual data model types for flags and segments, rather than the // TestItem type from DataStoreTestTypes, because the dependency behavior is based on the real data model. - private EventBroadcasterImpl flagChangeBroadcaster = - EventBroadcasterImpl.forFlagChangeEvents(TestComponents.sharedExecutor); + private final EventBroadcasterImpl flagChangeBroadcaster = + EventBroadcasterImpl.forFlagChangeEvents(TestComponents.sharedExecutor, nullLogger); + private final EasyMockSupport mocks = new EasyMockSupport(); private DataSourceUpdatesImpl makeInstance(DataStore store) { return makeInstance(store, null); @@ -59,7 +61,7 @@ private DataSourceUpdatesImpl makeInstance( DataStore store, EventBroadcasterImpl statusBroadcaster ) { - return new DataSourceUpdatesImpl(store, null, flagChangeBroadcaster, statusBroadcaster, sharedExecutor, null); + return new DataSourceUpdatesImpl(store, null, flagChangeBroadcaster, statusBroadcaster, sharedExecutor, null, nullLogger); } @Test @@ -336,7 +338,7 @@ public void dataSetIsPassedToDataStoreInCorrectOrder() throws Exception { // The logic for this is already tested in DataModelDependenciesTest, but here we are verifying // that DataSourceUpdatesImpl is actually using DataModelDependencies. Capture> captureData = Capture.newInstance(); - DataStore store = createStrictMock(DataStore.class); + DataStore store = mocks.createStrictMock(DataStore.class); store.init(EasyMock.capture(captureData)); replay(store); @@ -351,7 +353,7 @@ public void dataSetIsPassedToDataStoreInCorrectOrder() throws Exception { @Test public void updateStatusBroadcastsNewStatus() { EventBroadcasterImpl broadcaster = - EventBroadcasterImpl.forDataSourceStatus(sharedExecutor); + EventBroadcasterImpl.forDataSourceStatus(sharedExecutor, nullLogger); DataSourceUpdatesImpl updates = makeInstance(inMemoryDataStore(), broadcaster); BlockingQueue statuses = new LinkedBlockingQueue<>(); @@ -371,7 +373,7 @@ public void updateStatusBroadcastsNewStatus() { @Test public void updateStatusKeepsStateUnchangedIfStateWasInitializingAndNewStateIsInterrupted() { EventBroadcasterImpl broadcaster = - EventBroadcasterImpl.forDataSourceStatus(sharedExecutor); + EventBroadcasterImpl.forDataSourceStatus(sharedExecutor, nullLogger); DataSourceUpdatesImpl updates = makeInstance(inMemoryDataStore(), broadcaster); assertThat(updates.getLastStatus().getState(), is(State.INITIALIZING)); @@ -393,7 +395,7 @@ public void updateStatusKeepsStateUnchangedIfStateWasInitializingAndNewStateIsIn @Test public void updateStatusDoesNothingIfParametersHaveNoNewData() { EventBroadcasterImpl broadcaster = - EventBroadcasterImpl.forDataSourceStatus(sharedExecutor); + EventBroadcasterImpl.forDataSourceStatus(sharedExecutor, nullLogger); DataSourceUpdatesImpl updates = makeInstance(inMemoryDataStore(), broadcaster); BlockingQueue statuses = new LinkedBlockingQueue<>(); @@ -414,9 +416,10 @@ public void outageTimeoutLogging() throws Exception { inMemoryDataStore(), null, flagChangeBroadcaster, - EventBroadcasterImpl.forDataSourceStatus(sharedExecutor), + EventBroadcasterImpl.forDataSourceStatus(sharedExecutor, nullLogger), sharedExecutor, - outageTimeout + outageTimeout, + nullLogger ); updates.onOutageErrorLog = outageErrors::add; diff --git a/src/test/java/com/launchdarkly/sdk/server/DataStoreStatusProviderImplTest.java b/src/test/java/com/launchdarkly/sdk/server/DataStoreStatusProviderImplTest.java index bfcd7878c..de9208ea8 100644 --- a/src/test/java/com/launchdarkly/sdk/server/DataStoreStatusProviderImplTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/DataStoreStatusProviderImplTest.java @@ -24,9 +24,9 @@ import static org.hamcrest.Matchers.nullValue; @SuppressWarnings("javadoc") -public class DataStoreStatusProviderImplTest { +public class DataStoreStatusProviderImplTest extends BaseTest { private EventBroadcasterImpl broadcaster = - EventBroadcasterImpl.forDataStoreStatus(sharedExecutor); + EventBroadcasterImpl.forDataStoreStatus(sharedExecutor, testLogger); private MockDataStore store = new MockDataStore(); private DataStoreUpdatesImpl updates = new DataStoreUpdatesImpl(broadcaster); private DataStoreStatusProviderImpl statusProvider = new DataStoreStatusProviderImpl(store, updates); diff --git a/src/test/java/com/launchdarkly/sdk/server/DataStoreUpdatesImplTest.java b/src/test/java/com/launchdarkly/sdk/server/DataStoreUpdatesImplTest.java index 1ed1691ff..b2176ba80 100644 --- a/src/test/java/com/launchdarkly/sdk/server/DataStoreUpdatesImplTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/DataStoreUpdatesImplTest.java @@ -16,9 +16,9 @@ import static org.hamcrest.Matchers.equalTo; @SuppressWarnings("javadoc") -public class DataStoreUpdatesImplTest { +public class DataStoreUpdatesImplTest extends BaseTest { private EventBroadcasterImpl broadcaster = - EventBroadcasterImpl.forDataStoreStatus(sharedExecutor); + EventBroadcasterImpl.forDataStoreStatus(sharedExecutor, testLogger); private final DataStoreUpdatesImpl updates = new DataStoreUpdatesImpl(broadcaster); @Test diff --git a/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorDiagnosticsTest.java b/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorDiagnosticsTest.java index b018737f7..0b4669aea 100644 --- a/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorDiagnosticsTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorDiagnosticsTest.java @@ -133,7 +133,7 @@ public void periodicDiagnosticEventsAreSentAutomatically() throws Exception { EventsConfiguration eventsConfig = makeEventsConfigurationWithBriefDiagnosticInterval(es); try (DefaultEventProcessor ep = new DefaultEventProcessor(eventsConfig, sharedExecutor, Thread.MAX_PRIORITY, - diagnosticAccumulator, initEvent)) { + diagnosticAccumulator, initEvent, testLogger)) { // Ignore the initial diagnostic event es.awaitRequest(); @@ -180,7 +180,7 @@ public void diagnosticEventsStopAfter401Error() throws Exception { EventsConfiguration eventsConfig = makeEventsConfigurationWithBriefDiagnosticInterval(es); try (DefaultEventProcessor ep = new DefaultEventProcessor(eventsConfig, sharedExecutor, Thread.MAX_PRIORITY, - diagnosticAccumulator, initEvent)) { + diagnosticAccumulator, initEvent, testLogger)) { // Ignore the initial diagnostic event es.awaitRequest(); diff --git a/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorTest.java b/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorTest.java index 115467dae..14884e026 100644 --- a/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorTest.java @@ -110,7 +110,8 @@ public void eventsAreFlushedAutomatically() throws Exception { Duration.ofSeconds(5), null ); - try (DefaultEventProcessor ep = new DefaultEventProcessor(eventsConfig, sharedExecutor, Thread.MAX_PRIORITY, null, null)) { + try (DefaultEventProcessor ep = new DefaultEventProcessor(eventsConfig, sharedExecutor, Thread.MAX_PRIORITY, + null, null, testLogger)) { Event.Custom event1 = EventFactory.DEFAULT.newCustomEvent("event1", user, null, null); Event.Custom event2 = EventFactory.DEFAULT.newCustomEvent("event2", user, null, null); ep.sendEvent(event1); @@ -189,7 +190,7 @@ public void userKeysAreFlushedAutomatically() throws Exception { null ); try (DefaultEventProcessor ep = new DefaultEventProcessor(eventsConfig, sharedExecutor, Thread.MAX_PRIORITY, - null, null)) { + null, null, testLogger)) { Event.Custom event1 = EventFactory.DEFAULT.newCustomEvent("event1", user, null, null); Event.Custom event2 = EventFactory.DEFAULT.newCustomEvent("event2", user, null, null); ep.sendEvent(event1); diff --git a/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorTestBase.java b/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorTestBase.java index ddf6a7b5d..d4222c247 100644 --- a/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorTestBase.java +++ b/src/test/java/com/launchdarkly/sdk/server/DefaultEventProcessorTestBase.java @@ -37,7 +37,7 @@ import static org.junit.Assert.assertNotNull; @SuppressWarnings("javadoc") -public abstract class DefaultEventProcessorTestBase { +public abstract class DefaultEventProcessorTestBase extends BaseTest { public static final String SDK_KEY = "SDK_KEY"; public static final URI FAKE_URI = URI.create("http://fake"); public static final LDUser user = new LDUser.Builder("userkey").name("Red").build(); @@ -45,8 +45,6 @@ public abstract class DefaultEventProcessorTestBase { public static final LDValue userJson = LDValue.buildObject().put("key", "userkey").put("name", "Red").build(); public static final LDValue filteredUserJson = LDValue.buildObject().put("key", "userkey") .put("privateAttrs", LDValue.buildArray().add("name").build()).build(); - public static final LDConfig baseLDConfig = new LDConfig.Builder().diagnosticOptOut(true).build(); - public static final LDConfig diagLDConfig = new LDConfig.Builder().diagnosticOptOut(false).build(); // Note that all of these events depend on the fact that DefaultEventProcessor does a synchronous // flush when it is closed; in this case, it's closed implicitly by the try-with-resources block. @@ -55,17 +53,21 @@ public static EventProcessorBuilder baseConfig(MockEventSender es) { return sendEvents().eventSender(senderFactory(es)); } - public static DefaultEventProcessor makeEventProcessor(EventProcessorBuilder ec) { - return makeEventProcessor(ec, baseLDConfig); + public DefaultEventProcessor makeEventProcessor(EventProcessorBuilder ec) { + LDConfig config = new LDConfig.Builder().diagnosticOptOut(true) + .logging(Components.logging(testLogging)).build(); + return makeEventProcessor(ec, config); } - public static DefaultEventProcessor makeEventProcessor(EventProcessorBuilder ec, LDConfig config) { + public DefaultEventProcessor makeEventProcessor(EventProcessorBuilder ec, LDConfig config) { return (DefaultEventProcessor)ec.createEventProcessor(clientContext(SDK_KEY, config)); } - public static DefaultEventProcessor makeEventProcessor(EventProcessorBuilder ec, DiagnosticAccumulator diagnosticAccumulator) { + public DefaultEventProcessor makeEventProcessor(EventProcessorBuilder ec, DiagnosticAccumulator diagnosticAccumulator) { + LDConfig config = new LDConfig.Builder().diagnosticOptOut(false) + .logging(Components.logging(testLogging)).build(); return (DefaultEventProcessor)ec.createEventProcessor( - clientContext(SDK_KEY, diagLDConfig, diagnosticAccumulator)); + clientContext(SDK_KEY, config, diagnosticAccumulator)); } public static EventSenderFactory senderFactory(final MockEventSender es) { diff --git a/src/test/java/com/launchdarkly/sdk/server/DefaultEventSenderTest.java b/src/test/java/com/launchdarkly/sdk/server/DefaultEventSenderTest.java index c2f61279c..e66715069 100644 --- a/src/test/java/com/launchdarkly/sdk/server/DefaultEventSenderTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/DefaultEventSenderTest.java @@ -35,21 +35,22 @@ import static org.junit.Assert.assertTrue; @SuppressWarnings("javadoc") -public class DefaultEventSenderTest { +public class DefaultEventSenderTest extends BaseTest { private static final String SDK_KEY = "SDK_KEY"; private static final String FAKE_DATA = "some data"; private static final SimpleDateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); private static final Duration BRIEF_RETRY_DELAY = Duration.ofMillis(50); - private static EventSender makeEventSender() { + private EventSender makeEventSender() { return makeEventSender(LDConfig.DEFAULT); } - private static EventSender makeEventSender(LDConfig config) { + private EventSender makeEventSender(LDConfig config) { return new DefaultEventSender( clientContext(SDK_KEY, config).getHttp(), - BRIEF_RETRY_DELAY + BRIEF_RETRY_DELAY, + testLogger ); } @@ -66,7 +67,7 @@ public void factoryCreatesDefaultSenderWithDefaultRetryDelay() throws Exception @Test public void constructorUsesDefaultRetryDelayIfNotSpecified() throws Exception { ClientContext context = clientContext(SDK_KEY, LDConfig.DEFAULT); - try (EventSender es = new DefaultEventSender(context.getHttp(), null)) { + try (EventSender es = new DefaultEventSender(context.getHttp(), null, testLogger)) { assertThat(((DefaultEventSender)es).retryDelay, equalTo(DefaultEventSender.DEFAULT_RETRY_DELAY)); } } diff --git a/src/test/java/com/launchdarkly/sdk/server/DefaultFeatureRequestorTest.java b/src/test/java/com/launchdarkly/sdk/server/DefaultFeatureRequestorTest.java index 18a06eab1..5a29a42a2 100644 --- a/src/test/java/com/launchdarkly/sdk/server/DefaultFeatureRequestorTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/DefaultFeatureRequestorTest.java @@ -30,7 +30,7 @@ import static org.junit.Assert.fail; @SuppressWarnings("javadoc") -public class DefaultFeatureRequestorTest { +public class DefaultFeatureRequestorTest extends BaseTest { private static final String sdkKey = "sdk-key"; private static final String flag1Key = "flag1"; private static final FeatureFlag flag1 = flagBuilder(flag1Key).version(1000).build(); @@ -49,7 +49,7 @@ private DefaultFeatureRequestor makeRequestor(HttpServer server) { } private DefaultFeatureRequestor makeRequestor(HttpServer server, LDConfig config) { - return new DefaultFeatureRequestor(makeHttpConfig(config), server.getUri()); + return new DefaultFeatureRequestor(makeHttpConfig(config), server.getUri(), testLogger); } private HttpConfiguration makeHttpConfig(LDConfig config) { @@ -149,7 +149,7 @@ public void testSpecialHttpConfigurations() throws Exception { TestHttpUtil.testWithSpecialHttpConfigurations(handler, (targetUri, goodHttpConfig) -> { LDConfig config = new LDConfig.Builder().http(goodHttpConfig).build(); - try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(makeHttpConfig(config), targetUri)) { + try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(makeHttpConfig(config), targetUri, testLogger)) { try { FullDataSet data = r.getAllData(false); verifyExpectedData(data); @@ -161,7 +161,7 @@ public void testSpecialHttpConfigurations() throws Exception { (targetUri, badHttpConfig) -> { LDConfig config = new LDConfig.Builder().http(badHttpConfig).build(); - try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(makeHttpConfig(config), targetUri)) { + try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(makeHttpConfig(config), targetUri, testLogger)) { try { r.getAllData(false); fail("expected exception"); @@ -177,9 +177,9 @@ public void baseUriDoesNotNeedTrailingSlash() throws Exception { Handler resp = Handlers.bodyJson(allDataJson); try (HttpServer server = HttpServer.start(resp)) { - try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(makeHttpConfig(LDConfig.DEFAULT), server.getUri())) { + try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(makeHttpConfig(LDConfig.DEFAULT), server.getUri(), testLogger)) { FullDataSet data = r.getAllData(true); - + RequestInfo req = server.getRecorder().requireRequest(); assertEquals("/sdk/latest-all", req.getPath()); verifyHeaders(req); @@ -196,9 +196,9 @@ public void baseUriCanHaveContextPath() throws Exception { try (HttpServer server = HttpServer.start(resp)) { URI uri = server.getUri().resolve("/context/path"); - try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(makeHttpConfig(LDConfig.DEFAULT), uri)) { + try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(makeHttpConfig(LDConfig.DEFAULT), uri, testLogger)) { FullDataSet data = r.getAllData(true); - + RequestInfo req = server.getRecorder().requireRequest(); assertEquals("/context/path/sdk/latest-all", req.getPath()); verifyHeaders(req); diff --git a/src/test/java/com/launchdarkly/sdk/server/EvalResultTest.java b/src/test/java/com/launchdarkly/sdk/server/EvalResultTest.java index a3987486f..cc7808f45 100644 --- a/src/test/java/com/launchdarkly/sdk/server/EvalResultTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/EvalResultTest.java @@ -15,6 +15,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.sameInstance; +@SuppressWarnings("javadoc") public class EvalResultTest { private static final LDValue SOME_VALUE = LDValue.of("value"); private static final LDValue ARRAY_VALUE = LDValue.arrayOf(); diff --git a/src/test/java/com/launchdarkly/sdk/server/EvaluatorBigSegmentTest.java b/src/test/java/com/launchdarkly/sdk/server/EvaluatorBigSegmentTest.java index 88a587c0e..a4d6a8e8c 100644 --- a/src/test/java/com/launchdarkly/sdk/server/EvaluatorBigSegmentTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/EvaluatorBigSegmentTest.java @@ -9,7 +9,6 @@ import java.util.Collections; import static com.launchdarkly.sdk.server.Evaluator.makeBigSegmentRef; -import static com.launchdarkly.sdk.server.EvaluatorTestUtil.evaluatorBuilder; import static com.launchdarkly.sdk.server.EvaluatorTestUtil.expectNoPrerequisiteEvals; import static com.launchdarkly.sdk.server.ModelBuilders.booleanFlagWithClauses; import static com.launchdarkly.sdk.server.ModelBuilders.clauseMatchingSegment; @@ -25,7 +24,7 @@ import static org.junit.Assert.assertEquals; @SuppressWarnings("javadoc") -public class EvaluatorBigSegmentTest { +public class EvaluatorBigSegmentTest extends EvaluatorTestBase { private static final LDUser testUser = new LDUser("userkey"); @Test @@ -141,7 +140,7 @@ public void bigSegmentStateIsQueriedOnlyOncePerUserEvenIfFlagReferencesMultipleS expect(mockGetters.getSegment(segment2.getKey())).andReturn(segment2); replay(mockGetters); - Evaluator evaluator = new Evaluator(mockGetters); + Evaluator evaluator = new Evaluator(mockGetters, testLogger); EvalResult result = evaluator.evaluate(flag, testUser, expectNoPrerequisiteEvals()); assertEquals(LDValue.of(true), result.getValue()); assertEquals(BigSegmentsStatus.HEALTHY, result.getReason().getBigSegmentsStatus()); diff --git a/src/test/java/com/launchdarkly/sdk/server/EvaluatorClauseTest.java b/src/test/java/com/launchdarkly/sdk/server/EvaluatorClauseTest.java index 880f821d3..cbd6f37f0 100644 --- a/src/test/java/com/launchdarkly/sdk/server/EvaluatorClauseTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/EvaluatorClauseTest.java @@ -10,7 +10,6 @@ import static com.launchdarkly.sdk.EvaluationDetail.fromValue; import static com.launchdarkly.sdk.server.EvaluatorTestUtil.BASE_EVALUATOR; -import static com.launchdarkly.sdk.server.EvaluatorTestUtil.evaluatorBuilder; import static com.launchdarkly.sdk.server.EvaluatorTestUtil.expectNoPrerequisiteEvals; import static com.launchdarkly.sdk.server.ModelBuilders.booleanFlagWithClauses; import static com.launchdarkly.sdk.server.ModelBuilders.clause; @@ -24,7 +23,7 @@ import static org.junit.Assert.assertNotNull; @SuppressWarnings("javadoc") -public class EvaluatorClauseTest { +public class EvaluatorClauseTest extends EvaluatorTestBase { private static void assertMatch(Evaluator eval, DataModel.FeatureFlag flag, LDUser user, boolean expectMatch) { assertEquals(LDValue.of(expectMatch), eval.evaluate(flag, user, expectNoPrerequisiteEvals()).getValue()); } diff --git a/src/test/java/com/launchdarkly/sdk/server/EvaluatorSegmentMatchTest.java b/src/test/java/com/launchdarkly/sdk/server/EvaluatorSegmentMatchTest.java index e5f730f22..e7bf68631 100644 --- a/src/test/java/com/launchdarkly/sdk/server/EvaluatorSegmentMatchTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/EvaluatorSegmentMatchTest.java @@ -6,7 +6,6 @@ import org.junit.Test; -import static com.launchdarkly.sdk.server.EvaluatorTestUtil.evaluatorBuilder; import static com.launchdarkly.sdk.server.EvaluatorTestUtil.expectNoPrerequisiteEvals; import static com.launchdarkly.sdk.server.ModelBuilders.booleanFlagWithClauses; import static com.launchdarkly.sdk.server.ModelBuilders.clause; @@ -16,7 +15,7 @@ import static org.junit.Assert.assertTrue; @SuppressWarnings("javadoc") -public class EvaluatorSegmentMatchTest { +public class EvaluatorSegmentMatchTest extends EvaluatorTestBase { private int maxWeight = 100000; @@ -111,7 +110,7 @@ public void nonMatchingRuleWithMultipleClauses() { assertFalse(segmentMatchesUser(s, u)); } - private static boolean segmentMatchesUser(DataModel.Segment segment, LDUser user) { + private boolean segmentMatchesUser(DataModel.Segment segment, LDUser user) { DataModel.Clause clause = clause(null, DataModel.Operator.segmentMatch, LDValue.of(segment.getKey())); DataModel.FeatureFlag flag = booleanFlagWithClauses("flag", clause); Evaluator e = evaluatorBuilder().withStoredSegments(segment).build(); diff --git a/src/test/java/com/launchdarkly/sdk/server/EvaluatorTest.java b/src/test/java/com/launchdarkly/sdk/server/EvaluatorTest.java index 9ddd3411d..bee6e1f5f 100644 --- a/src/test/java/com/launchdarkly/sdk/server/EvaluatorTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/EvaluatorTest.java @@ -20,7 +20,6 @@ import static com.launchdarkly.sdk.EvaluationDetail.NO_VARIATION; import static com.launchdarkly.sdk.server.EvaluatorTestUtil.BASE_EVALUATOR; -import static com.launchdarkly.sdk.server.EvaluatorTestUtil.evaluatorBuilder; import static com.launchdarkly.sdk.server.EvaluatorTestUtil.expectNoPrerequisiteEvals; import static com.launchdarkly.sdk.server.ModelBuilders.clause; import static com.launchdarkly.sdk.server.ModelBuilders.flagBuilder; @@ -33,7 +32,7 @@ import static org.junit.Assert.assertSame; @SuppressWarnings("javadoc") -public class EvaluatorTest { +public class EvaluatorTest extends EvaluatorTestBase { private static final LDUser BASE_USER = new LDUser.Builder("x").build(); // These constants and flag builders define two kinds of flag: one with three variations-- allowing us to diff --git a/src/test/java/com/launchdarkly/sdk/server/EvaluatorTestBase.java b/src/test/java/com/launchdarkly/sdk/server/EvaluatorTestBase.java new file mode 100644 index 000000000..5d2a7025c --- /dev/null +++ b/src/test/java/com/launchdarkly/sdk/server/EvaluatorTestBase.java @@ -0,0 +1,10 @@ +package com.launchdarkly.sdk.server; + +import com.launchdarkly.sdk.server.EvaluatorTestUtil.EvaluatorBuilder; + +@SuppressWarnings("javadoc") +public class EvaluatorTestBase extends BaseTest { + public EvaluatorBuilder evaluatorBuilder() { + return new EvaluatorBuilder(testLogger); + } +} diff --git a/src/test/java/com/launchdarkly/sdk/server/EvaluatorTestUtil.java b/src/test/java/com/launchdarkly/sdk/server/EvaluatorTestUtil.java index 4a71fcd71..98c354bd2 100644 --- a/src/test/java/com/launchdarkly/sdk/server/EvaluatorTestUtil.java +++ b/src/test/java/com/launchdarkly/sdk/server/EvaluatorTestUtil.java @@ -1,5 +1,7 @@ package com.launchdarkly.sdk.server; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.Logs; import com.launchdarkly.sdk.LDUser; import com.launchdarkly.sdk.server.BigSegmentStoreWrapper.BigSegmentsQueryResult; import com.launchdarkly.sdk.server.DataModel.FeatureFlag; @@ -10,41 +12,46 @@ @SuppressWarnings("javadoc") public abstract class EvaluatorTestUtil { - public static Evaluator BASE_EVALUATOR = evaluatorBuilder().build(); + public static Evaluator BASE_EVALUATOR = new EvaluatorBuilder().build(); - public static EvaluatorBuilder evaluatorBuilder() { - return new EvaluatorBuilder(); - } - public static class EvaluatorBuilder { HashMap flagMap = new HashMap<>(); HashMap segmentMap = new HashMap<>(); HashMap bigSegmentMap = new HashMap<>(); + private final LDLogger logger; + EvaluatorBuilder() { + this(LDLogger.withAdapter(Logs.none(), "")); + } + + EvaluatorBuilder(LDLogger logger) { + this.logger = logger; + } + public Evaluator build() { return new Evaluator(new Evaluator.Getters() { - public DataModel.FeatureFlag getFlag(String key) { - if (!flagMap.containsKey(key)) { - throw new IllegalStateException("Evaluator unexpectedly tried to query flag: " + key); - } - return flagMap.get(key); + public DataModel.FeatureFlag getFlag(String key) { + if (!flagMap.containsKey(key)) { + throw new IllegalStateException("Evaluator unexpectedly tried to query flag: " + key); } + return flagMap.get(key); + } - public DataModel.Segment getSegment(String key) { - if (!segmentMap.containsKey(key)) { - throw new IllegalStateException("Evaluator unexpectedly tried to query segment: " + key); - } - return segmentMap.get(key); + public DataModel.Segment getSegment(String key) { + if (!segmentMap.containsKey(key)) { + throw new IllegalStateException("Evaluator unexpectedly tried to query segment: " + key); } + return segmentMap.get(key); + } - public BigSegmentsQueryResult getBigSegments(String key) { - if (!bigSegmentMap.containsKey(key)) { - throw new IllegalStateException("Evaluator unexpectedly tried to query Big Segment: " + key); - } - return bigSegmentMap.get(key); + public BigSegmentsQueryResult getBigSegments(String key) { + if (!bigSegmentMap.containsKey(key)) { + throw new IllegalStateException("Evaluator unexpectedly tried to query Big Segment: " + key); } - }); - } + return bigSegmentMap.get(key); + } + }, logger); + } public EvaluatorBuilder withStoredFlags(final DataModel.FeatureFlag... flags) { for (DataModel.FeatureFlag f: flags) { diff --git a/src/test/java/com/launchdarkly/sdk/server/EventBroadcasterImplTest.java b/src/test/java/com/launchdarkly/sdk/server/EventBroadcasterImplTest.java index 721cf87ac..bfe25b1cf 100644 --- a/src/test/java/com/launchdarkly/sdk/server/EventBroadcasterImplTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/EventBroadcasterImplTest.java @@ -10,9 +10,9 @@ import static org.hamcrest.Matchers.is; @SuppressWarnings("javadoc") -public class EventBroadcasterImplTest { +public class EventBroadcasterImplTest extends BaseTest { private EventBroadcasterImpl broadcaster = - new EventBroadcasterImpl<>(FakeListener::sendEvent, sharedExecutor); + new EventBroadcasterImpl<>(FakeListener::sendEvent, sharedExecutor, testLogger); @Test public void sendingEventWithNoListenersDoesNotCauseError() { @@ -21,7 +21,7 @@ public void sendingEventWithNoListenersDoesNotCauseError() { @Test public void sendingEventWithNoExecutorDoesNotCauseError() { - new EventBroadcasterImpl<>(FakeListener::sendEvent, null).broadcast(new FakeEvent()); + new EventBroadcasterImpl<>(FakeListener::sendEvent, null, testLogger).broadcast(new FakeEvent()); } @Test diff --git a/src/test/java/com/launchdarkly/sdk/server/FlagTrackerImplTest.java b/src/test/java/com/launchdarkly/sdk/server/FlagTrackerImplTest.java index 0b2d775c1..05a4f36e4 100644 --- a/src/test/java/com/launchdarkly/sdk/server/FlagTrackerImplTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/FlagTrackerImplTest.java @@ -21,13 +21,13 @@ import static org.hamcrest.Matchers.equalTo; @SuppressWarnings("javadoc") -public class FlagTrackerImplTest { +public class FlagTrackerImplTest extends BaseTest { @Test public void flagChangeListeners() throws Exception { String flagKey = "flagkey"; EventBroadcasterImpl broadcaster = - EventBroadcasterImpl.forFlagChangeEvents(TestComponents.sharedExecutor); + EventBroadcasterImpl.forFlagChangeEvents(TestComponents.sharedExecutor, testLogger); FlagTrackerImpl tracker = new FlagTrackerImpl(broadcaster, null); @@ -66,7 +66,7 @@ public void flagValueChangeListener() throws Exception { LDUser user = new LDUser("important-user"); LDUser otherUser = new LDUser("unimportant-user"); EventBroadcasterImpl broadcaster = - EventBroadcasterImpl.forFlagChangeEvents(TestComponents.sharedExecutor); + EventBroadcasterImpl.forFlagChangeEvents(TestComponents.sharedExecutor, testLogger); Map, LDValue> resultMap = new HashMap<>(); FlagTrackerImpl tracker = new FlagTrackerImpl(broadcaster, diff --git a/src/test/java/com/launchdarkly/sdk/server/LDClientBigSegmentsTest.java b/src/test/java/com/launchdarkly/sdk/server/LDClientBigSegmentsTest.java index 1a0de9e3e..743c850e0 100644 --- a/src/test/java/com/launchdarkly/sdk/server/LDClientBigSegmentsTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/LDClientBigSegmentsTest.java @@ -35,7 +35,7 @@ import java.util.Collections; @SuppressWarnings("javadoc") -public class LDClientBigSegmentsTest extends EasyMockSupport { +public class LDClientBigSegmentsTest extends BaseTest { private final LDUser user = new LDUser("userkey"); private final Segment bigSegment = segmentBuilder("segmentkey").unbounded(true).generation(1).build(); private final FeatureFlag flag = booleanFlagWithClauses("flagkey", clauseMatchingSegment(bigSegment)); @@ -43,6 +43,7 @@ public class LDClientBigSegmentsTest extends EasyMockSupport { private LDConfig.Builder configBuilder; private BigSegmentStore storeMock; private BigSegmentStoreFactory storeFactoryMock; + private final EasyMockSupport mocks = new EasyMockSupport(); @Before public void setup() { @@ -50,21 +51,18 @@ public void setup() { upsertFlag(dataStore, flag); upsertSegment(dataStore, bigSegment); - storeMock = niceMock(BigSegmentStore.class); - storeFactoryMock = strictMock(BigSegmentStoreFactory.class); + storeMock = mocks.niceMock(BigSegmentStore.class); + storeFactoryMock = mocks.strictMock(BigSegmentStoreFactory.class); expect(storeFactoryMock.createBigSegmentStore(isA(ClientContext.class))).andReturn(storeMock); - configBuilder = new LDConfig.Builder() - .dataSource(Components.externalUpdatesOnly()) - .dataStore(specificDataStore(dataStore)) - .events(Components.noEvents()); + configBuilder = baseConfig().dataStore(specificDataStore(dataStore)); } @Test public void userNotFound() throws Exception { expect(storeMock.getMetadata()).andAnswer(() -> new StoreMetadata(System.currentTimeMillis())).anyTimes(); expect(storeMock.getMembership(hashForUserKey(user.getKey()))).andReturn(null); - replayAll(); + mocks.replayAll(); LDConfig config = configBuilder.bigSegments(Components.bigSegments(storeFactoryMock)).build(); try (LDClient client = new LDClient("SDK_KEY", config)) { @@ -79,7 +77,7 @@ public void userFound() throws Exception { Membership membership = createMembershipFromSegmentRefs(Collections.singleton(makeBigSegmentRef(bigSegment)), null); expect(storeMock.getMetadata()).andAnswer(() -> new StoreMetadata(System.currentTimeMillis())).anyTimes(); expect(storeMock.getMembership(hashForUserKey(user.getKey()))).andReturn(membership); - replayAll(); + mocks.replayAll(); LDConfig config = configBuilder.bigSegments(Components.bigSegments(storeFactoryMock)).build(); try (LDClient client = new LDClient("SDK_KEY", config)) { @@ -93,7 +91,7 @@ public void userFound() throws Exception { public void storeError() throws Exception { expect(storeMock.getMetadata()).andAnswer(() -> new StoreMetadata(System.currentTimeMillis())).anyTimes(); expect(storeMock.getMembership(hashForUserKey(user.getKey()))).andThrow(new RuntimeException("sorry")); - replayAll(); + mocks.replayAll(); LDConfig config = configBuilder.bigSegments(Components.bigSegments(storeFactoryMock)).build(); try (LDClient client = new LDClient("SDK_KEY", config)) { diff --git a/src/test/java/com/launchdarkly/sdk/server/LDClientEndToEndTest.java b/src/test/java/com/launchdarkly/sdk/server/LDClientEndToEndTest.java index ee442b860..6045ea069 100644 --- a/src/test/java/com/launchdarkly/sdk/server/LDClientEndToEndTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/LDClientEndToEndTest.java @@ -30,7 +30,7 @@ import static org.junit.Assert.assertTrue; @SuppressWarnings("javadoc") -public class LDClientEndToEndTest { +public class LDClientEndToEndTest extends BaseTest { private static final Gson gson = new Gson(); private static final String sdkKey = "sdk-key"; private static final String flagKey = "flag1"; @@ -61,7 +61,7 @@ private static Handler makeServiceUnavailableResponse() { @Test public void clientStartsInPollingMode() throws Exception { try (HttpServer server = HttpServer.start(makePollingSuccessResponse())) { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.pollingDataSource().baseURI(server.getUri())) .events(noEvents()) .build(); @@ -81,7 +81,7 @@ public void clientStartsInPollingModeAfterRecoverableError() throws Exception { ); try (HttpServer server = HttpServer.start(errorThenSuccess)) { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.pollingDataSourceInternal() .pollIntervalWithNoMinimum(Duration.ofMillis(5)) // use small interval because we expect it to retry .baseURI(server.getUri())) @@ -98,7 +98,7 @@ public void clientStartsInPollingModeAfterRecoverableError() throws Exception { @Test public void clientFailsInPollingModeWith401Error() throws Exception { try (HttpServer server = HttpServer.start(makeInvalidSdkKeyResponse())) { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.pollingDataSourceInternal() .pollIntervalWithNoMinimum(Duration.ofMillis(5)) // use small interval so we'll know if it does not stop permanently .baseURI(server.getUri())) @@ -120,7 +120,7 @@ public void testPollingModeSpecialHttpConfigurations() throws Exception { testWithSpecialHttpConfigurations( makePollingSuccessResponse(), (serverUri, httpConfig) -> - new LDConfig.Builder() + baseConfig() .dataSource(Components.pollingDataSource().baseURI(serverUri)) .events(noEvents()) .http(httpConfig)); @@ -129,7 +129,7 @@ public void testPollingModeSpecialHttpConfigurations() throws Exception { @Test public void clientStartsInStreamingMode() throws Exception { try (HttpServer server = HttpServer.start(makeStreamingSuccessResponse())) { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.streamingDataSource().baseURI(server.getUri())) .events(noEvents()) .build(); @@ -149,7 +149,7 @@ public void clientStartsInStreamingModeAfterRecoverableError() throws Exception ); try (HttpServer server = HttpServer.start(errorThenStream)) { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.streamingDataSource().baseURI(server.getUri()).initialReconnectDelay(Duration.ZERO)) // use zero reconnect delay so we'll know if it does not stop permanently .events(noEvents()) @@ -169,7 +169,7 @@ public void clientStartsInStreamingModeAfterRecoverableError() throws Exception @Test public void clientFailsInStreamingModeWith401Error() throws Exception { try (HttpServer server = HttpServer.start(makeInvalidSdkKeyResponse())) { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.streamingDataSource().baseURI(server.getUri()).initialReconnectDelay(Duration.ZERO)) // use zero reconnect delay so we'll know if it does not stop permanently .events(noEvents()) @@ -203,7 +203,7 @@ public void testStreamingModeSpecialHttpConfigurations() throws Exception { testWithSpecialHttpConfigurations( makeStreamingSuccessResponse(), (serverUri, httpConfig) -> - new LDConfig.Builder() + baseConfig() .dataSource(Components.streamingDataSource().baseURI(serverUri)) .events(noEvents()) .http(httpConfig)); @@ -214,7 +214,7 @@ public void clientSendsAnalyticsEvent() throws Exception { Handler resp = Handlers.status(202); try (HttpServer server = HttpServer.start(resp)) { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(externalUpdatesOnly()) .events(Components.sendEvents().baseURI(server.getUri())) .diagnosticOptOut(true) @@ -235,7 +235,7 @@ public void clientSendsDiagnosticEvent() throws Exception { Handler resp = Handlers.status(202); try (HttpServer server = HttpServer.start(resp)) { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(externalUpdatesOnly()) .events(Components.sendEvents().baseURI(server.getUri())) .build(); diff --git a/src/test/java/com/launchdarkly/sdk/server/LDClientEvaluationTest.java b/src/test/java/com/launchdarkly/sdk/server/LDClientEvaluationTest.java index c21d4ea9e..a3c732a90 100644 --- a/src/test/java/com/launchdarkly/sdk/server/LDClientEvaluationTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/LDClientEvaluationTest.java @@ -40,14 +40,14 @@ import static org.junit.Assert.assertTrue; @SuppressWarnings("javadoc") -public class LDClientEvaluationTest { +public class LDClientEvaluationTest extends BaseTest { private static final LDUser user = new LDUser("userkey"); private static final LDUser userWithNullKey = new LDUser.Builder((String)null).build(); private static final Gson gson = new Gson(); private DataStore dataStore = initedDataStore(); - private LDConfig config = new LDConfig.Builder() + private LDConfig config = baseConfig() .dataStore(specificDataStore(dataStore)) .events(Components.noEvents()) .dataSource(Components.externalUpdatesOnly()) @@ -306,9 +306,8 @@ public void deletedFlagPlaceholderIsTreatedAsUnknownFlag() { @Test public void appropriateErrorIfClientNotInitialized() throws Exception { DataStore badDataStore = new InMemoryDataStore(); - LDConfig badConfig = new LDConfig.Builder() + LDConfig badConfig = baseConfig() .dataStore(specificDataStore(badDataStore)) - .events(Components.noEvents()) .dataSource(specificDataSource(failedDataSource())) .startWait(Duration.ZERO) .build(); @@ -358,10 +357,8 @@ public void appropriateErrorIfValueWrongType() throws Exception { public void appropriateErrorForUnexpectedExceptionFromDataStore() throws Exception { RuntimeException exception = new RuntimeException("sorry"); DataStore badDataStore = dataStoreThatThrowsException(exception); - LDConfig badConfig = new LDConfig.Builder() + LDConfig badConfig = baseConfig() .dataStore(specificDataStore(badDataStore)) - .events(Components.noEvents()) - .dataSource(Components.externalUpdatesOnly()) .build(); try (LDClientInterface badClient = new LDClient("SDK_KEY", badConfig)) { EvaluationDetail expectedResult = EvaluationDetail.fromValue(false, NO_VARIATION, @@ -390,9 +387,8 @@ public void canEvaluateWithNonNullButEmptyUserKey() throws Exception { @Test public void evaluationUsesStoreIfStoreIsInitializedButClientIsNot() throws Exception { upsertFlag(dataStore, flagWithValue("key", LDValue.of("value"))); - LDConfig customConfig = new LDConfig.Builder() + LDConfig customConfig = baseConfig() .dataStore(specificDataStore(dataStore)) - .events(Components.noEvents()) .dataSource(specificDataSource(failedDataSource())) .startWait(Duration.ZERO) .build(); @@ -590,10 +586,8 @@ public void allFlagsStateReturnsEmptyStateForNullUserKey() throws Exception { @Test public void allFlagsStateReturnsEmptyStateIfDataStoreThrowsException() throws Exception { - LDConfig customConfig = new LDConfig.Builder() + LDConfig customConfig = baseConfig() .dataStore(specificDataStore(TestComponents.dataStoreThatThrowsException(new RuntimeException("sorry")))) - .events(Components.noEvents()) - .dataSource(Components.externalUpdatesOnly()) .startWait(Duration.ZERO) .build(); @@ -619,9 +613,8 @@ public void allFlagsStateUsesNullValueForFlagIfEvaluationThrowsException() throw @Test public void allFlagsStateUsesStoreDataIfStoreIsInitializedButClientIsNot() throws Exception { upsertFlag(dataStore, flagWithValue("key", LDValue.of("value"))); - LDConfig customConfig = new LDConfig.Builder() + LDConfig customConfig = baseConfig() .dataStore(specificDataStore(dataStore)) - .events(Components.noEvents()) .dataSource(specificDataSource(failedDataSource())) .startWait(Duration.ZERO) .build(); @@ -637,8 +630,7 @@ public void allFlagsStateUsesStoreDataIfStoreIsInitializedButClientIsNot() throw @Test public void allFlagsStateReturnsEmptyStateIfClientAndStoreAreNotInitialized() throws Exception { - LDConfig customConfig = new LDConfig.Builder() - .events(Components.noEvents()) + LDConfig customConfig = baseConfig() .dataSource(specificDataSource(failedDataSource())) .startWait(Duration.ZERO) .build(); diff --git a/src/test/java/com/launchdarkly/sdk/server/LDClientEventTest.java b/src/test/java/com/launchdarkly/sdk/server/LDClientEventTest.java index 6f992ff59..8f50f9c47 100644 --- a/src/test/java/com/launchdarkly/sdk/server/LDClientEventTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/LDClientEventTest.java @@ -32,14 +32,14 @@ import static org.junit.Assert.assertTrue; @SuppressWarnings("javadoc") -public class LDClientEventTest { +public class LDClientEventTest extends BaseTest { private static final LDUser user = new LDUser("userkey"); private static final LDUser userWithNullKey = new LDUser.Builder((String)null).build(); private static final LDUser userWithEmptyKey = new LDUser.Builder("").build(); private DataStore dataStore = initedDataStore(); private TestComponents.TestEventProcessor eventSink = new TestComponents.TestEventProcessor(); - private LDConfig config = new LDConfig.Builder() + private LDConfig config = baseConfig() .dataStore(specificDataStore(dataStore)) .events(specificEventProcessor(eventSink)) .dataSource(Components.externalUpdatesOnly()) @@ -519,7 +519,7 @@ public void canFlush() { @Test public void identifyWithEventsDisabledDoesNotCauseError() throws Exception { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .events(Components.noEvents()) .dataSource(Components.externalUpdatesOnly()) .build(); @@ -530,7 +530,7 @@ public void identifyWithEventsDisabledDoesNotCauseError() throws Exception { @Test public void trackWithEventsDisabledDoesNotCauseError() throws Exception { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .events(Components.noEvents()) .dataSource(Components.externalUpdatesOnly()) .build(); @@ -541,7 +541,7 @@ public void trackWithEventsDisabledDoesNotCauseError() throws Exception { @Test public void flushWithEventsDisabledDoesNotCauseError() throws Exception { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .events(Components.noEvents()) .dataSource(Components.externalUpdatesOnly()) .build(); diff --git a/src/test/java/com/launchdarkly/sdk/server/LDClientExternalUpdatesOnlyTest.java b/src/test/java/com/launchdarkly/sdk/server/LDClientExternalUpdatesOnlyTest.java index 3e086d798..e3e1658aa 100644 --- a/src/test/java/com/launchdarkly/sdk/server/LDClientExternalUpdatesOnlyTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/LDClientExternalUpdatesOnlyTest.java @@ -17,10 +17,10 @@ import static org.junit.Assert.assertTrue; @SuppressWarnings("javadoc") -public class LDClientExternalUpdatesOnlyTest { +public class LDClientExternalUpdatesOnlyTest extends BaseTest { @Test public void externalUpdatesOnlyClientHasNullDataSource() throws Exception { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.externalUpdatesOnly()) .build(); try (LDClient client = new LDClient("SDK_KEY", config)) { @@ -28,19 +28,9 @@ public void externalUpdatesOnlyClientHasNullDataSource() throws Exception { } } - @Test - public void externalUpdatesOnlyClientHasDefaultEventProcessor() throws Exception { - LDConfig config = new LDConfig.Builder() - .dataSource(Components.externalUpdatesOnly()) - .build(); - try (LDClient client = new LDClient("SDK_KEY", config)) { - assertEquals(DefaultEventProcessor.class, client.eventProcessor.getClass()); - } - } - @Test public void externalUpdatesOnlyClientIsInitialized() throws Exception { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.externalUpdatesOnly()) .build(); try (LDClient client = new LDClient("SDK_KEY", config)) { @@ -53,7 +43,7 @@ public void externalUpdatesOnlyClientIsInitialized() throws Exception { @Test public void externalUpdatesOnlyClientGetsFlagFromDataStore() throws IOException { DataStore testDataStore = initedDataStore(); - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.externalUpdatesOnly()) .dataStore(specificDataStore(testDataStore)) .build(); diff --git a/src/test/java/com/launchdarkly/sdk/server/LDClientListenersTest.java b/src/test/java/com/launchdarkly/sdk/server/LDClientListenersTest.java index 6a776ea26..9a13e2232 100644 --- a/src/test/java/com/launchdarkly/sdk/server/LDClientListenersTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/LDClientListenersTest.java @@ -53,15 +53,15 @@ * together correctly so that they work from an application's point of view. */ @SuppressWarnings("javadoc") -public class LDClientListenersTest extends EasyMockSupport { +public class LDClientListenersTest extends BaseTest { private final static String SDK_KEY = "SDK_KEY"; - + @Test public void clientSendsFlagChangeEvents() throws Exception { String flagKey = "flagkey"; TestData testData = TestData.dataSource(); testData.update(testData.flag(flagKey).on(true)); - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(testData) .events(Components.noEvents()) .build(); @@ -106,7 +106,7 @@ public void clientSendsFlagValueChangeEvents() throws Exception { TestData testData = TestData.dataSource(); testData.update(testData.flag(flagKey).on(false)); - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(testData) .events(Components.noEvents()) .build(); @@ -148,7 +148,7 @@ public void clientSendsFlagValueChangeEvents() throws Exception { @Test public void dataSourceStatusProviderReturnsLatestStatus() throws Exception { TestData testData = TestData.dataSource(); - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(testData) .events(Components.noEvents()) .build(); @@ -174,7 +174,7 @@ public void dataSourceStatusProviderReturnsLatestStatus() throws Exception { @Test public void dataSourceStatusProviderSendsStatusUpdates() throws Exception { TestData testData = TestData.dataSource(); - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(testData) .events(Components.noEvents()) .build(); @@ -196,7 +196,7 @@ public void dataSourceStatusProviderSendsStatusUpdates() throws Exception { @Test public void dataStoreStatusMonitoringIsDisabledForInMemoryStore() throws Exception { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.externalUpdatesOnly()) .events(Components.noEvents()) .build(); @@ -207,7 +207,7 @@ public void dataStoreStatusMonitoringIsDisabledForInMemoryStore() throws Excepti @Test public void dataStoreStatusMonitoringIsEnabledForPersistentStore() throws Exception { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.externalUpdatesOnly()) .dataStore( Components.persistentDataStore(specificPersistentDataStore(new MockPersistentDataStore())) @@ -224,7 +224,7 @@ public void dataStoreStatusProviderReturnsLatestStatus() throws Exception { DataStoreFactory underlyingStoreFactory = Components.persistentDataStore( specificPersistentDataStore(new MockPersistentDataStore())); DataStoreFactoryThatExposesUpdater factoryWithUpdater = new DataStoreFactoryThatExposesUpdater(underlyingStoreFactory); - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.externalUpdatesOnly()) .dataStore(factoryWithUpdater) .events(Components.noEvents()) @@ -243,7 +243,7 @@ public void dataStoreStatusProviderSendsStatusUpdates() throws Exception { DataStoreFactory underlyingStoreFactory = Components.persistentDataStore( specificPersistentDataStore(new MockPersistentDataStore())); DataStoreFactoryThatExposesUpdater factoryWithUpdater = new DataStoreFactoryThatExposesUpdater(underlyingStoreFactory); - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(Components.externalUpdatesOnly()) .dataStore(factoryWithUpdater) .events(Components.noEvents()) @@ -266,7 +266,7 @@ public void eventsAreDispatchedOnTaskThread() throws Exception { TestData testData = TestData.dataSource(); testData.update(testData.flag("flagkey").on(true)); - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .dataSource(testData) .events(Components.noEvents()) .threadPriority(desiredPriority) @@ -288,10 +288,7 @@ public void eventsAreDispatchedOnTaskThread() throws Exception { @Test public void bigSegmentStoreStatusReturnsUnavailableStatusWhenNotConfigured() throws Exception { - LDConfig config = new LDConfig.Builder() - .dataSource(Components.externalUpdatesOnly()) - .events(Components.noEvents()) - .build(); + LDConfig config = baseConfig().build(); try (LDClient client = new LDClient(SDK_KEY, config)) { BigSegmentStoreStatusProvider.Status status = client.getBigSegmentStoreStatusProvider().getStatus(); assertFalse(status.isAvailable()); @@ -301,8 +298,9 @@ public void bigSegmentStoreStatusReturnsUnavailableStatusWhenNotConfigured() thr @Test public void bigSegmentStoreStatusProviderSendsStatusUpdates() throws Exception { + EasyMockSupport mocks = new EasyMockSupport(); AtomicBoolean storeAvailable = new AtomicBoolean(true); - BigSegmentStore storeMock = niceMock(BigSegmentStore.class); + BigSegmentStore storeMock = mocks.niceMock(BigSegmentStore.class); expect(storeMock.getMetadata()).andAnswer(() -> { if (storeAvailable.get()) { return new BigSegmentStoreTypes.StoreMetadata(System.currentTimeMillis()); @@ -310,17 +308,15 @@ public void bigSegmentStoreStatusProviderSendsStatusUpdates() throws Exception { throw new RuntimeException("sorry"); }).anyTimes(); - BigSegmentStoreFactory storeFactoryMock = strictMock(BigSegmentStoreFactory.class); + BigSegmentStoreFactory storeFactoryMock = mocks.strictMock(BigSegmentStoreFactory.class); expect(storeFactoryMock.createBigSegmentStore(isA(ClientContext.class))).andReturn(storeMock); replay(storeFactoryMock, storeMock); - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .bigSegments( Components.bigSegments(storeFactoryMock).statusPollInterval(Duration.ofMillis(10)) ) - .dataSource(Components.externalUpdatesOnly()) - .events(Components.noEvents()) .build(); try (LDClient client = new LDClient(SDK_KEY, config)) { diff --git a/src/test/java/com/launchdarkly/sdk/server/LDClientOfflineTest.java b/src/test/java/com/launchdarkly/sdk/server/LDClientOfflineTest.java index 80a7212d5..41679fe15 100644 --- a/src/test/java/com/launchdarkly/sdk/server/LDClientOfflineTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/LDClientOfflineTest.java @@ -18,12 +18,12 @@ import static org.junit.Assert.assertTrue; @SuppressWarnings("javadoc") -public class LDClientOfflineTest { +public class LDClientOfflineTest extends BaseTest { private static final LDUser user = new LDUser("user"); @Test public void offlineClientHasNullDataSource() throws IOException { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .offline(true) .build(); try (LDClient client = new LDClient("SDK_KEY", config)) { @@ -33,7 +33,7 @@ public void offlineClientHasNullDataSource() throws IOException { @Test public void offlineClientHasNullEventProcessor() throws IOException { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .offline(true) .build(); try (LDClient client = new LDClient("SDK_KEY", config)) { @@ -43,7 +43,7 @@ public void offlineClientHasNullEventProcessor() throws IOException { @Test public void offlineClientIsInitialized() throws IOException { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .offline(true) .build(); try (LDClient client = new LDClient("SDK_KEY", config)) { @@ -55,7 +55,7 @@ public void offlineClientIsInitialized() throws IOException { @Test public void offlineClientReturnsDefaultValue() throws IOException { - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .offline(true) .build(); try (LDClient client = new LDClient("SDK_KEY", config)) { @@ -66,7 +66,7 @@ public void offlineClientReturnsDefaultValue() throws IOException { @Test public void offlineClientGetsFlagsStateFromDataStore() throws IOException { DataStore testDataStore = initedDataStore(); - LDConfig config = new LDConfig.Builder() + LDConfig config = baseConfig() .offline(true) .dataStore(specificDataStore(testDataStore)) .build(); diff --git a/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java b/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java index d7ac2eec3..2f33adeef 100644 --- a/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java @@ -52,20 +52,21 @@ * See also LDClientEvaluationTest, etc. This file contains mostly tests for the startup logic. */ @SuppressWarnings("javadoc") -public class LDClientTest extends EasyMockSupport { +public class LDClientTest extends BaseTest { private final static String SDK_KEY = "SDK_KEY"; private DataSource dataSource; private EventProcessor eventProcessor; private Future initFuture; private LDClientInterface client; + private final EasyMockSupport mocks = new EasyMockSupport(); @SuppressWarnings("unchecked") @Before public void before() { - dataSource = createStrictMock(DataSource.class); - eventProcessor = createStrictMock(EventProcessor.class); - initFuture = createStrictMock(Future.class); + dataSource = mocks.createStrictMock(DataSource.class); + eventProcessor = mocks.createStrictMock(EventProcessor.class); + initFuture = mocks.createStrictMock(Future.class); } @Test @@ -109,7 +110,7 @@ public void constructorAllowsSdkKeyToBeEmpty() throws Exception { // It may seem counter-intuitive to allow this, but if someone is using the SDK in offline // mode, or with a file data source or a test fixture, they may reasonably assume that it's // OK to pass an empty string since the key won't actually be used. - try (LDClient client = new LDClient(SDK_KEY + "")) {} + try (LDClient client = new LDClient("", baseConfig().build())) {} } @Test @@ -126,6 +127,7 @@ public void clientHasDefaultEventProcessorWithDefaultConfig() throws Exception { LDConfig config = new LDConfig.Builder() .dataSource(Components.externalUpdatesOnly()) .diagnosticOptOut(true) + .logging(Components.logging(testLogging)) .build(); try (LDClient client = new LDClient("SDK_KEY", config)) { assertEquals(DefaultEventProcessor.class, client.eventProcessor.getClass()); @@ -138,6 +140,7 @@ public void clientHasDefaultEventProcessorWithSendEvents() throws Exception { .dataSource(Components.externalUpdatesOnly()) .events(Components.sendEvents()) .diagnosticOptOut(true) + .logging(Components.logging(testLogging)) .build(); try (LDClient client = new LDClient("SDK_KEY", config)) { assertEquals(DefaultEventProcessor.class, client.eventProcessor.getClass()); @@ -149,6 +152,7 @@ public void clientHasNullEventProcessorWithNoEvents() throws Exception { LDConfig config = new LDConfig.Builder() .dataSource(Components.externalUpdatesOnly()) .events(Components.noEvents()) + .logging(Components.logging(testLogging)) .build(); try (LDClient client = new LDClient("SDK_KEY", config)) { assertEquals(ComponentsImpl.NullEventProcessor.class, client.eventProcessor.getClass()); @@ -159,9 +163,11 @@ public void clientHasNullEventProcessorWithNoEvents() throws Exception { public void canSetCustomEventsEndpoint() throws Exception { URI eu = URI.create("http://fake"); LDConfig config = new LDConfig.Builder() + .dataSource(Components.externalUpdatesOnly()) .serviceEndpoints(Components.serviceEndpoints().events(eu)) .events(Components.sendEvents()) .diagnosticOptOut(true) + .logging(Components.logging(testLogging)) .build(); try (LDClient client = new LDClient(SDK_KEY, config)) { assertEquals(eu, ((DefaultEventProcessor) client.eventProcessor).dispatcher.eventsConfig.eventsUri); @@ -171,8 +177,10 @@ public void canSetCustomEventsEndpoint() throws Exception { @Test public void streamingClientHasStreamProcessor() throws Exception { LDConfig config = new LDConfig.Builder() - .dataSource(Components.streamingDataSource().baseURI(URI.create("http://fake"))) + .dataSource(Components.streamingDataSource()) + .serviceEndpoints(Components.serviceEndpoints().streaming("http://fake")) .events(Components.noEvents()) + .logging(Components.logging(testLogging)) .startWait(Duration.ZERO) .build(); try (LDClient client = new LDClient(SDK_KEY, config)) { @@ -186,6 +194,7 @@ public void canSetCustomStreamingEndpoint() throws Exception { LDConfig config = new LDConfig.Builder() .serviceEndpoints(Components.serviceEndpoints().streaming(su)) .events(Components.noEvents()) + .logging(Components.logging(testLogging)) .startWait(Duration.ZERO) .build(); try (LDClient client = new LDClient(SDK_KEY, config)) { @@ -196,8 +205,10 @@ public void canSetCustomStreamingEndpoint() throws Exception { @Test public void pollingClientHasPollingProcessor() throws IOException { LDConfig config = new LDConfig.Builder() - .dataSource(Components.pollingDataSource().baseURI(URI.create("http://fake"))) + .dataSource(Components.pollingDataSource()) + .serviceEndpoints(Components.serviceEndpoints().polling("http://fake")) .events(Components.noEvents()) + .logging(Components.logging(testLogging)) .startWait(Duration.ZERO) .build(); try (LDClient client = new LDClient(SDK_KEY, config)) { @@ -212,6 +223,7 @@ public void canSetCustomPollingEndpoint() throws Exception { .dataSource(Components.pollingDataSource()) .serviceEndpoints(Components.serviceEndpoints().polling(pu)) .events(Components.noEvents()) + .logging(Components.logging(testLogging)) .startWait(Duration.ZERO) .build(); try (LDClient client = new LDClient(SDK_KEY, config)) { @@ -221,11 +233,13 @@ public void canSetCustomPollingEndpoint() throws Exception { @Test public void sameDiagnosticAccumulatorPassedToFactoriesWhenSupported() throws IOException { - DataSourceFactory mockDataSourceFactory = createStrictMock(DataSourceFactory.class); + DataSourceFactory mockDataSourceFactory = mocks.createStrictMock(DataSourceFactory.class); LDConfig config = new LDConfig.Builder() .dataSource(mockDataSourceFactory) - .events(Components.sendEvents().baseURI(URI.create("fake-host"))) // event processor will try to send a diagnostic event here + .serviceEndpoints(Components.serviceEndpoints().events("fake-host")) // event processor will try to send a diagnostic event here + .events(Components.sendEvents()) + .logging(Components.logging(testLogging)) .startWait(Duration.ZERO) .build(); @@ -233,10 +247,10 @@ public void sameDiagnosticAccumulatorPassedToFactoriesWhenSupported() throws IOE expect(mockDataSourceFactory.createDataSource(capture(capturedDataSourceContext), isA(DataSourceUpdates.class))).andReturn(failedDataSource()); - replayAll(); + mocks.replayAll(); try (LDClient client = new LDClient(SDK_KEY, config)) { - verifyAll(); + mocks.verifyAll(); DiagnosticAccumulator acc = ((DefaultEventProcessor)client.eventProcessor).dispatcher.diagnosticAccumulator; assertNotNull(acc); assertSame(acc, ClientContextImpl.get(capturedDataSourceContext.getValue()).diagnosticAccumulator); @@ -245,11 +259,12 @@ public void sameDiagnosticAccumulatorPassedToFactoriesWhenSupported() throws IOE @Test public void nullDiagnosticAccumulatorPassedToFactoriesWhenOptedOut() throws IOException { - DataSourceFactory mockDataSourceFactory = createStrictMock(DataSourceFactory.class); + DataSourceFactory mockDataSourceFactory = mocks.createStrictMock(DataSourceFactory.class); LDConfig config = new LDConfig.Builder() .dataSource(mockDataSourceFactory) .diagnosticOptOut(true) + .logging(Components.logging(testLogging)) .startWait(Duration.ZERO) .build(); @@ -257,10 +272,10 @@ public void nullDiagnosticAccumulatorPassedToFactoriesWhenOptedOut() throws IOEx expect(mockDataSourceFactory.createDataSource(capture(capturedDataSourceContext), isA(DataSourceUpdates.class))).andReturn(failedDataSource()); - replayAll(); + mocks.replayAll(); try (LDClient client = new LDClient(SDK_KEY, config)) { - verifyAll(); + mocks.verifyAll(); assertNull(((DefaultEventProcessor)client.eventProcessor).dispatcher.diagnosticAccumulator); assertNull(ClientContextImpl.get(capturedDataSourceContext.getValue()).diagnosticAccumulator); } @@ -268,15 +283,16 @@ public void nullDiagnosticAccumulatorPassedToFactoriesWhenOptedOut() throws IOEx @Test public void nullDiagnosticAccumulatorPassedToUpdateFactoryWhenEventProcessorDoesNotSupportDiagnostics() throws IOException { - EventProcessor mockEventProcessor = createStrictMock(EventProcessor.class); + EventProcessor mockEventProcessor = mocks.createStrictMock(EventProcessor.class); mockEventProcessor.close(); EasyMock.expectLastCall().anyTimes(); - EventProcessorFactory mockEventProcessorFactory = createStrictMock(EventProcessorFactory.class); - DataSourceFactory mockDataSourceFactory = createStrictMock(DataSourceFactory.class); + EventProcessorFactory mockEventProcessorFactory = mocks.createStrictMock(EventProcessorFactory.class); + DataSourceFactory mockDataSourceFactory = mocks.createStrictMock(DataSourceFactory.class); LDConfig config = new LDConfig.Builder() .events(mockEventProcessorFactory) .dataSource(mockDataSourceFactory) + .logging(Components.logging(testLogging)) .startWait(Duration.ZERO) .build(); @@ -286,10 +302,10 @@ public void nullDiagnosticAccumulatorPassedToUpdateFactoryWhenEventProcessorDoes expect(mockDataSourceFactory.createDataSource(capture(capturedDataSourceContext), isA(DataSourceUpdates.class))).andReturn(failedDataSource()); - replayAll(); + mocks.replayAll(); try (LDClient client = new LDClient(SDK_KEY, config)) { - verifyAll(); + mocks.verifyAll(); assertNull(ClientContextImpl.get(capturedEventContext.getValue()).diagnosticAccumulator); assertNull(ClientContextImpl.get(capturedDataSourceContext.getValue()).diagnosticAccumulator); } @@ -302,12 +318,12 @@ public void noWaitForDataSourceIfWaitMillisIsZero() throws Exception { expect(dataSource.start()).andReturn(initFuture); expect(dataSource.isInitialized()).andReturn(false); - replayAll(); + mocks.replayAll(); client = createMockClient(config); assertFalse(client.isInitialized()); - verifyAll(); + mocks.verifyAll(); } @Test @@ -318,12 +334,12 @@ public void willWaitForDataSourceIfWaitMillisIsGreaterThanZero() throws Exceptio expect(dataSource.start()).andReturn(initFuture); expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andReturn(null); expect(dataSource.isInitialized()).andReturn(false).anyTimes(); - replayAll(); + mocks.replayAll(); client = createMockClient(config); assertFalse(client.isInitialized()); - verifyAll(); + mocks.verifyAll(); } @Test @@ -333,12 +349,12 @@ public void noWaitForDataSourceIfWaitMillisIsNegative() throws Exception { expect(dataSource.start()).andReturn(initFuture); expect(dataSource.isInitialized()).andReturn(false); - replayAll(); + mocks.replayAll(); client = createMockClient(config); assertFalse(client.isInitialized()); - verifyAll(); + mocks.verifyAll(); } @Test @@ -349,12 +365,12 @@ public void dataSourceCanTimeOut() throws Exception { expect(dataSource.start()).andReturn(initFuture); expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andThrow(new TimeoutException()); expect(dataSource.isInitialized()).andReturn(false).anyTimes(); - replayAll(); + mocks.replayAll(); client = createMockClient(config); assertFalse(client.isInitialized()); - verifyAll(); + mocks.verifyAll(); } @Test @@ -365,12 +381,12 @@ public void clientCatchesRuntimeExceptionFromDataSource() throws Exception { expect(dataSource.start()).andReturn(initFuture); expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andThrow(new RuntimeException()); expect(dataSource.isInitialized()).andReturn(false).anyTimes(); - replayAll(); + mocks.replayAll(); client = createMockClient(config); assertFalse(client.isInitialized()); - verifyAll(); + mocks.verifyAll(); } @Test @@ -381,13 +397,13 @@ public void isFlagKnownReturnsTrueForExistingFlag() throws Exception { .dataStore(specificDataStore(testDataStore)); expect(dataSource.start()).andReturn(initFuture); expect(dataSource.isInitialized()).andReturn(true).times(1); - replayAll(); + mocks.replayAll(); client = createMockClient(config); upsertFlag(testDataStore, flagWithValue("key", LDValue.of(1))); assertTrue(client.isFlagKnown("key")); - verifyAll(); + mocks.verifyAll(); } @Test @@ -398,12 +414,12 @@ public void isFlagKnownReturnsFalseForUnknownFlag() throws Exception { .dataStore(specificDataStore(testDataStore)); expect(dataSource.start()).andReturn(initFuture); expect(dataSource.isInitialized()).andReturn(true).times(1); - replayAll(); + mocks.replayAll(); client = createMockClient(config); assertFalse(client.isFlagKnown("key")); - verifyAll(); + mocks.verifyAll(); } @Test @@ -414,13 +430,13 @@ public void isFlagKnownReturnsFalseIfStoreAndClientAreNotInitialized() throws Ex .dataStore(specificDataStore(testDataStore)); expect(dataSource.start()).andReturn(initFuture); expect(dataSource.isInitialized()).andReturn(false).times(1); - replayAll(); + mocks.replayAll(); client = createMockClient(config); upsertFlag(testDataStore, flagWithValue("key", LDValue.of(1))); assertFalse(client.isFlagKnown("key")); - verifyAll(); + mocks.verifyAll(); } @Test @@ -431,13 +447,13 @@ public void isFlagKnownUsesStoreIfStoreIsInitializedButClientIsNot() throws Exce .dataStore(specificDataStore(testDataStore)); expect(dataSource.start()).andReturn(initFuture); expect(dataSource.isInitialized()).andReturn(false).times(1); - replayAll(); + mocks.replayAll(); client = createMockClient(config); upsertFlag(testDataStore, flagWithValue("key", LDValue.of(1))); assertTrue(client.isFlagKnown("key")); - verifyAll(); + mocks.verifyAll(); } @Test @@ -448,7 +464,7 @@ public void isFlagKnownCatchesExceptionFromDataStore() throws Exception { .dataStore(specificDataStore(badStore)); expect(dataSource.start()).andReturn(initFuture); expect(dataSource.isInitialized()).andReturn(false).times(1); - replayAll(); + mocks.replayAll(); client = createMockClient(config); @@ -460,6 +476,7 @@ public void getVersion() throws Exception { LDConfig config = new LDConfig.Builder() .dataSource(Components.externalUpdatesOnly()) .events(Components.noEvents()) + .logging(Components.logging(testLogging)) .build(); try (LDClient client = new LDClient(SDK_KEY, config)) { assertEquals(Version.SDK_VERSION, client.version()); @@ -468,15 +485,12 @@ public void getVersion() throws Exception { @Test public void canGetCacheStatsFromDataStoreStatusProvider() throws Exception { - LDConfig config1 = new LDConfig.Builder() - .dataSource(Components.externalUpdatesOnly()) - .events(Components.noEvents()) - .build(); + LDConfig config1 = baseConfig().build(); try (LDClient client1 = new LDClient(SDK_KEY, config1)) { assertNull(client1.getDataStoreStatusProvider().getCacheStats()); } - LDConfig config2 = new LDConfig.Builder() + LDConfig config2 = baseConfig() .dataStore(Components.persistentDataStore(c -> new MockPersistentDataStore())) .build(); try (LDClient client2 = new LDClient(SDK_KEY, config2)) { @@ -491,7 +505,8 @@ public void testSecureModeHash() throws IOException { LDUser user = new LDUser.Builder("userkey").build(); String expectedHash = "c097a70924341660427c2e487b86efee789210f9e6dafc3b5f50e75bc596ff99"; - client = createMockClient(new LDConfig.Builder().startWait(Duration.ZERO)); + client = createMockClient(new LDConfig.Builder() + .startWait(Duration.ZERO)); assertEquals(expectedHash, client.secureModeHash(user)); assertNull(client.secureModeHash(null)); @@ -501,12 +516,13 @@ public void testSecureModeHash() throws IOException { private void setupMockDataSourceToInitialize(boolean willInitialize) { expect(dataSource.start()).andReturn(initFuture); expect(dataSource.isInitialized()).andReturn(willInitialize); - replayAll(); + mocks.replayAll(); } private LDClient createMockClient(LDConfig.Builder config) { config.dataSource(specificDataSource(dataSource)); config.events(specificEventProcessor(eventProcessor)); + config.logging(Components.logging(testLogging)); return new LDClient(SDK_KEY, config.build()); } } \ No newline at end of file diff --git a/src/test/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapperOtherTest.java b/src/test/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapperOtherTest.java index 52296c824..b68f60b00 100644 --- a/src/test/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapperOtherTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapperOtherTest.java @@ -24,7 +24,7 @@ * PersistentDataStoreWrapperTest suite. */ @SuppressWarnings("javadoc") -public class PersistentDataStoreWrapperOtherTest { +public class PersistentDataStoreWrapperOtherTest extends BaseTest { private static final RuntimeException FAKE_ERROR = new RuntimeException("fake error"); private final MockPersistentDataStore core; @@ -40,7 +40,8 @@ private PersistentDataStoreWrapper makeWrapper(Duration cacheTtl, StaleValuesPol policy, false, status -> {}, - sharedExecutor + sharedExecutor, + testLogger ); } diff --git a/src/test/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapperTest.java b/src/test/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapperTest.java index 439f73606..26341a334 100644 --- a/src/test/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapperTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/PersistentDataStoreWrapperTest.java @@ -2,8 +2,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.launchdarkly.sdk.server.PersistentDataStoreStatusManager; -import com.launchdarkly.sdk.server.PersistentDataStoreWrapper; import com.launchdarkly.sdk.server.DataStoreTestTypes.DataBuilder; import com.launchdarkly.sdk.server.DataStoreTestTypes.TestItem; import com.launchdarkly.sdk.server.integrations.MockPersistentDataStore; @@ -43,7 +41,7 @@ @SuppressWarnings("javadoc") @RunWith(Parameterized.class) -public class PersistentDataStoreWrapperTest { +public class PersistentDataStoreWrapperTest extends BaseTest { private static final RuntimeException FAKE_ERROR = new RuntimeException("fake error"); private final TestMode testMode; @@ -110,9 +108,10 @@ public PersistentDataStoreWrapperTest(TestMode testMode) { PersistentDataStoreBuilder.StaleValuesPolicy.EVICT, false, this::updateStatus, - sharedExecutor + sharedExecutor, + testLogger ); - this.statusBroadcaster = EventBroadcasterImpl.forDataStoreStatus(sharedExecutor); + this.statusBroadcaster = EventBroadcasterImpl.forDataStoreStatus(sharedExecutor, testLogger); this.dataStoreUpdates = new DataStoreUpdatesImpl(statusBroadcaster); this.dataStoreStatusProvider = new DataStoreStatusProviderImpl(wrapper, dataStoreUpdates); } @@ -474,7 +473,8 @@ public void initializedCanCacheFalseResult() throws Exception { PersistentDataStoreBuilder.StaleValuesPolicy.EVICT, false, this::updateStatus, - sharedExecutor + sharedExecutor, + testLogger )) { assertThat(wrapper1.isInitialized(), is(false)); assertThat(core.initedQueryCount, equalTo(1)); @@ -508,7 +508,8 @@ public void canGetCacheStats() throws Exception { PersistentDataStoreBuilder.StaleValuesPolicy.EVICT, true, this::updateStatus, - sharedExecutor + sharedExecutor, + testLogger )) { CacheStats stats = w.getCacheStats(); diff --git a/src/test/java/com/launchdarkly/sdk/server/PollingProcessorTest.java b/src/test/java/com/launchdarkly/sdk/server/PollingProcessorTest.java index 9e39712a6..10c4b3bbf 100644 --- a/src/test/java/com/launchdarkly/sdk/server/PollingProcessorTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/PollingProcessorTest.java @@ -47,7 +47,7 @@ import static org.junit.Assert.assertTrue; @SuppressWarnings("javadoc") -public class PollingProcessorTest { +public class PollingProcessorTest extends BaseTest { private static final String SDK_KEY = "sdk-key"; private static final Duration LENGTHY_INTERVAL = Duration.ofSeconds(60); private static final Duration BRIEF_INTERVAL = Duration.ofMillis(20); @@ -61,8 +61,8 @@ public void setup() { } private PollingProcessor makeProcessor(URI baseUri, Duration pollInterval) { - FeatureRequestor requestor = new DefaultFeatureRequestor(defaultHttpConfiguration(), baseUri); - return new PollingProcessor(requestor, dataSourceUpdates, sharedExecutor, pollInterval); + FeatureRequestor requestor = new DefaultFeatureRequestor(defaultHttpConfiguration(), baseUri, testLogger); + return new PollingProcessor(requestor, dataSourceUpdates, sharedExecutor, pollInterval, testLogger); } private static class TestPollHandler implements Handler { @@ -95,7 +95,7 @@ public void setError(int status) { @Test public void builderHasDefaultConfiguration() throws Exception { DataSourceFactory f = Components.pollingDataSource(); - try (PollingProcessor pp = (PollingProcessor)f.createDataSource(clientContext(SDK_KEY, LDConfig.DEFAULT), null)) { + try (PollingProcessor pp = (PollingProcessor)f.createDataSource(clientContext(SDK_KEY, baseConfig().build()), null)) { assertThat(((DefaultFeatureRequestor)pp.requestor).baseUri, equalTo(StandardEndpoints.DEFAULT_POLLING_BASE_URI)); assertThat(pp.pollInterval, equalTo(PollingDataSourceBuilder.DEFAULT_POLL_INTERVAL)); } @@ -107,7 +107,7 @@ public void builderCanSpecifyConfiguration() throws Exception { DataSourceFactory f = Components.pollingDataSource() .baseURI(uri) .pollInterval(LENGTHY_INTERVAL); - try (PollingProcessor pp = (PollingProcessor)f.createDataSource(clientContext(SDK_KEY, LDConfig.DEFAULT), null)) { + try (PollingProcessor pp = (PollingProcessor)f.createDataSource(clientContext(SDK_KEY, baseConfig().build()), null)) { assertThat(((DefaultFeatureRequestor)pp.requestor).baseUri, equalTo(uri)); assertThat(pp.pollInterval, equalTo(LENGTHY_INTERVAL)); } diff --git a/src/test/java/com/launchdarkly/sdk/server/StreamProcessorTest.java b/src/test/java/com/launchdarkly/sdk/server/StreamProcessorTest.java index 95a1eb717..a8b4cd356 100644 --- a/src/test/java/com/launchdarkly/sdk/server/StreamProcessorTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/StreamProcessorTest.java @@ -62,8 +62,7 @@ import static org.junit.Assert.assertTrue; @SuppressWarnings("javadoc") -public class StreamProcessorTest { - +public class StreamProcessorTest extends BaseTest { private static final String SDK_KEY = "sdk_key"; private static final Duration BRIEF_RECONNECT_DELAY = Duration.ofMillis(10); private static final String FEATURE1_KEY = "feature1"; @@ -754,7 +753,8 @@ private StreamProcessor createStreamProcessor(LDConfig config, URI streamUri, Di Thread.MIN_PRIORITY, acc, streamUri, - BRIEF_RECONNECT_DELAY + BRIEF_RECONNECT_DELAY, + testLogger ); } diff --git a/src/test/java/com/launchdarkly/sdk/server/TestComponents.java b/src/test/java/com/launchdarkly/sdk/server/TestComponents.java index 587cf4abb..13a7a91cc 100644 --- a/src/test/java/com/launchdarkly/sdk/server/TestComponents.java +++ b/src/test/java/com/launchdarkly/sdk/server/TestComponents.java @@ -1,6 +1,8 @@ package com.launchdarkly.sdk.server; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.Logs; import com.launchdarkly.sdk.UserAttribute; import com.launchdarkly.sdk.server.integrations.EventProcessorBuilder; import com.launchdarkly.sdk.server.interfaces.ClientContext; @@ -48,6 +50,8 @@ public class TestComponents { static ScheduledExecutorService sharedExecutor = newSingleThreadScheduledExecutor( new ThreadFactoryBuilder().setNameFormat("TestComponents-sharedExecutor-%d").build()); + public static LDLogger nullLogger = LDLogger.withAdapter(Logs.none(), ""); + public static ClientContext clientContext(final String sdkKey, final LDConfig config) { return new ClientContextImpl(sdkKey, config, sharedExecutor, null); } @@ -177,15 +181,16 @@ public static class UpsertParams { public MockDataSourceUpdates(DataStore store, DataStoreStatusProvider dataStoreStatusProvider) { this.dataStoreStatusProvider = dataStoreStatusProvider; - this.flagChangeEventBroadcaster = EventBroadcasterImpl.forFlagChangeEvents(sharedExecutor); - this.statusBroadcaster = EventBroadcasterImpl.forDataSourceStatus(sharedExecutor); + this.flagChangeEventBroadcaster = EventBroadcasterImpl.forFlagChangeEvents(sharedExecutor, nullLogger); + this.statusBroadcaster = EventBroadcasterImpl.forDataSourceStatus(sharedExecutor, nullLogger); this.wrappedInstance = new DataSourceUpdatesImpl( store, dataStoreStatusProvider, flagChangeEventBroadcaster, statusBroadcaster, sharedExecutor, - null + null, + nullLogger ); } @@ -350,7 +355,7 @@ public MockDataStoreStatusProvider() { } public MockDataStoreStatusProvider(boolean statusMonitoringEnabled) { - this.statusBroadcaster = EventBroadcasterImpl.forDataStoreStatus(sharedExecutor); + this.statusBroadcaster = EventBroadcasterImpl.forDataStoreStatus(sharedExecutor, nullLogger); this.lastStatus = new AtomicReference<>(new DataStoreStatusProvider.Status(true, false)); this.statusMonitoringEnabled = statusMonitoringEnabled; } diff --git a/src/test/java/com/launchdarkly/sdk/server/TestHttpUtil.java b/src/test/java/com/launchdarkly/sdk/server/TestHttpUtil.java index a8d23b2e9..11c5c7659 100644 --- a/src/test/java/com/launchdarkly/sdk/server/TestHttpUtil.java +++ b/src/test/java/com/launchdarkly/sdk/server/TestHttpUtil.java @@ -6,9 +6,6 @@ import com.launchdarkly.testhelpers.httptest.RequestInfo; import com.launchdarkly.testhelpers.httptest.ServerTLSConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.net.URI; @@ -18,8 +15,6 @@ import static org.hamcrest.Matchers.nullValue; class TestHttpUtil { - static Logger logger = LoggerFactory.getLogger(TestHttpUtil.class); - // Used for testWithSpecialHttpConfigurations static interface HttpConfigurationTestAction { void accept(URI targetUri, HttpConfigurationFactory httpConfig) throws IOException; @@ -62,7 +57,6 @@ static void testWithSpecialHttpConfigurations( static void testHttpClientDoesNotAllowSelfSignedCertByDefault(Handler handler, HttpConfigurationTestAction testActionShouldFail) { - logger.warn("testHttpClientDoesNotAllowSelfSignedCertByDefault"); try { ServerTLSConfiguration tlsConfig = ServerTLSConfiguration.makeSelfSignedCertificate(); try (HttpServer secureServer = HttpServer.startSecure(tlsConfig, handler)) { @@ -76,7 +70,6 @@ static void testHttpClientDoesNotAllowSelfSignedCertByDefault(Handler handler, static void testHttpClientCanBeConfiguredToAllowSelfSignedCert(Handler handler, HttpConfigurationTestAction testActionShouldSucceed) { - logger.warn("testHttpClientCanBeConfiguredToAllowSelfSignedCert"); try { ServerTLSConfiguration tlsConfig = ServerTLSConfiguration.makeSelfSignedCertificate(); HttpConfigurationFactory httpConfig = Components.httpConfiguration() @@ -91,8 +84,7 @@ static void testHttpClientCanBeConfiguredToAllowSelfSignedCert(Handler handler, } static void testHttpClientCanUseCustomSocketFactory(Handler handler, - HttpConfigurationTestAction testActionShouldSucceed) { - logger.warn("testHttpClientCanUseCustomSocketFactory"); + HttpConfigurationTestAction testActionShouldSucceed) { try { try (HttpServer server = HttpServer.start(handler)) { HttpConfigurationFactory httpConfig = Components.httpConfiguration() @@ -109,7 +101,6 @@ static void testHttpClientCanUseCustomSocketFactory(Handler handler, static void testHttpClientCanUseProxy(Handler handler, HttpConfigurationTestAction testActionShouldSucceed) { - logger.warn("testHttpClientCanUseProxy"); try { try (HttpServer server = HttpServer.start(handler)) { HttpConfigurationFactory httpConfig = Components.httpConfiguration() @@ -126,7 +117,6 @@ static void testHttpClientCanUseProxy(Handler handler, static void testHttpClientCanUseProxyWithBasicAuth(Handler handler, HttpConfigurationTestAction testActionShouldSucceed) { - logger.warn("testHttpClientCanUseProxyWithBasicAuth"); Handler proxyHandler = ctx -> { if (ctx.getRequest().getHeader("Proxy-Authorization") == null) { ctx.setStatus(407); diff --git a/src/test/java/com/launchdarkly/sdk/server/UtilTest.java b/src/test/java/com/launchdarkly/sdk/server/UtilTest.java index 1aca2b32f..e85a35617 100644 --- a/src/test/java/com/launchdarkly/sdk/server/UtilTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/UtilTest.java @@ -9,6 +9,7 @@ import java.time.Duration; import static com.launchdarkly.sdk.server.TestComponents.clientContext; +import static com.launchdarkly.sdk.server.Util.applicationTagHeader; import static com.launchdarkly.sdk.server.Util.configureHttpClientBuilder; import static com.launchdarkly.sdk.server.Util.shutdownHttpClient; import static org.junit.Assert.assertEquals; @@ -21,7 +22,7 @@ import okhttp3.Response; @SuppressWarnings("javadoc") -public class UtilTest { +public class UtilTest extends BaseTest { @Test public void testConnectTimeout() { LDConfig config = new LDConfig.Builder().http(Components.httpConfiguration().connectTimeout(Duration.ofSeconds(3))).build(); @@ -93,16 +94,20 @@ public void describeDuration() { } @Test - public void applicationTagHeader() { - assertEquals("", Util.applicationTagHeader(new ApplicationInfo(null, null))); - assertEquals("application-id/foo", Util.applicationTagHeader(new ApplicationInfo("foo", null))); - assertEquals("application-version/1.0.0", Util.applicationTagHeader(new ApplicationInfo(null, "1.0.0"))); - assertEquals("application-id/foo application-version/1.0.0", Util.applicationTagHeader(new ApplicationInfo("foo", "1.0.0"))); + public void testApplicationTagHeader() { + assertEquals("", applicationTagHeader(new ApplicationInfo(null, null), testLogger)); + assertEquals("application-id/foo", applicationTagHeader(new ApplicationInfo("foo", null), testLogger)); + assertEquals("application-version/1.0.0", + applicationTagHeader(new ApplicationInfo(null, "1.0.0"), testLogger)); + assertEquals("application-id/foo application-version/1.0.0", + applicationTagHeader(new ApplicationInfo("foo", "1.0.0"), testLogger)); // Values with invalid characters get discarded - assertEquals("", Util.applicationTagHeader(new ApplicationInfo("invalid name", "lol!"))); + assertEquals("", applicationTagHeader(new ApplicationInfo("invalid name", "lol!"), testLogger)); // Values over 64 chars get discarded - assertEquals("", Util.applicationTagHeader(new ApplicationInfo("look-at-this-incredibly-long-application-id-like-wow-it-sure-is-verbose", null))); + assertEquals("", applicationTagHeader( + new ApplicationInfo("look-at-this-incredibly-long-application-id-like-wow-it-sure-is-verbose", null), + testLogger)); // Empty values get discarded - assertEquals("", Util.applicationTagHeader(new ApplicationInfo("", ""))); + assertEquals("", applicationTagHeader(new ApplicationInfo("", ""), testLogger)); } } diff --git a/src/test/java/com/launchdarkly/sdk/server/integrations/BigSegmentStoreTestBase.java b/src/test/java/com/launchdarkly/sdk/server/integrations/BigSegmentStoreTestBase.java index e703f46e0..be6a9c79d 100644 --- a/src/test/java/com/launchdarkly/sdk/server/integrations/BigSegmentStoreTestBase.java +++ b/src/test/java/com/launchdarkly/sdk/server/integrations/BigSegmentStoreTestBase.java @@ -1,16 +1,11 @@ package com.launchdarkly.sdk.server.integrations; -import static com.launchdarkly.sdk.server.TestComponents.clientContext; -import static com.launchdarkly.sdk.server.interfaces.BigSegmentStoreTypes.createMembershipFromSegmentRefs; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import com.launchdarkly.sdk.server.LDConfig; +import com.launchdarkly.sdk.server.BaseTest; import com.launchdarkly.sdk.server.interfaces.BigSegmentStore; import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreFactory; import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreTypes.Membership; import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreTypes.StoreMetadata; +import com.launchdarkly.sdk.server.interfaces.ClientContext; import org.junit.Assert; import org.junit.Test; @@ -20,6 +15,12 @@ import java.util.List; import java.util.Objects; +import static com.launchdarkly.sdk.server.TestComponents.clientContext; +import static com.launchdarkly.sdk.server.interfaces.BigSegmentStoreTypes.createMembershipFromSegmentRefs; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + /** * A configurable test class for all implementations of {@link BigSegmentStore}. *

@@ -31,15 +32,18 @@ * {@link #setMetadata(String, StoreMetadata)}, and {@link #setSegments(String, String, Iterable, Iterable)}. */ @SuppressWarnings("javadoc") -public abstract class BigSegmentStoreTestBase { +public abstract class BigSegmentStoreTestBase extends BaseTest { private static final String prefix = "testprefix"; private static final String fakeUserHash = "userhash"; private static final String segmentRef1 = "key1", segmentRef2 = "key2", segmentRef3 = "key3"; private static final String[] allSegmentRefs = {segmentRef1, segmentRef2, segmentRef3}; + private ClientContext makeClientContext() { + return clientContext("", baseConfig().build()); + } + private BigSegmentStore makeEmptyStore() throws Exception { - LDConfig config = new LDConfig.Builder().build(); - BigSegmentStore store = makeStore(prefix).createBigSegmentStore(clientContext("sdk-key", config)); + BigSegmentStore store = makeStore(prefix).createBigSegmentStore(makeClientContext()); try { clearData(prefix); } catch (RuntimeException ex) { diff --git a/src/test/java/com/launchdarkly/sdk/server/integrations/FileDataSourceAutoUpdateTest.java b/src/test/java/com/launchdarkly/sdk/server/integrations/FileDataSourceAutoUpdateTest.java index d60f5cd9f..38b479cb7 100644 --- a/src/test/java/com/launchdarkly/sdk/server/integrations/FileDataSourceAutoUpdateTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/integrations/FileDataSourceAutoUpdateTest.java @@ -1,5 +1,6 @@ package com.launchdarkly.sdk.server.integrations; +import com.launchdarkly.sdk.server.BaseTest; import com.launchdarkly.sdk.server.LDConfig; import com.launchdarkly.sdk.server.TestComponents; import com.launchdarkly.sdk.server.TestComponents.MockDataSourceUpdates; @@ -32,10 +33,10 @@ import static org.junit.Assert.assertTrue; @SuppressWarnings("javadoc") -public class FileDataSourceAutoUpdateTest { +public class FileDataSourceAutoUpdateTest extends BaseTest { private final DataStore store; private MockDataSourceUpdates dataSourceUpdates; - private final LDConfig config = new LDConfig.Builder().build(); + private final LDConfig config = baseConfig().build(); public FileDataSourceAutoUpdateTest() throws Exception { store = inMemoryDataStore(); diff --git a/src/test/java/com/launchdarkly/sdk/server/integrations/FileDataSourceTest.java b/src/test/java/com/launchdarkly/sdk/server/integrations/FileDataSourceTest.java index 8977a1d3a..e27e96882 100644 --- a/src/test/java/com/launchdarkly/sdk/server/integrations/FileDataSourceTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/integrations/FileDataSourceTest.java @@ -1,6 +1,7 @@ package com.launchdarkly.sdk.server.integrations; import com.google.common.collect.ImmutableSet; +import com.launchdarkly.sdk.server.BaseTest; import com.launchdarkly.sdk.server.LDConfig; import com.launchdarkly.sdk.server.TestComponents; import com.launchdarkly.sdk.server.TestComponents.MockDataSourceUpdates; @@ -31,12 +32,12 @@ import static org.junit.Assert.assertEquals; @SuppressWarnings("javadoc") -public class FileDataSourceTest { +public class FileDataSourceTest extends BaseTest { private static final Path badFilePath = Paths.get("no-such-file.json"); private final DataStore store; private MockDataSourceUpdates dataSourceUpdates; - private final LDConfig config = new LDConfig.Builder().build(); + private final LDConfig config = baseConfig().build(); public FileDataSourceTest() throws Exception { store = inMemoryDataStore(); diff --git a/src/test/java/com/launchdarkly/sdk/server/integrations/LoggingConfigurationBuilderTest.java b/src/test/java/com/launchdarkly/sdk/server/integrations/LoggingConfigurationBuilderTest.java index 8100ad89c..cc547e55e 100644 --- a/src/test/java/com/launchdarkly/sdk/server/integrations/LoggingConfigurationBuilderTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/integrations/LoggingConfigurationBuilderTest.java @@ -1,5 +1,10 @@ package com.launchdarkly.sdk.server.integrations; +import com.launchdarkly.logging.LDLogLevel; +import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LDSLF4J; +import com.launchdarkly.logging.LogCapture; +import com.launchdarkly.logging.Logs; import com.launchdarkly.sdk.server.Components; import com.launchdarkly.sdk.server.interfaces.BasicConfiguration; import com.launchdarkly.sdk.server.interfaces.LoggingConfiguration; @@ -8,6 +13,9 @@ import java.time.Duration; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -35,4 +43,40 @@ public void logDataSourceOutageAsErrorAfter() { .createLoggingConfiguration(BASIC_CONFIG); assertNull(c2.getLogDataSourceOutageAsErrorAfter()); } + + @Test + public void defaultLogAdapterIsSLF4J() { + LoggingConfiguration c = Components.logging() + .createLoggingConfiguration(BASIC_CONFIG); + assertThat(c.getLogAdapter(), sameInstance(LDSLF4J.adapter())); + } + + @Test + public void canSetLogAdapterAndLevel() { + LogCapture logSink = Logs.capture(); + LoggingConfiguration c = Components.logging() + .adapter(logSink) + .level(LDLogLevel.WARN) + .createLoggingConfiguration(BASIC_CONFIG); + LDLogger logger = LDLogger.withAdapter(c.getLogAdapter(), ""); + logger.debug("message 1"); + logger.info("message 2"); + logger.warn("message 3"); + logger.error("message 4"); + assertThat(logSink.getMessageStrings(), contains("WARN:message 3", "ERROR:message 4")); + } + + @Test + public void defaultLevelIsInfo() { + LogCapture logSink = Logs.capture(); + LoggingConfiguration c = Components.logging() + .adapter(logSink) + .createLoggingConfiguration(BASIC_CONFIG); + LDLogger logger = LDLogger.withAdapter(c.getLogAdapter(), ""); + logger.debug("message 1"); + logger.info("message 2"); + logger.warn("message 3"); + logger.error("message 4"); + assertThat(logSink.getMessageStrings(), contains("INFO:message 2", "WARN:message 3", "ERROR:message 4")); + } } diff --git a/src/test/java/com/launchdarkly/sdk/server/integrations/PersistentDataStoreGenericTest.java b/src/test/java/com/launchdarkly/sdk/server/integrations/PersistentDataStoreGenericTest.java index af4040c3a..f2066c834 100644 --- a/src/test/java/com/launchdarkly/sdk/server/integrations/PersistentDataStoreGenericTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/integrations/PersistentDataStoreGenericTest.java @@ -1,6 +1,8 @@ package com.launchdarkly.sdk.server.integrations; import com.google.common.collect.ImmutableList; +import com.launchdarkly.sdk.server.TestComponents; +import com.launchdarkly.sdk.server.interfaces.PersistentDataStoreFactory; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -48,15 +50,10 @@ public PersistentDataStoreGenericTest(TestMode testMode) { } @Override - protected MockPersistentDataStore makeStore() { - return makeStoreWithPrefix(""); - } - - @Override - protected MockPersistentDataStore makeStoreWithPrefix(String prefix) { + protected PersistentDataStoreFactory buildStore(String prefix) { MockPersistentDataStore store = new MockPersistentDataStore(sharedData, prefix); store.persistOnlyAsString = testMode.persistOnlyAsString; - return store; + return TestComponents.specificPersistentDataStore(store); } @Override diff --git a/src/test/java/com/launchdarkly/sdk/server/integrations/PersistentDataStoreTestBase.java b/src/test/java/com/launchdarkly/sdk/server/integrations/PersistentDataStoreTestBase.java index df805b330..c83ee811f 100644 --- a/src/test/java/com/launchdarkly/sdk/server/integrations/PersistentDataStoreTestBase.java +++ b/src/test/java/com/launchdarkly/sdk/server/integrations/PersistentDataStoreTestBase.java @@ -1,11 +1,14 @@ package com.launchdarkly.sdk.server.integrations; +import com.launchdarkly.sdk.server.BaseTest; import com.launchdarkly.sdk.server.DataStoreTestTypes.DataBuilder; import com.launchdarkly.sdk.server.DataStoreTestTypes.TestItem; -import com.launchdarkly.sdk.server.interfaces.PersistentDataStore; +import com.launchdarkly.sdk.server.interfaces.ClientContext; import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.FullDataSet; import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.ItemDescriptor; import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.SerializedItemDescriptor; +import com.launchdarkly.sdk.server.interfaces.PersistentDataStore; +import com.launchdarkly.sdk.server.interfaces.PersistentDataStoreFactory; import org.junit.After; import org.junit.Assume; @@ -18,6 +21,7 @@ import static com.launchdarkly.sdk.server.DataStoreTestTypes.TEST_ITEMS; import static com.launchdarkly.sdk.server.DataStoreTestTypes.toItemsMap; import static com.launchdarkly.sdk.server.DataStoreTestTypes.toSerialized; +import static com.launchdarkly.sdk.server.TestComponents.clientContext; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -30,7 +34,7 @@ * CachingStoreWrapperTest. */ @SuppressWarnings("javadoc") -public abstract class PersistentDataStoreTestBase { +public abstract class PersistentDataStoreTestBase extends BaseTest { protected T store; protected TestItem item1 = new TestItem("key1", "first", 10); @@ -39,17 +43,57 @@ public abstract class PersistentDataStoreTestBase protected TestItem otherItem1 = new TestItem("key1", "other-first", 11); + private ClientContext makeClientContext() { + return clientContext("", baseConfig().build()); + } + + @SuppressWarnings("unchecked") + private T makeConfiguredStore() { + PersistentDataStoreFactory builder = buildStore(null); + if (builder == null) { + return makeStore(); + } + return (T)builder.createPersistentDataStore(makeClientContext()); + } + + @SuppressWarnings("unchecked") + private T makeConfiguredStoreWithPrefix(String prefix) { + PersistentDataStoreFactory builder = buildStore(prefix); + if (builder == null) { + return makeStoreWithPrefix(prefix); + } + return (T)builder.createPersistentDataStore(makeClientContext()); + } + + /** + * Test subclasses can override either this method or {@link #buildStore(String)} to create an instance + * of the feature store class with default properties. + * @deprecated It is preferable to override {@link #buildStore(String)} instead. + */ + @Deprecated + protected T makeStore() { + throw new RuntimeException("test subclasses must override either makeStore or buildStore"); + } + /** - * Test subclasses must override this method to create an instance of the feature store class - * with default properties. + * Test subclasses can implement this (or override {@link #buildStore(String)}) if the feature store + * class supports a key prefix option for keeping data sets distinct within the same database. + * @deprecated It is preferable to override {@link #buildStore(String)} instead. */ - protected abstract T makeStore(); + protected T makeStoreWithPrefix(String prefix) { + return null; + } /** - * Test subclasses should implement this if the feature store class supports a key prefix option - * for keeping data sets distinct within the same database. + * Test subclasses should override this method to prepare an instance of the data store class. + * They are allowed to return null if {@code prefix} is non-null and they do not support prefixes. + * + * @param prefix a database prefix or null + * @return a factory for creating the data store */ - protected abstract T makeStoreWithPrefix(String prefix); + protected PersistentDataStoreFactory buildStore(String prefix) { + return null; + } /** * Test classes should override this to clear all data from the underlying database. @@ -87,12 +131,14 @@ private void assertEqualsDeletedItem(SerializedItemDescriptor expected, Serializ @Before public void setup() { - store = makeStore(); + store = makeConfiguredStore(); } @After public void teardown() throws Exception { - store.close(); + if (store != null) { + store.close(); + } } @Test @@ -127,7 +173,7 @@ public void initCompletelyReplacesPreviousData() { @Test public void oneInstanceCanDetectIfAnotherInstanceHasInitializedTheStore() { clearAllData(); - T store2 = makeStore(); + T store2 = makeConfiguredStore(); assertFalse(store.isInitialized()); @@ -139,7 +185,7 @@ public void oneInstanceCanDetectIfAnotherInstanceHasInitializedTheStore() { @Test public void oneInstanceCanDetectIfAnotherInstanceHasInitializedTheStoreEvenIfEmpty() { clearAllData(); - T store2 = makeStore(); + T store2 = makeConfiguredStore(); assertFalse(store.isInitialized()); @@ -243,7 +289,7 @@ public void upsertOlderVersionAfterDelete() { @Test public void handlesUpsertRaceConditionAgainstExternalClientWithLowerVersion() throws Exception { - final T store2 = makeStore(); + final T store2 = makeConfiguredStore(); int startVersion = 1; final int store2VersionStart = 2; @@ -279,7 +325,7 @@ public void run() { @Test public void handlesUpsertRaceConditionAgainstExternalClientWithHigherVersion() throws Exception { - final T store2 = makeStore(); + final T store2 = makeConfiguredStore(); int startVersion = 1; final int store2Version = 3; @@ -310,9 +356,9 @@ public void run() { @Test public void storesWithDifferentPrefixAreIndependent() throws Exception { - T store1 = makeStoreWithPrefix("aaa"); + T store1 = makeConfiguredStoreWithPrefix("aaa"); Assume.assumeNotNull(store1); - T store2 = makeStoreWithPrefix("bbb"); + T store2 = makeConfiguredStoreWithPrefix("bbb"); clearAllData(); try { diff --git a/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataTest.java b/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataTest.java index 2379f2db3..329805360 100644 --- a/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataTest.java +++ b/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataTest.java @@ -152,8 +152,8 @@ public void flagConfigSimpleBoolean() throws Exception { verifyFlag(f -> f.booleanFlag(), onProps + fallthroughTrue); verifyFlag(f -> f.on(true), onProps + fallthroughTrue); verifyFlag(f -> f.on(false), offProps + fallthroughTrue); - verifyFlag(f -> f.variationForAllUsers(false), onProps + fallthroughFalse); - verifyFlag(f -> f.variationForAllUsers(true), onProps + fallthroughTrue); + verifyFlag(f -> f.variationForAll(false), onProps + fallthroughFalse); + verifyFlag(f -> f.variationForAll(true), onProps + fallthroughTrue); verifyFlag( f -> f.fallthroughVariation(true).offVariation(false),