Skip to content
This repository has been archived by the owner on May 30, 2024. It is now read-only.

Commit

Permalink
Merge pull request #181 from launchdarkly/eb/ch62088/store-type-desc
Browse files Browse the repository at this point in the history
(1 of 2) allow each component to describe its own configuration for diagnostics
  • Loading branch information
eli-darkly authored Jan 28, 2020
2 parents f43a473 + c9482c5 commit 067eb74
Show file tree
Hide file tree
Showing 8 changed files with 423 additions and 136 deletions.
100 changes: 92 additions & 8 deletions src/main/java/com/launchdarkly/client/Components.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.launchdarkly.client;

import com.launchdarkly.client.DiagnosticEvent.ConfigProperty;
import com.launchdarkly.client.integrations.EventProcessorBuilder;
import com.launchdarkly.client.integrations.PersistentDataStoreBuilder;
import com.launchdarkly.client.integrations.PollingDataSourceBuilder;
import com.launchdarkly.client.integrations.StreamingDataSourceBuilder;
import com.launchdarkly.client.interfaces.DiagnosticDescription;
import com.launchdarkly.client.interfaces.PersistentDataStoreFactory;
import com.launchdarkly.client.value.LDValue;

import java.io.IOException;
import java.net.URI;
Expand All @@ -30,7 +33,7 @@ public abstract class Components {
private static final EventProcessorFactory defaultEventProcessorFactory = new DefaultEventProcessorFactory();
private static final EventProcessorFactory nullEventProcessorFactory = new NullEventProcessorFactory();
private static final UpdateProcessorFactory defaultUpdateProcessorFactory = new DefaultUpdateProcessorFactory();
private static final UpdateProcessorFactory nullUpdateProcessorFactory = new NullUpdateProcessorFactory();
private static final NullUpdateProcessorFactory nullUpdateProcessorFactory = new NullUpdateProcessorFactory();

/**
* Returns a configuration object for using the default in-memory implementation of a data store.
Expand Down Expand Up @@ -307,11 +310,16 @@ public static UpdateProcessorFactory nullUpdateProcessor() {
return nullUpdateProcessorFactory;
}

private static final class InMemoryFeatureStoreFactory implements FeatureStoreFactory {
private static final class InMemoryFeatureStoreFactory implements FeatureStoreFactory, DiagnosticDescription {
@Override
public FeatureStore createFeatureStore() {
return new InMemoryFeatureStore();
}

@Override
public LDValue describeConfiguration(LDConfig config) {
return LDValue.of("memory");
}
}

private static final class DefaultEventProcessorFactory implements EventProcessorFactoryWithDiagnostics {
Expand Down Expand Up @@ -361,7 +369,8 @@ public void close() {
}

// This can be removed once the deprecated polling/streaming config options have been removed.
private static final class DefaultUpdateProcessorFactory implements UpdateProcessorFactoryWithDiagnostics {
private static final class DefaultUpdateProcessorFactory implements UpdateProcessorFactoryWithDiagnostics,
DiagnosticDescription {
@Override
public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, FeatureStore featureStore) {
return createUpdateProcessor(sdkKey, config, featureStore, null);
Expand All @@ -370,6 +379,9 @@ public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, Fea
@Override
public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, FeatureStore featureStore,
DiagnosticAccumulator diagnosticAccumulator) {
if (config.offline) {
return Components.externalUpdatesOnly().createUpdateProcessor(sdkKey, config, featureStore);
}
// We don't need to check config.offline or config.useLdd here; the former is checked automatically
// by StreamingDataSourceBuilder and PollingDataSourceBuilder, and setting the latter is translated
// into using externalUpdatesOnly() by LDConfig.Builder.
Expand All @@ -386,9 +398,37 @@ public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, Fea
.createUpdateProcessor(sdkKey, config, featureStore);
}
}

@Override
public LDValue describeConfiguration(LDConfig config) {
if (config.offline) {
return nullUpdateProcessorFactory.describeConfiguration(config);
}
if (config.deprecatedStream) {
return LDValue.buildObject()
.put(ConfigProperty.STREAMING_DISABLED.name, false)
.put(ConfigProperty.CUSTOM_BASE_URI.name,
config.deprecatedBaseURI != null && !config.deprecatedBaseURI.equals(LDConfig.DEFAULT_BASE_URI))
.put(ConfigProperty.CUSTOM_STREAM_URI.name,
config.deprecatedStreamURI != null && !config.deprecatedStreamURI.equals(LDConfig.DEFAULT_STREAM_URI))
.put(ConfigProperty.RECONNECT_TIME_MILLIS.name, config.deprecatedReconnectTimeMs)
.put(ConfigProperty.USING_RELAY_DAEMON.name, false)
.build();
} else {
return LDValue.buildObject()
.put(ConfigProperty.STREAMING_DISABLED.name, true)
.put(ConfigProperty.CUSTOM_BASE_URI.name,
config.deprecatedBaseURI != null && !config.deprecatedBaseURI.equals(LDConfig.DEFAULT_BASE_URI))
.put(ConfigProperty.CUSTOM_STREAM_URI.name, false)
.put(ConfigProperty.POLLING_INTERVAL_MILLIS.name, config.deprecatedPollingIntervalMillis)
.put(ConfigProperty.USING_RELAY_DAEMON.name, false)
.build();
}
}

}

private static final class NullUpdateProcessorFactory implements UpdateProcessorFactory {
private static final class NullUpdateProcessorFactory implements UpdateProcessorFactory, DiagnosticDescription {
@Override
public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, FeatureStore featureStore) {
if (config.offline) {
Expand All @@ -400,6 +440,19 @@ public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, Fea
}
return new NullUpdateProcessor();
}

@Override
public LDValue describeConfiguration(LDConfig config) {
// We can assume that if they don't have a data source, and they *do* have a persistent data store, then
// they're using Relay in daemon mode.
return LDValue.buildObject()
.put(ConfigProperty.CUSTOM_BASE_URI.name, false)
.put(ConfigProperty.CUSTOM_STREAM_URI.name, false)
.put(ConfigProperty.STREAMING_DISABLED.name, false)
.put(ConfigProperty.USING_RELAY_DAEMON.name,
config.dataStoreFactory != null && config.dataStoreFactory != Components.inMemoryDataStore())
.build();
}
}

// Package-private for visibility in tests
Expand All @@ -418,7 +471,8 @@ public boolean initialized() {
public void close() throws IOException {}
}

private static final class StreamingDataSourceBuilderImpl extends StreamingDataSourceBuilder implements UpdateProcessorFactoryWithDiagnostics {
private static final class StreamingDataSourceBuilderImpl extends StreamingDataSourceBuilder
implements UpdateProcessorFactoryWithDiagnostics, DiagnosticDescription {
@Override
public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, FeatureStore featureStore) {
return createUpdateProcessor(sdkKey, config, featureStore, null);
Expand All @@ -430,7 +484,6 @@ public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, Fea
// Note, we log startup messages under the LDClient class to keep logs more readable

if (config.offline) {
LDClient.logger.info("Starting LaunchDarkly client in offline mode");
return Components.externalUpdatesOnly().createUpdateProcessor(sdkKey, config, featureStore);
}

Expand Down Expand Up @@ -466,15 +519,31 @@ public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, Fea
initialReconnectDelayMillis
);
}

@Override
public LDValue describeConfiguration(LDConfig config) {
if (config.offline) {
return nullUpdateProcessorFactory.describeConfiguration(config);
}
return LDValue.buildObject()
.put(ConfigProperty.STREAMING_DISABLED.name, false)
.put(ConfigProperty.CUSTOM_BASE_URI.name,
(pollingBaseUri != null && !pollingBaseUri.equals(LDConfig.DEFAULT_BASE_URI)) ||
(pollingBaseUri == null && baseUri != null && !baseUri.equals(LDConfig.DEFAULT_STREAM_URI)))
.put(ConfigProperty.CUSTOM_STREAM_URI.name,
baseUri != null && !baseUri.equals(LDConfig.DEFAULT_STREAM_URI))
.put(ConfigProperty.RECONNECT_TIME_MILLIS.name, initialReconnectDelayMillis)
.put(ConfigProperty.USING_RELAY_DAEMON.name, false)
.build();
}
}

private static final class PollingDataSourceBuilderImpl extends PollingDataSourceBuilder {
private static final class PollingDataSourceBuilderImpl extends PollingDataSourceBuilder implements DiagnosticDescription {
@Override
public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, FeatureStore featureStore) {
// Note, we log startup messages under the LDClient class to keep logs more readable

if (config.offline) {
LDClient.logger.info("Starting LaunchDarkly client in offline mode");
return Components.externalUpdatesOnly().createUpdateProcessor(sdkKey, config, featureStore);
}

Expand All @@ -490,6 +559,21 @@ public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, Fea
);
return new PollingProcessor(requestor, featureStore, pollIntervalMillis);
}

@Override
public LDValue describeConfiguration(LDConfig config) {
if (config.offline) {
return nullUpdateProcessorFactory.describeConfiguration(config);
}
return LDValue.buildObject()
.put(ConfigProperty.STREAMING_DISABLED.name, true)
.put(ConfigProperty.CUSTOM_BASE_URI.name,
baseUri != null && !baseUri.equals(LDConfig.DEFAULT_BASE_URI))
.put(ConfigProperty.CUSTOM_STREAM_URI.name, false)
.put(ConfigProperty.POLLING_INTERVAL_MILLIS.name, pollIntervalMillis)
.put(ConfigProperty.USING_RELAY_DAEMON.name, false)
.build();
}
}

private static final class EventProcessorBuilderImpl extends EventProcessorBuilder
Expand Down
151 changes: 90 additions & 61 deletions src/main/java/com/launchdarkly/client/DiagnosticEvent.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
package com.launchdarkly.client;

import com.launchdarkly.client.interfaces.DiagnosticDescription;
import com.launchdarkly.client.value.LDValue;
import com.launchdarkly.client.value.LDValueType;
import com.launchdarkly.client.value.ObjectBuilder;

import java.util.List;

class DiagnosticEvent {

static enum ConfigProperty {
CUSTOM_BASE_URI("customBaseURI", LDValueType.BOOLEAN),
CUSTOM_EVENTS_URI("customEventsURI", LDValueType.BOOLEAN),
CUSTOM_STREAM_URI("customStreamURI", LDValueType.BOOLEAN),
POLLING_INTERVAL_MILLIS("pollingIntervalMillis", LDValueType.NUMBER),
RECONNECT_TIME_MILLIS("reconnectTimeMillis", LDValueType.NUMBER),
STREAMING_DISABLED("streamingDisabled", LDValueType.BOOLEAN),
USING_RELAY_DAEMON("usingRelayDaemon", LDValueType.BOOLEAN);

String name;
LDValueType type;

private ConfigProperty(String name, LDValueType type) {
this.name = name;
this.type = type;
}
}

final String kind;
final long creationDate;
final DiagnosticId id;
Expand Down Expand Up @@ -46,77 +68,84 @@ static class Statistics extends DiagnosticEvent {
}

static class Init extends DiagnosticEvent {

final DiagnosticSdk sdk;
final DiagnosticConfiguration configuration;
final LDValue configuration;
final DiagnosticPlatform platform = new DiagnosticPlatform();

Init(long creationDate, DiagnosticId diagnosticId, LDConfig config) {
super("diagnostic-init", creationDate, diagnosticId);
this.sdk = new DiagnosticSdk(config);
this.configuration = new DiagnosticConfiguration(config);
this.configuration = getConfigurationData(config);
}

@SuppressWarnings("unused") // fields are for JSON serialization only
static class DiagnosticConfiguration {
private final boolean customBaseURI;
private final boolean customEventsURI;
private final boolean customStreamURI;
private final int eventsCapacity;
private final int connectTimeoutMillis;
private final int socketTimeoutMillis;
private final long eventsFlushIntervalMillis;
private final boolean usingProxy;
private final boolean usingProxyAuthenticator;
private final boolean streamingDisabled;
private final boolean usingRelayDaemon;
private final boolean offline;
private final boolean allAttributesPrivate;
private final long pollingIntervalMillis;
private final long startWaitMillis;
private final int samplingInterval;
private final long reconnectTimeMillis;
private final int userKeysCapacity;
private final long userKeysFlushIntervalMillis;
private final boolean inlineUsersInEvents;
private final int diagnosticRecordingIntervalMillis;
private final String featureStore;

DiagnosticConfiguration(LDConfig config) {
this.customBaseURI = !(LDConfig.DEFAULT_BASE_URI.equals(config.deprecatedBaseURI)); // TODO: get actual config from components
this.customEventsURI = !(LDConfig.DEFAULT_EVENTS_URI.equals(config.deprecatedEventsURI));
this.customStreamURI = !(LDConfig.DEFAULT_STREAM_URI.equals(config.deprecatedStreamURI));
this.eventsCapacity = config.deprecatedCapacity;
this.connectTimeoutMillis = (int)config.httpConfig.connectTimeoutUnit.toMillis(config.httpConfig.connectTimeout);
this.socketTimeoutMillis = (int)config.httpConfig.socketTimeoutUnit.toMillis(config.httpConfig.socketTimeout);
this.eventsFlushIntervalMillis = config.deprecatedFlushInterval * 1000;
this.usingProxy = config.httpConfig.proxy != null;
this.usingProxyAuthenticator = config.httpConfig.proxyAuthenticator != null;
this.streamingDisabled = !config.deprecatedStream;
this.usingRelayDaemon =
config.dataSourceFactory == Components.externalUpdatesOnly() &&
config.dataStoreFactory != null &&
config.dataStoreFactory != Components.inMemoryDataStore();
this.offline = config.offline;
this.allAttributesPrivate = config.deprecatedAllAttributesPrivate;
this.pollingIntervalMillis = config.deprecatedPollingIntervalMillis;
this.startWaitMillis = config.startWaitMillis;
this.samplingInterval = config.deprecatedSamplingInterval;
this.reconnectTimeMillis = config.deprecatedReconnectTimeMs;
this.userKeysCapacity = config.deprecatedUserKeysCapacity;
this.userKeysFlushIntervalMillis = config.deprecatedUserKeysFlushInterval * 1000;
this.inlineUsersInEvents = config.deprecatedInlineUsersInEvents;
this.diagnosticRecordingIntervalMillis = config.diagnosticRecordingIntervalMillis;
if (config.deprecatedFeatureStore != null) {
this.featureStore = config.deprecatedFeatureStore.getClass().getSimpleName();
} else if (config.dataStoreFactory != null) {
this.featureStore = config.dataStoreFactory.getClass().getSimpleName();
} else {
this.featureStore = Components.inMemoryDataStore().getClass().getSimpleName();
@SuppressWarnings("deprecation")
static LDValue getConfigurationData(LDConfig config) {
ObjectBuilder builder = LDValue.buildObject();

// Add the top-level properties that are not specific to a particular component type.
builder.put("customEventsURI", !(LDConfig.DEFAULT_EVENTS_URI.equals(config.deprecatedEventsURI)));
builder.put("eventsCapacity", config.deprecatedCapacity);
builder.put("connectTimeoutMillis", config.httpConfig.connectTimeoutUnit.toMillis(config.httpConfig.connectTimeout));
builder.put("socketTimeoutMillis", config.httpConfig.socketTimeoutUnit.toMillis(config.httpConfig.socketTimeout));
builder.put("eventsFlushIntervalMillis", config.deprecatedFlushInterval * 1000);
builder.put("usingProxy", config.httpConfig.proxy != null);
builder.put("usingProxyAuthenticator", config.httpConfig.proxyAuthenticator != null);
builder.put("offline", config.offline);
builder.put("allAttributesPrivate", config.deprecatedAllAttributesPrivate);
builder.put("startWaitMillis", config.startWaitMillis);
builder.put("samplingInterval", config.deprecatedSamplingInterval);
builder.put("userKeysCapacity", config.deprecatedUserKeysCapacity);
builder.put("userKeysFlushIntervalMillis", config.deprecatedUserKeysFlushInterval * 1000);
builder.put("inlineUsersInEvents", config.deprecatedInlineUsersInEvents);
builder.put("diagnosticRecordingIntervalMillis", config.diagnosticRecordingIntervalMillis);

// Allow each pluggable component to describe its own relevant properties.
mergeComponentProperties(builder, config.deprecatedFeatureStore, config, "dataStoreType");
mergeComponentProperties(builder,
config.dataStoreFactory == null ? Components.inMemoryDataStore() : config.dataStoreFactory,
config, "dataStoreType");
mergeComponentProperties(builder,
config.dataSourceFactory == null ? Components.defaultUpdateProcessor() : config.dataSourceFactory,
config, null);
return builder.build();
}

// Attempts to add relevant configuration properties, if any, from a customizable component:
// - If the component does not implement DiagnosticDescription, set the defaultPropertyName property to its class name.
// - If it does implement DiagnosticDescription, call its describeConfiguration() method to get a value.
// - If the value is a string, then set the defaultPropertyName property to that value.
// - If the value is an object, then copy all of its properties as long as they are ones we recognize
// and have the expected type.
private static void mergeComponentProperties(ObjectBuilder builder, Object component, LDConfig config, String defaultPropertyName) {
if (component == null) {
return;
}
if (!(component instanceof DiagnosticDescription)) {
if (defaultPropertyName != null) {
builder.put(defaultPropertyName, LDValue.of(component.getClass().getSimpleName()));
}
return;
}
LDValue componentDesc = ((DiagnosticDescription)component).describeConfiguration(config);
if (componentDesc == null || componentDesc.isNull()) {
return;
}
if (componentDesc.isString() && defaultPropertyName != null) {
builder.put(defaultPropertyName, componentDesc);
} else if (componentDesc.getType() == LDValueType.OBJECT) {
for (String key: componentDesc.keys()) {
for (ConfigProperty prop: ConfigProperty.values()) {
if (prop.name.equals(key)) {
LDValue value = componentDesc.get(key);
if (value.isNull() || value.getType() == prop.type) {
builder.put(key, value);
}
}
}
}
}
}

static class DiagnosticSdk {
final String name = "java-server-sdk";
final String version = LDClient.CLIENT_VERSION;
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/com/launchdarkly/client/InMemoryFeatureStore.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.launchdarkly.client;

import com.launchdarkly.client.interfaces.DiagnosticDescription;
import com.launchdarkly.client.value.LDValue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -12,7 +15,7 @@
* A thread-safe, versioned store for feature flags and related data based on a
* {@link HashMap}. This is the default implementation of {@link FeatureStore}.
*/
public class InMemoryFeatureStore implements FeatureStore {
public class InMemoryFeatureStore implements FeatureStore, DiagnosticDescription {
private static final Logger logger = LoggerFactory.getLogger(InMemoryFeatureStore.class);

private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
Expand Down Expand Up @@ -137,4 +140,9 @@ public boolean initialized() {
public void close() throws IOException {
return;
}

@Override
public LDValue describeConfiguration(LDConfig config) {
return LDValue.of("memory");
}
}
Loading

0 comments on commit 067eb74

Please sign in to comment.