Allow config to pull from env vars and sys props

This commit is contained in:
Tyler Benson 2017-12-21 13:45:12 -08:00
parent d036512318
commit 0fb057dbcc
10 changed files with 350 additions and 30 deletions

View File

@ -0,0 +1,63 @@
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(".", "_");
}
}

View File

@ -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,9 +47,39 @@ 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 String serviceName) {
this(new DDTraceConfig(serviceName));
}
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 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.info("New instance: {}", this);
}
public DDTracer(final Writer writer) {
@ -58,21 +90,6 @@ public class DDTracer extends ThreadLocalActiveSpanSource implements io.opentrac
this(UNASSIGNED_DEFAULT_SERVICE_NAME, writer, sampler);
}
public DDTracer(final String defaultServiceName, final Writer writer, final Sampler sampler) {
this.defaultServiceName = defaultServiceName;
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());
}
/**
* Returns the list of span context decorators
*
@ -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 =

View File

@ -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) {

View File

@ -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();
final TracerConfig tracerConfig =
FactoryUtils.loadConfigFromFilePropertyOrResource(
DDTracerFactory.SYSTEM_PROPERTY_CONFIG_PATH,
DDTracerFactory.CONFIG_PATH,
TracerConfig.class);
return tracer;
if (tracerConfig != null) {
return DDTracerFactory.createFromConfigurationFile();
} else {
return new DDTracer();
}
}
}

View File

@ -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 + " }";
}
}

View File

@ -1,9 +1,14 @@
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() {}
}
}

View File

@ -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 {

View File

@ -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 + " }";
}
}

View File

@ -1,12 +1,17 @@
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() {}
}
}

View File

@ -0,0 +1,145 @@
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 } }"
}
}