Allow config to pull from env vars and sys props
ADD
dd-trace/src/main/java/com/datadoghq/trace/DDTraceConfig.java
+package com.datadoghq.trace;
+
+import java.util.Properties;
+
+/**
+ * Config gives priority to system properties and falls back to environment variables.
+ *
+ * <p>System properties are {@link DDTraceConfig#PREFIX}'ed. Environment variables are the same as
+ * the system property, but uppercased with '.' -> '_'.
+ */
+public class DDTraceConfig extends Properties {
+ /** Config keys bel */
+ private static final String PREFIX = "dd.";
+
+ public static final String SERVICE_NAME = "service.name";
+ public static final String WRITER_TYPE = "writer.type";
+ public static final String AGENT_HOST = "agent.host";
+ public static final String AGENT_PORT = "agent.port";
+ public static final String SAMPLER_TYPE = "sampler.type";
+ public static final String SAMPLER_RATE = "sampler.rate";
+
+ private final String serviceName = getPropOrEnv(PREFIX + SERVICE_NAME);
+ private final String writerType = getPropOrEnv(PREFIX + WRITER_TYPE);
+ private final String agentHost = getPropOrEnv(PREFIX + AGENT_HOST);
+ private final String agentPort = getPropOrEnv(PREFIX + AGENT_PORT);
+ private final String samplerType = getPropOrEnv(PREFIX + SAMPLER_TYPE);
+ private final String samplerRate = getPropOrEnv(PREFIX + SAMPLER_RATE);
+
+ public DDTraceConfig() {
+ super();
+
+ final Properties defaults = new Properties();
+ defaults.setProperty(SERVICE_NAME, DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME);
+ super.defaults = defaults;
+
+ final Properties baseValues = new Properties(defaults);
+ setIfNotNull(SERVICE_NAME, serviceName);
+ setIfNotNull(WRITER_TYPE, writerType);
+ setIfNotNull(AGENT_HOST, agentHost);
+ setIfNotNull(AGENT_PORT, agentPort);
+ setIfNotNull(SAMPLER_TYPE, samplerType);
+ setIfNotNull(SAMPLER_RATE, samplerRate);
+ }
+
+ public DDTraceConfig(final String serviceName) {
+ super();
+ put(SERVICE_NAME, serviceName);
+ }
+
+ private void setIfNotNull(final String key, final String value) {
+ if (value != null) {
+ setProperty(key, value);
+ }
+ }
+
+ private String getPropOrEnv(final String name) {
+ return System.getProperty(name, System.getenv(propToEnvName(name)));
+ }
+
+ static String propToEnvName(final String name) {
+ return name.toUpperCase().replace(".", "_");
+ }
+}

MODIFY
dd-trace/src/main/java/com/datadoghq/trace/DDTracer.java
dd-trace/src/main/java/com/datadoghq/trace/DDTracer.java
@@ -3,9 +3,10 @@ package com.datadoghq.trace;
import com.datadoghq.trace.integration.AbstractDecorator;
import com.datadoghq.trace.propagation.Codec;
import com.datadoghq.trace.propagation.HTTPCodec;
+import com.datadoghq.trace.resolver.DDDecoratorsFactory;
import com.datadoghq.trace.sampling.AllSampler;
import com.datadoghq.trace.sampling.Sampler;
-import com.datadoghq.trace.writer.LoggingWriter;
+import com.datadoghq.trace.writer.DDAgentWriter;
import com.datadoghq.trace.writer.Writer;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.opentracing.ActiveSpan;
@@ -19,6 +20,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.ThreadLocalRandom;
import lombok.extern.slf4j.Slf4j;
@@ -28,16 +30,16 @@ import lombok.extern.slf4j.Slf4j;
public class DDTracer extends ThreadLocalActiveSpanSource implements io.opentracing.Tracer {
public static final String UNASSIGNED_DEFAULT_SERVICE_NAME = "unnamed-java-app";
- public static final Writer UNASSIGNED_WRITER = new LoggingWriter();
+ public static final Writer UNASSIGNED_WRITER = new DDAgentWriter();
public static final Sampler UNASSIGNED_SAMPLER = new AllSampler();
/** Writer is an charge of reporting traces and spans to the desired endpoint */
- private final Writer writer;
+ final Writer writer;
/** Sampler defines the sampling policy in order to reduce the number of traces for instance */
- private final Sampler sampler;
+ final Sampler sampler;
/** Default service name if none provided on the trace or span */
- private final String defaultServiceName;
+ final String serviceName;
/** Span context decorators */
private final Map<String, List<AbstractDecorator>> spanContextDecorators = new HashMap<>();
@@ -45,32 +47,47 @@ public class DDTracer extends ThreadLocalActiveSpanSource implements io.opentrac
private final CodecRegistry registry;
private final Map<String, Service> services = new HashMap<>();
- /** Default constructor, trace/spans are logged, no trace/span dropped */
+ /** By default, report to local agent and collect all traces. */
public DDTracer() {
- this(UNASSIGNED_WRITER);
+ this(new DDTraceConfig());
}
- public DDTracer(final Writer writer) {
- this(writer, new AllSampler());
+ public DDTracer(final String serviceName) {
+ this(new DDTraceConfig(serviceName));
}
- public DDTracer(final Writer writer, final Sampler sampler) {
- this(UNASSIGNED_DEFAULT_SERVICE_NAME, writer, sampler);
+ public DDTracer(final Properties config) {
+ this(
+ config.getProperty(DDTraceConfig.SERVICE_NAME),
+ Writer.Builder.forConfig(config),
+ Sampler.Builder.forConfig(config));
+ log.debug("Using config: {}", config);
+
+ // Create decorators from resource files
+ final List<AbstractDecorator> decorators = DDDecoratorsFactory.createFromResources();
+ for (final AbstractDecorator decorator : decorators) {
+ log.debug("Loading decorator: {}", decorator.getClass().getSimpleName());
+ addDecorator(decorator);
+ }
}
- public DDTracer(final String defaultServiceName, final Writer writer, final Sampler sampler) {
- this.defaultServiceName = defaultServiceName;
+ public DDTracer(final String serviceName, final Writer writer, final Sampler sampler) {
+ this.serviceName = serviceName;
this.writer = writer;
this.writer.start();
this.sampler = sampler;
registry = new CodecRegistry();
registry.register(Format.Builtin.HTTP_HEADERS, new HTTPCodec());
registry.register(Format.Builtin.TEXT_MAP, new HTTPCodec());
- log.debug(
- "New tracer instance, default-service={}, writer={}, sampler={}",
- defaultServiceName,
- writer.getClass().getSimpleName(),
- sampler.getClass().getSimpleName());
+ log.info("New instance: {}", this);
+ }
+
+ public DDTracer(final Writer writer) {
+ this(writer, new AllSampler());
+ }
+
+ public DDTracer(final Writer writer, final Sampler sampler) {
+ this(UNASSIGNED_DEFAULT_SERVICE_NAME, writer, sampler);
}
/**
@@ -147,7 +164,15 @@ public class DDTracer extends ThreadLocalActiveSpanSource implements io.opentrac
@Override
public String toString() {
- return "DDTracer{" + "writer=" + writer + ", sampler=" + sampler + '}';
+ return "DDTracer-"
+ + Integer.toHexString(hashCode())
+ + "{ service-name="
+ + serviceName
+ + ", writer="
+ + writer
+ + ", sampler="
+ + sampler
+ + '}';
}
/**
@@ -368,7 +393,7 @@ public class DDTracer extends ThreadLocalActiveSpanSource implements io.opentrac
}
if (serviceName == null) {
- serviceName = defaultServiceName;
+ serviceName = DDTracer.this.serviceName;
}
final String operationName =

MODIFY
dd-trace/src/main/java/com/datadoghq/trace/resolver/DDTracerFactory.java
dd-trace/src/main/java/com/datadoghq/trace/resolver/DDTracerFactory.java
@@ -33,7 +33,7 @@ public class DDTracerFactory {
* @param config
* @return the corresponding tracer
*/
- public static DDTracer create(final TracerConfig config) {
+ static DDTracer create(final TracerConfig config) {
final String defaultServiceName =
config.getDefaultServiceName() != null
? config.getDefaultServiceName()
@@ -88,11 +88,15 @@ public class DDTracerFactory {
return new DDTracer(defaultServiceName, writer, sampler);
}
+ @Deprecated
public static DDTracer createFromConfigurationFile() {
final TracerConfig tracerConfig =
FactoryUtils.loadConfigFromFilePropertyOrResource(
SYSTEM_PROPERTY_CONFIG_PATH, CONFIG_PATH, TracerConfig.class);
+ log.warn("DDTracerFactory is deprecated and will be removed in the next release.");
+ log.warn("Use the constructor on DDTrace directly and env vars/sys props for config.");
+
DDTracer tracer = null;
log.trace("Tracer configuration: \n{}", tracerConfig);
if (tracerConfig == null) {

MODIFY
dd-trace/src/main/java/com/datadoghq/trace/resolver/DDTracerResolver.java
dd-trace/src/main/java/com/datadoghq/trace/resolver/DDTracerResolver.java
@@ -29,11 +29,16 @@ public class DDTracerResolver extends TracerResolver {
protected Tracer resolve() {
log.info("Creating the Datadog Tracer from the resolver");
- // Find a resource file named dd-trace.yml
- DDTracer tracer = null;
- // Create tracer from resource files
- tracer = DDTracerFactory.createFromConfigurationFile();
-
- return tracer;
+ final TracerConfig tracerConfig =
+ FactoryUtils.loadConfigFromFilePropertyOrResource(
+ DDTracerFactory.SYSTEM_PROPERTY_CONFIG_PATH,
+ DDTracerFactory.CONFIG_PATH,
+ TracerConfig.class);
+
+ if (tracerConfig != null) {
+ return DDTracerFactory.createFromConfigurationFile();
+ } else {
+ return new DDTracer();
+ }
}
}

MODIFY
dd-trace/src/main/java/com/datadoghq/trace/sampling/RateSampler.java
dd-trace/src/main/java/com/datadoghq/trace/sampling/RateSampler.java
@@ -17,6 +17,10 @@ public class RateSampler extends AbstractSampler {
/** The sample rate used */
private final double sampleRate;
+ public RateSampler(final String sampleRate) {
+ this(sampleRate == null ? 1 : Double.valueOf(sampleRate));
+ }
+
/**
* Build an instance of the sampler. The Sample rate is fixed for each instance.
*
@@ -45,4 +49,9 @@ public class RateSampler extends AbstractSampler {
public double getSampleRate() {
return this.sampleRate;
}
+
+ @Override
+ public String toString() {
+ return "RateSampler { sampleRate=" + sampleRate + " }";
+ }
}

MODIFY
dd-trace/src/main/java/com/datadoghq/trace/sampling/Sampler.java
dd-trace/src/main/java/com/datadoghq/trace/sampling/Sampler.java
package com.datadoghq.trace.sampling;
import com.datadoghq.trace.DDBaseSpan;
+import com.datadoghq.trace.DDTraceConfig;
+import com.datadoghq.trace.DDTracer;
+import java.util.Properties;
/** Main interface to sample a collection of traces. */
public interface Sampler {
+ static final String ALL_SAMPLER_TYPE = AllSampler.class.getSimpleName();
+ static final String RATE_SAMPLER_TYPE = RateSampler.class.getSimpleName();
/**
* Sample a collection of traces based on the parent span
@@ -12,4 +17,26 @@ public interface Sampler {
* @return true when the trace/spans has to be reported/written
*/
boolean sample(DDBaseSpan<?> span);
+
+ final class Builder {
+ public static Sampler forConfig(final Properties config) {
+ final Sampler sampler;
+
+ if (config != null) {
+ final String configuredType = config.getProperty(DDTraceConfig.SAMPLER_TYPE);
+ if (RATE_SAMPLER_TYPE.equals(configuredType)) {
+ sampler = new RateSampler(config.getProperty(DDTraceConfig.SAMPLER_RATE));
+ } else if (ALL_SAMPLER_TYPE.equals(configuredType)) {
+ sampler = new AllSampler();
+ } else {
+ sampler = DDTracer.UNASSIGNED_SAMPLER;
+ }
+ } else {
+ sampler = DDTracer.UNASSIGNED_SAMPLER;
+ }
+ return sampler;
+ }
+
+ private Builder() {}
+ }
}

MODIFY
dd-trace/src/main/java/com/datadoghq/trace/writer/DDAgentWriter.java
dd-trace/src/main/java/com/datadoghq/trace/writer/DDAgentWriter.java
@@ -140,6 +140,11 @@ public class DDAgentWriter implements Writer {
}
}
+ @Override
+ public String toString() {
+ return "DDAgentWriter { api=" + api + " }";
+ }
+
/** Infinite tasks blocking until some spans come in the blocking queue. */
class TracesSendingTask implements Runnable {

MODIFY
dd-trace/src/main/java/com/datadoghq/trace/writer/DDApi.java
dd-trace/src/main/java/com/datadoghq/trace/writer/DDApi.java
@@ -126,4 +126,9 @@ public class DDApi {
httpCon.setRequestProperty("Datadog-Meta-Tracer-Version", DDTraceInfo.VERSION);
return httpCon;
}
+
+ @Override
+ public String toString() {
+ return "DDApi { tracesEndpoint=" + tracesEndpoint + " }";
+ }
}

MODIFY
dd-trace/src/main/java/com/datadoghq/trace/writer/Writer.java
dd-trace/src/main/java/com/datadoghq/trace/writer/Writer.java
package com.datadoghq.trace.writer;
import com.datadoghq.trace.DDBaseSpan;
+import com.datadoghq.trace.DDTraceConfig;
+import com.datadoghq.trace.DDTracer;
import com.datadoghq.trace.Service;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
/** A writer is responsible to send collected spans to some place */
public interface Writer {
+ static final String DD_AGENT_WRITER_TYPE = DDAgentWriter.class.getSimpleName();
+ static final String LOGGING_WRITER_TYPE = LoggingWriter.class.getSimpleName();
/**
* Write a trace represented by the entire list of all the finished spans
@@ -30,4 +35,31 @@ public interface Writer {
* connections and tasks
*/
void close();
+
+ final class Builder {
+ public static Writer forConfig(final Properties config) {
+ final Writer writer;
+
+ if (config != null) {
+ final String configuredType = config.getProperty(DDTraceConfig.WRITER_TYPE);
+ if (DD_AGENT_WRITER_TYPE.equals(configuredType)) {
+ writer =
+ new DDAgentWriter(
+ new DDApi(
+ config.getProperty(DDTraceConfig.AGENT_HOST),
+ Integer.parseInt(config.getProperty(DDTraceConfig.AGENT_PORT))));
+ } else if (LOGGING_WRITER_TYPE.equals(configuredType)) {
+ writer = new LoggingWriter();
+ } else {
+ writer = DDTracer.UNASSIGNED_WRITER;
+ }
+ } else {
+ writer = DDTracer.UNASSIGNED_WRITER;
+ }
+
+ return writer;
+ }
+
+ private Builder() {}
+ }
}

ADD
dd-trace/src/test/groovy/com/datadoghq/trace/DDTraceConfigTest.groovy
+package com.datadoghq.trace
+
+import com.datadoghq.trace.sampling.AllSampler
+import com.datadoghq.trace.sampling.RateSampler
+import com.datadoghq.trace.writer.DDAgentWriter
+import com.datadoghq.trace.writer.ListWriter
+import com.datadoghq.trace.writer.LoggingWriter
+import spock.lang.Specification
+
+import java.lang.reflect.Field
+import java.lang.reflect.Modifier
+
+import static com.datadoghq.trace.DDTraceConfig.*
+
+class DDTraceConfigTest extends Specification {
+ static originalEnvMap
+ static overrideEnvMap = new HashMap<String, String>()
+
+ def setupSpec() {
+ def envMapField = ProcessEnvironment.getDeclaredField("theUnmodifiableEnvironment")
+ envMapField.setAccessible(true)
+
+ Field modifiersField = Field.getDeclaredField("modifiers")
+ modifiersField.setAccessible(true)
+ modifiersField.setInt(envMapField, envMapField.getModifiers() & ~Modifier.FINAL)
+
+ originalEnvMap = envMapField.get(null)
+ overrideEnvMap.putAll(originalEnvMap)
+ envMapField.set(null, overrideEnvMap)
+ }
+
+ def cleanupSpec() {
+ def envMapField = ProcessEnvironment.getDeclaredField("theUnmodifiableEnvironment")
+ envMapField.setAccessible(true)
+
+ Field modifiersField = Field.getDeclaredField("modifiers")
+ modifiersField.setAccessible(true)
+ modifiersField.setInt(envMapField, envMapField.getModifiers() & ~Modifier.FINAL)
+
+ originalEnvMap = envMapField.get(null)
+ envMapField.set(null, originalEnvMap)
+ }
+
+ def setup() {
+ overrideEnvMap.clear()
+ overrideEnvMap.putAll(originalEnvMap)
+
+ System.clearProperty(PREFIX + SERVICE_NAME)
+ System.clearProperty(PREFIX + WRITER_TYPE)
+ System.clearProperty(PREFIX + AGENT_HOST)
+ System.clearProperty(PREFIX + AGENT_PORT)
+ System.clearProperty(PREFIX + SAMPLER_TYPE)
+ System.clearProperty(PREFIX + SAMPLER_RATE)
+ }
+
+ def "verify env override"() {
+ setup:
+ overrideEnvMap.put("SOME_RANDOM_ENTRY", "asdf")
+
+ expect:
+ System.getenv("SOME_RANDOM_ENTRY") == "asdf"
+ }
+
+ def "verify defaults"() {
+ when:
+ def config = new DDTraceConfig()
+
+ then:
+ config.getProperty(SERVICE_NAME) == "unnamed-java-app"
+ config.getProperty(WRITER_TYPE) == null
+ config.getProperty(AGENT_HOST) == null
+ config.getProperty(AGENT_PORT) == null
+ config.getProperty(SAMPLER_TYPE) == null
+ config.getProperty(SAMPLER_RATE) == null
+
+ when:
+ config = new DDTraceConfig("A different service name")
+
+ then:
+ config.getProperty(SERVICE_NAME) == "A different service name"
+ config.getProperty(WRITER_TYPE) == null
+ config.getProperty(AGENT_HOST) == null
+ config.getProperty(AGENT_PORT) == null
+ config.getProperty(SAMPLER_TYPE) == null
+ config.getProperty(SAMPLER_RATE) == null
+ }
+
+ def "specify overrides via system properties"() {
+ when:
+ System.setProperty(PREFIX + SERVICE_NAME, "something else")
+ System.setProperty(PREFIX + WRITER_TYPE, LoggingWriter.simpleName)
+ System.setProperty(PREFIX + SAMPLER_TYPE, RateSampler.simpleName)
+ System.setProperty(PREFIX + SAMPLER_RATE, ".5")
+ def tracer = new DDTracer()
+
+ then:
+ tracer.serviceName == "something else"
+ tracer.writer instanceof LoggingWriter
+ tracer.sampler.toString() == "RateSampler { sampleRate=0.5 }"
+ }
+
+ def "specify overrides via env vars"() {
+ when:
+ overrideEnvMap.put(propToEnvName(PREFIX + SERVICE_NAME), "still something else")
+ overrideEnvMap.put(propToEnvName(PREFIX + WRITER_TYPE), LoggingWriter.simpleName)
+ overrideEnvMap.put(propToEnvName(PREFIX + SAMPLER_TYPE), AllSampler.simpleName)
+ def tracer = new DDTracer()
+
+ then:
+ tracer.serviceName == "still something else"
+ tracer.writer instanceof LoggingWriter
+ tracer.sampler instanceof AllSampler
+ }
+
+ def "sys props override env vars"() {
+ when:
+ overrideEnvMap.put(propToEnvName(PREFIX + SERVICE_NAME), "still something else")
+ overrideEnvMap.put(propToEnvName(PREFIX + WRITER_TYPE), ListWriter.simpleName)
+ overrideEnvMap.put(propToEnvName(PREFIX + SAMPLER_TYPE), AllSampler.simpleName)
+
+ System.setProperty(PREFIX + SERVICE_NAME, "what we actually want")
+ System.setProperty(PREFIX + WRITER_TYPE, DDAgentWriter.simpleName)
+ System.setProperty(PREFIX + AGENT_HOST, "somewhere")
+ System.setProperty(PREFIX + AGENT_PORT, "9999")
+ System.setProperty(PREFIX + SAMPLER_TYPE, RateSampler.simpleName)
+ System.setProperty(PREFIX + SAMPLER_RATE, ".9")
+
+ def tracer = new DDTracer()
+
+ then:
+ tracer.serviceName == "what we actually want"
+ tracer.writer.toString() == "DDAgentWriter { api=DDApi { tracesEndpoint=http://somewhere:9999/v0.3/traces } }"
+ tracer.sampler.toString() == "RateSampler { sampleRate=0.9 }"
+ }
+
+ def "verify defaults on tracer"() {
+ when:
+ def tracer = new DDTracer()
+
+ then:
+ tracer.serviceName == "unnamed-java-app"
+ tracer.sampler instanceof AllSampler
+ tracer.writer.toString() == "DDAgentWriter { api=DDApi { tracesEndpoint=http://localhost:8126/v0.3/traces } }"
+ }
+}

Allow config to pull from env vars and sys props