diff --git a/dd-trace/src/main/java/com/datadoghq/trace/DDTraceConfig.java b/dd-trace/src/main/java/com/datadoghq/trace/DDTraceConfig.java new file mode 100644 index 0000000000..9e45be4cd7 --- /dev/null +++ b/dd-trace/src/main/java/com/datadoghq/trace/DDTraceConfig.java @@ -0,0 +1,63 @@ +package com.datadoghq.trace; + +import java.util.Properties; + +/** + * Config gives priority to system properties and falls back to environment variables. + * + *

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(".", "_"); + } +} diff --git a/dd-trace/src/main/java/com/datadoghq/trace/DDTracer.java b/dd-trace/src/main/java/com/datadoghq/trace/DDTracer.java index f32e078139..56d1a8a029 100644 --- a/dd-trace/src/main/java/com/datadoghq/trace/DDTracer.java +++ b/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> spanContextDecorators = new HashMap<>(); @@ -45,9 +47,39 @@ public class DDTracer extends ThreadLocalActiveSpanSource implements io.opentrac private final CodecRegistry registry; private final Map 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 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 = diff --git a/dd-trace/src/main/java/com/datadoghq/trace/resolver/DDTracerFactory.java b/dd-trace/src/main/java/com/datadoghq/trace/resolver/DDTracerFactory.java index bd0b929379..620af20fcf 100644 --- a/dd-trace/src/main/java/com/datadoghq/trace/resolver/DDTracerFactory.java +++ b/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) { diff --git a/dd-trace/src/main/java/com/datadoghq/trace/resolver/DDTracerResolver.java b/dd-trace/src/main/java/com/datadoghq/trace/resolver/DDTracerResolver.java index 39cbbb2035..875865e8e3 100644 --- a/dd-trace/src/main/java/com/datadoghq/trace/resolver/DDTracerResolver.java +++ b/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(); + 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(); + } } } diff --git a/dd-trace/src/main/java/com/datadoghq/trace/sampling/RateSampler.java b/dd-trace/src/main/java/com/datadoghq/trace/sampling/RateSampler.java index 7108301ac6..a799947898 100644 --- a/dd-trace/src/main/java/com/datadoghq/trace/sampling/RateSampler.java +++ b/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 + " }"; + } } diff --git a/dd-trace/src/main/java/com/datadoghq/trace/sampling/Sampler.java b/dd-trace/src/main/java/com/datadoghq/trace/sampling/Sampler.java index 285ac9b392..a811e1bd28 100644 --- a/dd-trace/src/main/java/com/datadoghq/trace/sampling/Sampler.java +++ b/dd-trace/src/main/java/com/datadoghq/trace/sampling/Sampler.java @@ -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() {} + } } diff --git a/dd-trace/src/main/java/com/datadoghq/trace/writer/DDAgentWriter.java b/dd-trace/src/main/java/com/datadoghq/trace/writer/DDAgentWriter.java index 7d214f033c..930adf726e 100644 --- a/dd-trace/src/main/java/com/datadoghq/trace/writer/DDAgentWriter.java +++ b/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 { diff --git a/dd-trace/src/main/java/com/datadoghq/trace/writer/DDApi.java b/dd-trace/src/main/java/com/datadoghq/trace/writer/DDApi.java index ab3123d17e..2d0c09b2cd 100644 --- a/dd-trace/src/main/java/com/datadoghq/trace/writer/DDApi.java +++ b/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 + " }"; + } } diff --git a/dd-trace/src/main/java/com/datadoghq/trace/writer/Writer.java b/dd-trace/src/main/java/com/datadoghq/trace/writer/Writer.java index 3d5b786c44..38b9c29b33 100644 --- a/dd-trace/src/main/java/com/datadoghq/trace/writer/Writer.java +++ b/dd-trace/src/main/java/com/datadoghq/trace/writer/Writer.java @@ -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() {} + } } diff --git a/dd-trace/src/test/groovy/com/datadoghq/trace/DDTraceConfigTest.groovy b/dd-trace/src/test/groovy/com/datadoghq/trace/DDTraceConfigTest.groovy new file mode 100644 index 0000000000..f6b7c82aad --- /dev/null +++ b/dd-trace/src/test/groovy/com/datadoghq/trace/DDTraceConfigTest.groovy @@ -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() + + 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 } }" + } +}