Merge pull request #496 from DataDog/mar-kolya/move-tracer-config-to-api

Refactor DDTraceConfig to a Config we can use globally
This commit is contained in:
Nikolay Martynov 2018-09-18 09:14:12 -04:00 committed by GitHub
commit c3f7630387
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 591 additions and 480 deletions

View File

@ -1,6 +1,15 @@
apply from: "${rootDir}/gradle/java.gradle"
apply from: "${rootDir}/gradle/publish.gradle"
// These are tested outside of this module since this module mainly just defines 'API'
excludedClassesConverage += [
'datadog.trace.api.DDSpanTypes',
'datadog.trace.api.DDTraceApiInfo',
'datadog.trace.api.GlobalTracer*',
'datadog.trace.api.CorrelationIdentifier',
'datadog.trace.api.DDTags'
]
description = 'dd-trace-api'
dependencies {
compile deps.slf4j

View File

@ -0,0 +1,162 @@
package datadog.trace.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
/**
* Config gives priority to system properties and falls back to environment variables. It also
* includes default values to ensure a valid config.
*
* <p>
*
* <p>System properties are {@link Config#PREFIX}'ed. Environment variables are the same as the
* system property, but uppercased with '.' -> '_'.
*/
@Slf4j
@ToString(includeFieldNames = true)
public class Config {
/** Config keys below */
private static final String PREFIX = "dd.";
private static final Config INSTANCE = new Config();
public static final String SERVICE_NAME = "service.name";
public static final String SERVICE_MAPPING = "service.mapping";
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 PRIORITY_SAMPLING = "priority.sampling";
public static final String TRACE_RESOLVER_ENABLED = "trace.resolver.enabled";
public static final String SPAN_TAGS = "trace.span.tags";
public static final String HEADER_TAGS = "trace.header.tags";
public static final String DEFAULT_SERVICE_NAME = "unnamed-java-app";
public static final String DD_AGENT_WRITER_TYPE = "DDAgentWriter";
public static final String LOGGING_WRITER_TYPE = "LoggingWriter";
public static final String DEFAULT_AGENT_WRITER_TYPE = DD_AGENT_WRITER_TYPE;
public static final String DEFAULT_AGENT_HOST = "localhost";
public static final int DEFAULT_AGENT_PORT = 8126;
private static final String DEFAULT_PRIORITY_SAMPLING_ENABLED = "false";
private static final String DEFAULT_TRACE_RESOLVER_ENABLED = "true";
@Getter private final String serviceName;
@Getter private final String writerType;
@Getter private final String agentHost;
@Getter private final int agentPort;
@Getter private final boolean prioritySamplingEnabled;
@Getter private final boolean traceResolverEnabled;
@Getter private final Map<String, String> serviceMapping;
@Getter private final Map<String, String> spanTags;
@Getter private final Map<String, String> headerTags;
// Read order: System Properties -> Env Variables, [-> default value]
// Visible for testing
Config() {
serviceName = getSettingFromEnvironment(SERVICE_NAME, DEFAULT_SERVICE_NAME);
writerType = getSettingFromEnvironment(WRITER_TYPE, DEFAULT_AGENT_WRITER_TYPE);
agentHost = getSettingFromEnvironment(AGENT_HOST, DEFAULT_AGENT_HOST);
agentPort =
Integer.valueOf(
getSettingFromEnvironment(AGENT_PORT, Integer.toString(DEFAULT_AGENT_PORT)));
prioritySamplingEnabled =
Boolean.valueOf(
getSettingFromEnvironment(PRIORITY_SAMPLING, DEFAULT_PRIORITY_SAMPLING_ENABLED));
traceResolverEnabled =
Boolean.valueOf(
getSettingFromEnvironment(TRACE_RESOLVER_ENABLED, DEFAULT_TRACE_RESOLVER_ENABLED));
serviceMapping = getMapSettingFromEnvironment(SERVICE_MAPPING, null);
spanTags = getMapSettingFromEnvironment(SPAN_TAGS, null);
headerTags = getMapSettingFromEnvironment(HEADER_TAGS, null);
}
// Read order: Properties -> Parent
private Config(final Properties properties, final Config parent) {
serviceName = properties.getProperty(SERVICE_NAME, parent.serviceName);
writerType = properties.getProperty(WRITER_TYPE, parent.writerType);
agentHost = properties.getProperty(AGENT_HOST, parent.agentHost);
agentPort =
Integer.valueOf(properties.getProperty(AGENT_PORT, Integer.toString(parent.agentPort)));
prioritySamplingEnabled =
Boolean.valueOf(
properties.getProperty(
PRIORITY_SAMPLING, Boolean.toString(parent.prioritySamplingEnabled)));
traceResolverEnabled =
Boolean.valueOf(
properties.getProperty(
TRACE_RESOLVER_ENABLED, Boolean.toString(parent.traceResolverEnabled)));
serviceMapping = getPropertyMapValue(properties, SERVICE_MAPPING, parent.serviceMapping);
spanTags = getPropertyMapValue(properties, SPAN_TAGS, parent.spanTags);
headerTags = getPropertyMapValue(properties, HEADER_TAGS, parent.headerTags);
}
private static String getSettingFromEnvironment(final String name, final String defaultValue) {
final String completeName = PREFIX + name;
final String value =
System.getProperties()
.getProperty(completeName, System.getenv(propertyToEnvironmentName(completeName)));
return value == null ? defaultValue : value;
}
private static Map<String, String> getMapSettingFromEnvironment(
final String name, final String defaultValue) {
return parseMap(getSettingFromEnvironment(name, defaultValue), PREFIX + name);
}
private static String propertyToEnvironmentName(final String name) {
return name.toUpperCase().replace(".", "_");
}
private static Map<String, String> getPropertyMapValue(
final Properties properties, final String name, final Map<String, String> defaultValue) {
final String value = properties.getProperty(name);
return value == null ? defaultValue : parseMap(value, name);
}
private static Map<String, String> parseMap(final String str, final String settingName) {
if (str == null || str.trim().isEmpty()) {
return Collections.emptyMap();
}
if (!str.matches("(([^,:]+:[^,:]*,)*([^,:]+:[^,:]*),?)?")) {
log.warn(
"Invalid config for {}: '{}'. Must match 'key1:value1,key2:value2'.", settingName, str);
return Collections.emptyMap();
}
final String[] tokens = str.split(",", -1);
final Map<String, String> map = new HashMap<>(tokens.length + 1, 1f);
for (final String token : tokens) {
final String[] keyValue = token.split(":", -1);
if (keyValue.length == 2) {
final String key = keyValue[0].trim();
final String value = keyValue[1].trim();
if (value.length() <= 0) {
log.warn("Ignoring empty value for key '{}' in config for {}", key, settingName);
continue;
}
map.put(key, value);
}
}
return Collections.unmodifiableMap(map);
}
public static Config get() {
return INSTANCE;
}
public static Config get(final Properties properties) {
if (properties == null || properties.isEmpty()) {
return INSTANCE;
} else {
return new Config(properties, INSTANCE);
}
}
}

View File

@ -0,0 +1,183 @@
package datadog.trace.api
import org.junit.Rule
import org.junit.contrib.java.lang.system.EnvironmentVariables
import org.junit.contrib.java.lang.system.RestoreSystemProperties
import spock.lang.Specification
import static Config.AGENT_HOST
import static Config.AGENT_PORT
import static Config.HEADER_TAGS
import static Config.PREFIX
import static Config.SERVICE_MAPPING
import static Config.SERVICE_NAME
import static Config.SPAN_TAGS
import static Config.WRITER_TYPE
class ConfigTest extends Specification {
@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties()
@Rule
public final EnvironmentVariables environmentVariables = new EnvironmentVariables()
private static final DD_SERVICE_NAME_ENV = "DD_SERVICE_NAME"
private static final DD_WRITER_TYPE_ENV = "DD_WRITER_TYPE"
private static final DD_SERVICE_MAPPING_ENV = "DD_SERVICE_MAPPING"
private static final DD_SPAN_TAGS_ENV = "DD_SPAN_TAGS"
private static final DD_HEADER_TAGS_ENV = "DD_HEADER_TAGS"
def "verify defaults"() {
when:
def config = Config.get()
then:
config.serviceName == "unnamed-java-app"
config.serviceMapping == [:]
config.writerType == "DDAgentWriter"
config.prioritySamplingEnabled == false
config.agentHost == "localhost"
config.agentPort == 8126
config.spanTags == [:]
config.toString().contains("unnamed-java-app")
}
def "specify overrides via system properties"() {
setup:
System.setProperty(PREFIX + SERVICE_NAME, "something else")
System.setProperty(PREFIX + WRITER_TYPE, "LoggingWriter")
when:
def config = new Config()
then:
config.serviceName == "something else"
config.writerType == "LoggingWriter"
}
def "specify overrides via env vars"() {
setup:
environmentVariables.set(DD_SERVICE_NAME_ENV, "still something else")
environmentVariables.set(DD_WRITER_TYPE_ENV, "LoggingWriter")
when:
def config = new Config()
then:
config.serviceName == "still something else"
config.writerType == "LoggingWriter"
}
def "sys props override env vars"() {
setup:
environmentVariables.set(DD_SERVICE_NAME_ENV, "still something else")
environmentVariables.set(DD_WRITER_TYPE_ENV, "LoggingWriter")
System.setProperty(PREFIX + SERVICE_NAME, "what we actually want")
System.setProperty(PREFIX + WRITER_TYPE, "DDAgentWriter")
System.setProperty(PREFIX + AGENT_HOST, "somewhere")
System.setProperty(PREFIX + AGENT_PORT, "9999")
when:
def config = new Config()
then:
config.serviceName == "what we actually want"
config.writerType == "DDAgentWriter"
config.agentHost == "somewhere"
config.agentPort == 9999
}
def "sys props override properties"() {
setup:
Properties properties = new Properties()
properties.setProperty(SERVICE_NAME, "what we actually want")
properties.setProperty(WRITER_TYPE, "DDAgentWriter")
properties.setProperty(AGENT_HOST, "somewhere")
properties.setProperty(AGENT_PORT, "9999")
when:
def config = Config.get(properties)
then:
config.serviceName == "what we actually want"
config.writerType == "DDAgentWriter"
config.agentHost == "somewhere"
config.agentPort == 9999
}
def "sys props override null properties"() {
when:
def config = Config.get(null)
then:
config.serviceName == "unnamed-java-app"
config.writerType == "DDAgentWriter"
}
def "sys props override empty properties"() {
setup:
Properties properties = new Properties()
when:
def config = Config.get(properties)
then:
config.serviceName == "unnamed-java-app"
config.writerType == "DDAgentWriter"
}
def "verify mapping configs on tracer"() {
setup:
System.setProperty(PREFIX + SERVICE_MAPPING, mapString)
System.setProperty(PREFIX + SPAN_TAGS, mapString)
System.setProperty(PREFIX + HEADER_TAGS, mapString)
when:
def config = new Config()
then:
config.serviceMapping == map
config.spanTags == map
config.headerTags == map
where:
mapString | map
"a:1, a:2, a:3" | [a: "3"]
"a:b,c:d,e:" | [a: "b", c: "d"]
// More different string variants:
"a:" | [:]
"a:a;" | [a: "a;"]
"a:1, a:2, a:3" | [a: "3"]
"a:b,c:d,e:" | [a: "b", c: "d"]
"key 1!:va|ue_1," | ["key 1!": "va|ue_1"]
" key1 :value1 ,\t key2: value2" | [key1: "value1", key2: "value2"]
// Invalid strings:
"" | [:]
"1" | [:]
"a" | [:]
"a,1" | [:]
"in:val:id" | [:]
"a:b:c:d" | [:]
"a:b,c,d" | [:]
"!a" | [:]
}
def "verify null value mapping configs on tracer"() {
setup:
environmentVariables.set(DD_SERVICE_MAPPING_ENV, mapString)
environmentVariables.set(DD_SPAN_TAGS_ENV, mapString)
environmentVariables.set(DD_HEADER_TAGS_ENV, mapString)
when:
def config = new Config()
then:
config.serviceMapping == map
config.spanTags == map
config.headerTags == map
where:
mapString | map
null | [:]
}
}

View File

@ -1,11 +1,5 @@
package datadog.opentracing;
import static datadog.trace.common.DDTraceConfig.HEADER_TAGS;
import static datadog.trace.common.DDTraceConfig.SERVICE_MAPPING;
import static datadog.trace.common.DDTraceConfig.SERVICE_NAME;
import static datadog.trace.common.DDTraceConfig.SPAN_TAGS;
import static datadog.trace.common.util.Config.parseMap;
import datadog.opentracing.decorators.AbstractDecorator;
import datadog.opentracing.decorators.DDDecoratorsFactory;
import datadog.opentracing.propagation.Codec;
@ -13,11 +7,10 @@ import datadog.opentracing.propagation.ExtractedContext;
import datadog.opentracing.propagation.HTTPCodec;
import datadog.opentracing.scopemanager.ContextualScopeManager;
import datadog.opentracing.scopemanager.ScopeContext;
import datadog.trace.api.Config;
import datadog.trace.api.interceptor.MutableSpan;
import datadog.trace.api.interceptor.TraceInterceptor;
import datadog.trace.api.sampling.PrioritySampling;
import datadog.trace.common.DDTraceConfig;
import datadog.trace.common.sampling.AllSampler;
import datadog.trace.common.sampling.RateByServiceSampler;
import datadog.trace.common.sampling.Sampler;
import datadog.trace.common.writer.DDAgentWriter;
@ -50,8 +43,6 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace.api.Tracer {
public static final String UNASSIGNED_DEFAULT_SERVICE_NAME = "unnamed-java-app";
/** Default service name if none provided on the trace or span */
final String serviceName;
/** Writer is an charge of reporting traces and spans to the desired endpoint */
@ -84,21 +75,29 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
/** By default, report to local agent and collect all traces. */
public DDTracer() {
this(new DDTraceConfig());
this(Config.get());
}
public DDTracer(final String serviceName) {
this(new DDTraceConfig(serviceName));
this(serviceName, Config.get());
}
public DDTracer(final Properties config) {
this(Config.get(config));
}
public DDTracer(final Config config) {
this(config.getServiceName(), config);
}
private DDTracer(final String serviceName, final Config config) {
this(
config.getProperty(SERVICE_NAME),
serviceName,
Writer.Builder.forConfig(config),
Sampler.Builder.forConfig(config),
parseMap(config.getProperty(SPAN_TAGS), SPAN_TAGS),
parseMap(config.getProperty(SERVICE_MAPPING), SERVICE_MAPPING),
parseMap(config.getProperty(HEADER_TAGS), HEADER_TAGS));
config.getSpanTags(),
config.getServiceMapping(),
config.getHeaderTags());
log.debug("Using config: {}", config);
}
@ -112,6 +111,20 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
Collections.<String, String>emptyMap());
}
public DDTracer(final Writer writer) {
this(Config.get(), writer);
}
public DDTracer(final Config config, final Writer writer) {
this(
config.getServiceName(),
writer,
Sampler.Builder.forConfig(config),
config.getSpanTags(),
config.getServiceMapping(),
config.getHeaderTags());
}
public DDTracer(
final String serviceName,
final Writer writer,
@ -154,8 +167,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
registerClassLoader(ClassLoader.getSystemClassLoader());
final List<AbstractDecorator> decorators =
DDDecoratorsFactory.createBuiltinDecorators(serviceNameMappings);
final List<AbstractDecorator> decorators = DDDecoratorsFactory.createBuiltinDecorators();
for (final AbstractDecorator decorator : decorators) {
log.debug("Loading decorator: {}", decorator.getClass().getSimpleName());
addDecorator(decorator);
@ -164,16 +176,6 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
log.info("New instance: {}", this);
}
public DDTracer(final Writer writer) {
this(
UNASSIGNED_DEFAULT_SERVICE_NAME,
writer,
new AllSampler(),
parseMap(new DDTraceConfig().getProperty(SPAN_TAGS), SPAN_TAGS),
parseMap(new DDTraceConfig().getProperty(SERVICE_MAPPING), SPAN_TAGS),
parseMap(new DDTraceConfig().getProperty(HEADER_TAGS), SPAN_TAGS));
}
/**
* Returns the list of span context decorators
*
@ -296,7 +298,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
@Override
public String getTraceId() {
final Span activeSpan = this.activeSpan();
final Span activeSpan = activeSpan();
if (activeSpan instanceof DDSpan) {
return ((DDSpan) activeSpan).getTraceId();
}
@ -305,7 +307,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
@Override
public String getSpanId() {
final Span activeSpan = this.activeSpan();
final Span activeSpan = activeSpan();
if (activeSpan instanceof DDSpan) {
return ((DDSpan) activeSpan).getSpanId();
}

View File

@ -2,12 +2,10 @@ package datadog.opentracing.decorators;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/** Create DDSpanDecorators */
public class DDDecoratorsFactory {
public static List<AbstractDecorator> createBuiltinDecorators(
final Map<String, String> mappings) {
public static List<AbstractDecorator> createBuiltinDecorators() {
final HTTPComponent httpDecorator = new HTTPComponent();
httpDecorator.setMatchingTag("component");
httpDecorator.setMatchingValue("java-aws-sdk");

View File

@ -1,20 +1,20 @@
package datadog.opentracing.decorators;
import datadog.opentracing.DDSpanContext;
import datadog.opentracing.DDTracer;
import datadog.trace.api.Config;
public class ServletContextDecorator extends AbstractDecorator {
public ServletContextDecorator() {
super();
this.setMatchingTag("servlet.context");
setMatchingTag("servlet.context");
}
@Override
public boolean shouldSetTag(final DDSpanContext context, final String tag, final Object value) {
String contextName = String.valueOf(value).trim();
if (contextName.equals("/")
|| (!context.getServiceName().equals(DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME)
|| (!context.getServiceName().equals(Config.DEFAULT_SERVICE_NAME)
&& !context.getServiceName().isEmpty())) {
return true;
}

View File

@ -1,36 +1,20 @@
package datadog.opentracing.resolver;
import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import datadog.opentracing.DDTracer;
import datadog.trace.common.util.Config;
import datadog.trace.api.Config;
import io.opentracing.Tracer;
import io.opentracing.contrib.tracerresolver.TracerResolver;
import io.opentracing.noop.NoopTracerFactory;
import io.opentracing.util.GlobalTracer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@AutoService(TracerResolver.class)
public class DDTracerResolver extends TracerResolver {
static final String CONFIG_KEY = "dd.trace.resolver.enabled";
public static Tracer registerTracer() {
final Tracer tracer = TracerResolver.resolveTracer();
if (tracer == null) {
log.warn("Cannot resolved the tracer, use NoopTracer");
return NoopTracerFactory.create();
}
log.info("Register the tracer via GlobalTracer");
GlobalTracer.register(tracer);
return tracer;
}
@Override
protected Tracer resolve() {
final boolean enabled = !"false".equalsIgnoreCase(Config.getPropOrEnv(CONFIG_KEY));
if (enabled) {
@VisibleForTesting
Tracer resolve(final Config config) {
if (config.isTraceResolverEnabled()) {
log.info("Creating DDTracer with DDTracerResolver");
return new DDTracer();
} else {
@ -38,4 +22,9 @@ public class DDTracerResolver extends TracerResolver {
return null;
}
}
@Override
protected Tracer resolve() {
return resolve(Config.get());
}
}

View File

@ -1,73 +0,0 @@
package datadog.trace.common;
import static datadog.trace.common.util.Config.getPropOrEnv;
import datadog.opentracing.DDTracer;
import datadog.trace.common.writer.DDAgentWriter;
import datadog.trace.common.writer.Writer;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
/**
* Config gives priority to system properties and falls back to environment variables. It also
* includes default values to ensure a valid config.
*
* <p>
*
* <p>System properties are {@link DDTraceConfig#PREFIX}'ed. Environment variables are the same as
* the system property, but uppercased with '.' -> '_'.
*/
@Slf4j
public class DDTraceConfig extends Properties {
/** Config keys below */
private static final String PREFIX = "dd.";
public static final String SERVICE_NAME = "service.name";
public static final String SERVICE_MAPPING = "service.mapping";
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 PRIORITY_SAMPLING = "priority.sampling";
public static final String SPAN_TAGS = "trace.span.tags";
public static final String HEADER_TAGS = "trace.header.tags";
private final String serviceName = getPropOrEnv(PREFIX + SERVICE_NAME);
private final String serviceMapping = getPropOrEnv(PREFIX + SERVICE_MAPPING);
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 prioritySampling = getPropOrEnv(PREFIX + PRIORITY_SAMPLING);
private final String spanTags = getPropOrEnv(PREFIX + SPAN_TAGS);
private final String headerTags = getPropOrEnv(PREFIX + HEADER_TAGS);
public DDTraceConfig() {
super();
final Properties defaults = new Properties();
defaults.setProperty(SERVICE_NAME, DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME);
defaults.setProperty(WRITER_TYPE, Writer.DD_AGENT_WRITER_TYPE);
defaults.setProperty(AGENT_HOST, DDAgentWriter.DEFAULT_HOSTNAME);
defaults.setProperty(AGENT_PORT, String.valueOf(DDAgentWriter.DEFAULT_PORT));
super.defaults = defaults;
setIfNotNull(SERVICE_NAME, serviceName);
setIfNotNull(SERVICE_MAPPING, serviceMapping);
setIfNotNull(WRITER_TYPE, writerType);
setIfNotNull(AGENT_HOST, agentHost);
setIfNotNull(AGENT_PORT, agentPort);
setIfNotNull(PRIORITY_SAMPLING, prioritySampling);
setIfNotNull(SPAN_TAGS, spanTags);
setIfNotNull(HEADER_TAGS, headerTags);
}
public DDTraceConfig(final String serviceName) {
this();
put(SERVICE_NAME, serviceName);
}
private void setIfNotNull(final String key, final String value) {
if (value != null) {
setProperty(key, value);
}
}
}

View File

@ -1,12 +1,11 @@
package datadog.trace.common.sampling;
import datadog.opentracing.DDSpan;
import datadog.trace.common.DDTraceConfig;
import datadog.trace.api.Config;
import java.util.Properties;
/** Main interface to sample a collection of traces. */
public interface Sampler {
static final String ALL_SAMPLER_TYPE = AllSampler.class.getSimpleName();
/**
* Sample a collection of traces based on the parent span
@ -17,12 +16,10 @@ public interface Sampler {
boolean sample(DDSpan span);
final class Builder {
public static Sampler forConfig(final Properties config) {
public static Sampler forConfig(final Config config) {
final Sampler sampler;
if (config != null) {
final boolean prioritySamplingEnabled =
Boolean.parseBoolean(config.getProperty(DDTraceConfig.PRIORITY_SAMPLING));
if (prioritySamplingEnabled) {
if (config.isPrioritySamplingEnabled()) {
sampler = new RateByServiceSampler();
} else {
sampler = new AllSampler();
@ -33,6 +30,10 @@ public interface Sampler {
return sampler;
}
public static Sampler forConfig(final Properties config) {
return forConfig(Config.get(config));
}
private Builder() {}
}
}

View File

@ -1,46 +0,0 @@
package datadog.trace.common.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class Config {
public static String getPropOrEnv(final String name) {
return System.getProperty(name, System.getenv(propToEnvName(name)));
}
public static String propToEnvName(final String name) {
return name.toUpperCase().replace(".", "_");
}
public static Map<String, String> parseMap(final String str, final String settingName) {
if (str == null || str.trim().isEmpty()) {
return Collections.emptyMap();
}
if (!str.matches("(([^,:]+:[^,:]*,)*([^,:]+:[^,:]*),?)?")) {
log.warn(
"Invalid config for {}: '{}'. Must match 'key1:value1,key2:value2'.", settingName, str);
return Collections.emptyMap();
}
final String[] tokens = str.split(",", -1);
final Map<String, String> map = new HashMap<>(tokens.length + 1, 1f);
for (final String token : tokens) {
final String[] keyValue = token.split(":", -1);
if (keyValue.length == 2) {
final String key = keyValue[0].trim();
final String value = keyValue[1].trim();
if (value.length() <= 0) {
log.warn("Ignoring empty value for key '{}' in config for {}", key, settingName);
continue;
}
map.put(key, value);
}
}
return Collections.unmodifiableMap(map);
}
}

View File

@ -1,5 +1,8 @@
package datadog.trace.common.writer;
import static datadog.trace.api.Config.DEFAULT_AGENT_HOST;
import static datadog.trace.api.Config.DEFAULT_AGENT_PORT;
import datadog.opentracing.DDSpan;
import java.util.List;
import java.util.concurrent.Callable;
@ -25,11 +28,6 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DDAgentWriter implements Writer {
/** Default location of the DD agent */
public static final String DEFAULT_HOSTNAME = "localhost";
public static final int DEFAULT_PORT = 8126;
/** Maximum number of traces kept in memory */
static final int DEFAULT_MAX_TRACES = 7000;
@ -66,7 +64,7 @@ public class DDAgentWriter implements Writer {
private boolean queueFullReported = false;
public DDAgentWriter() {
this(new DDApi(DEFAULT_HOSTNAME, DEFAULT_PORT));
this(new DDApi(DEFAULT_AGENT_HOST, DEFAULT_AGENT_PORT));
}
public DDAgentWriter(final DDApi api) {

View File

@ -1,15 +1,13 @@
package datadog.trace.common.writer;
import datadog.opentracing.DDSpan;
import datadog.trace.common.DDTraceConfig;
import datadog.trace.api.Config;
import java.util.List;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
/** 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
@ -29,28 +27,21 @@ public interface Writer {
@Slf4j
final class Builder {
public static Writer forConfig(final Properties config) {
public static Writer forConfig(final Config 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)) {
final String configuredType = config.getWriterType();
if (Config.DD_AGENT_WRITER_TYPE.equals(configuredType)) {
writer = createAgentWriter(config);
} else if (Config.LOGGING_WRITER_TYPE.equals(configuredType)) {
writer = new LoggingWriter();
} else {
log.warn(
"Writer type not configured correctly: Type {} not recognized. Defaulting to DDAgentWriter.",
configuredType);
writer =
new DDAgentWriter(
new DDApi(
config.getProperty(DDTraceConfig.AGENT_HOST),
Integer.parseInt(config.getProperty(DDTraceConfig.AGENT_PORT))));
writer = createAgentWriter(config);
}
} else {
log.warn(
@ -61,6 +52,14 @@ public interface Writer {
return writer;
}
public static Writer forConfig(final Properties config) {
return forConfig(Config.get(config));
}
private static Writer createAgentWriter(final Config config) {
return new DDAgentWriter(new DDApi(config.getAgentHost(), config.getAgentPort()));
}
private Builder() {}
}
}

View File

@ -1,6 +1,7 @@
package datadog.opentracing
import datadog.opentracing.propagation.ExtractedContext
import datadog.trace.api.Config
import datadog.trace.api.DDTags
import datadog.trace.common.writer.ListWriter
import spock.lang.Specification
@ -273,7 +274,7 @@ class DDSpanBuilderTest extends Specification {
def "global span tags populated on each span"() {
setup:
System.setProperty("dd.trace.span.tags", tagString)
tracer = new DDTracer(writer)
tracer = new DDTracer(new Config(), writer)
def span = tracer.buildSpan("op name").withServiceName("foo").start()
tags.putAll([
(DDTags.THREAD_NAME): Thread.currentThread().getName(),

View File

@ -7,9 +7,11 @@ import spock.lang.Specification
import java.util.concurrent.TimeUnit
import static datadog.trace.api.Config.DEFAULT_SERVICE_NAME
class DDSpanTest extends Specification {
def writer = new ListWriter()
def tracer = new DDTracer(DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME, writer, new RateByServiceSampler())
def tracer = new DDTracer(DEFAULT_SERVICE_NAME, writer, new RateByServiceSampler())
def "getters and setters"() {
setup:
@ -77,7 +79,7 @@ class DDSpanTest extends Specification {
span = tracer.buildSpan(opName).start()
then:
span.getResourceName() == opName
span.getServiceName() == DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME
span.getServiceName() == DEFAULT_SERVICE_NAME
when:
final String resourceName = "fake"

View File

@ -11,7 +11,7 @@ import io.opentracing.tag.StringTag
import io.opentracing.tag.Tags
import spock.lang.Specification
import static datadog.opentracing.DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME
import static datadog.trace.api.Config.DEFAULT_SERVICE_NAME
import static java.util.Collections.emptyMap
class SpanDecoratorTest extends Specification {
@ -75,10 +75,10 @@ class SpanDecoratorTest extends Specification {
span.serviceName == expected
where:
serviceName | expected | mapping
UNASSIGNED_DEFAULT_SERVICE_NAME | UNASSIGNED_DEFAULT_SERVICE_NAME | ["other-service-name": "other-service"]
UNASSIGNED_DEFAULT_SERVICE_NAME | "new-service" | [(UNASSIGNED_DEFAULT_SERVICE_NAME): "new-service"]
"other-service-name" | "other-service" | ["other-service-name": "other-service"]
serviceName | expected | mapping
DEFAULT_SERVICE_NAME | DEFAULT_SERVICE_NAME | ["other-service-name": "other-service"]
DEFAULT_SERVICE_NAME | "new-service" | [(DEFAULT_SERVICE_NAME): "new-service"]
"other-service-name" | "other-service" | ["other-service-name": "other-service"]
}
def "set service name from servlet.context with context '#context'"() {
@ -90,15 +90,15 @@ class SpanDecoratorTest extends Specification {
span.serviceName == expected
where:
context | serviceName | expected
"/" | UNASSIGNED_DEFAULT_SERVICE_NAME | UNASSIGNED_DEFAULT_SERVICE_NAME
"" | UNASSIGNED_DEFAULT_SERVICE_NAME | UNASSIGNED_DEFAULT_SERVICE_NAME
"/some-context" | UNASSIGNED_DEFAULT_SERVICE_NAME | "some-context"
"other-context" | UNASSIGNED_DEFAULT_SERVICE_NAME | "other-context"
"/" | "my-service" | "my-service"
"" | "my-service" | "my-service"
"/some-context" | "my-service" | "my-service"
"other-context" | "my-service" | "my-service"
context | serviceName | expected
"/" | DEFAULT_SERVICE_NAME | DEFAULT_SERVICE_NAME
"" | DEFAULT_SERVICE_NAME | DEFAULT_SERVICE_NAME
"/some-context" | DEFAULT_SERVICE_NAME | "some-context"
"other-context" | DEFAULT_SERVICE_NAME | "other-context"
"/" | "my-service" | "my-service"
"" | "my-service" | "my-service"
"/some-context" | "my-service" | "my-service"
"other-context" | "my-service" | "my-service"
}
def "set service name from servlet.context with context '#context' for service #serviceName"() {
@ -114,15 +114,15 @@ class SpanDecoratorTest extends Specification {
span.serviceName == expected
where:
context | serviceName | expected
"/" | UNASSIGNED_DEFAULT_SERVICE_NAME | "new-service"
"" | UNASSIGNED_DEFAULT_SERVICE_NAME | "new-service"
"/some-context" | UNASSIGNED_DEFAULT_SERVICE_NAME | "some-context"
"other-context" | UNASSIGNED_DEFAULT_SERVICE_NAME | "other-context"
"/" | "my-service" | "new-service"
"" | "my-service" | "new-service"
"/some-context" | "my-service" | "new-service"
"other-context" | "my-service" | "new-service"
context | serviceName | expected
"/" | DEFAULT_SERVICE_NAME | "new-service"
"" | DEFAULT_SERVICE_NAME | "new-service"
"/some-context" | DEFAULT_SERVICE_NAME | "some-context"
"other-context" | DEFAULT_SERVICE_NAME | "other-context"
"/" | "my-service" | "new-service"
"" | "my-service" | "new-service"
"/some-context" | "my-service" | "new-service"
"other-context" | "my-service" | "new-service"
mapping = [(serviceName): "new-service"]
}

View File

@ -0,0 +1,34 @@
package datadog.opentracing.resolver
import datadog.opentracing.DDTracer
import datadog.trace.api.Config
import io.opentracing.contrib.tracerresolver.TracerResolver
import spock.lang.Specification
class DDTracerResolverTest extends Specification {
def resolver = new DDTracerResolver()
def "test resolveTracer"() {
when:
def tracer = TracerResolver.resolveTracer()
then:
tracer instanceof DDTracer
}
def "test disable DDTracerResolver"() {
setup:
System.setProperty("dd.trace.resolver.enabled", "false")
when:
def tracer = resolver.resolve(new Config())
then:
tracer == null
cleanup:
System.clearProperty("dd.trace.resolver.enabled")
}
}

View File

@ -1,65 +0,0 @@
package datadog.opentracing.resolver
import datadog.opentracing.DDTracer
import io.opentracing.Tracer
import io.opentracing.contrib.tracerresolver.TracerResolver
import io.opentracing.noop.NoopTracer
import io.opentracing.noop.NoopTracerFactory
import io.opentracing.util.GlobalTracer
import spock.lang.Specification
import java.lang.reflect.Field
class TracerResolverTest extends Specification {
def setup() {
setTracer(null)
assert !GlobalTracer.isRegistered()
}
def "test resolveTracer"() {
when:
def tracer = TracerResolver.resolveTracer()
then:
!GlobalTracer.isRegistered()
tracer instanceof DDTracer
}
def "test registerTracer"() {
when:
def tracer = DDTracerResolver.registerTracer()
then:
GlobalTracer.isRegistered()
tracer instanceof DDTracer
}
def "test disable DDTracerResolver"() {
setup:
System.setProperty("dd.trace.resolver.enabled", "false")
when:
def tracer = TracerResolver.resolveTracer()
then:
!GlobalTracer.isRegistered()
tracer == null
when:
tracer = DDTracerResolver.registerTracer()
then:
!GlobalTracer.isRegistered()
tracer instanceof NoopTracer
cleanup:
System.clearProperty("dd.trace.resolver.enabled")
}
def setTracer(Tracer tracer) {
final Field tracerField = GlobalTracer.getDeclaredField("tracer")
tracerField.setAccessible(true)
tracerField.set(tracer, NoopTracerFactory.create())
}
}

View File

@ -1,182 +0,0 @@
package datadog.trace
import datadog.opentracing.DDTracer
import datadog.trace.common.DDTraceConfig
import datadog.trace.common.sampling.AllSampler
import datadog.trace.common.writer.DDAgentWriter
import datadog.trace.common.writer.ListWriter
import datadog.trace.common.writer.LoggingWriter
import org.junit.Rule
import org.junit.contrib.java.lang.system.EnvironmentVariables
import org.junit.contrib.java.lang.system.RestoreSystemProperties
import spock.lang.Specification
import static datadog.trace.common.DDTraceConfig.AGENT_HOST
import static datadog.trace.common.DDTraceConfig.AGENT_PORT
import static datadog.trace.common.DDTraceConfig.HEADER_TAGS
import static datadog.trace.common.DDTraceConfig.PREFIX
import static datadog.trace.common.DDTraceConfig.SERVICE_MAPPING
import static datadog.trace.common.DDTraceConfig.SERVICE_NAME
import static datadog.trace.common.DDTraceConfig.SPAN_TAGS
import static datadog.trace.common.DDTraceConfig.WRITER_TYPE
import static datadog.trace.common.util.Config.parseMap
import static datadog.trace.common.util.Config.propToEnvName
class DDTraceConfigTest extends Specification {
@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties()
@Rule
public final EnvironmentVariables environmentVariables = new EnvironmentVariables()
def "verify env override"() {
setup:
environmentVariables.set("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(SERVICE_MAPPING) == null
config.getProperty(WRITER_TYPE) == "DDAgentWriter"
config.getProperty(AGENT_HOST) == "localhost"
config.getProperty(AGENT_PORT) == "8126"
config.getProperty(SPAN_TAGS) == null
when:
config = new DDTraceConfig("A different service name")
then:
config.getProperty(SERVICE_NAME) == "A different service name"
config.getProperty(SERVICE_MAPPING) == null
config.getProperty(WRITER_TYPE) == "DDAgentWriter"
config.getProperty(AGENT_HOST) == "localhost"
config.getProperty(AGENT_PORT) == "8126"
config.getProperty(SPAN_TAGS) == null
}
def "specify overrides via system properties"() {
when:
System.setProperty(PREFIX + SERVICE_NAME, "something else")
System.setProperty(PREFIX + WRITER_TYPE, LoggingWriter.simpleName)
def tracer = new DDTracer()
then:
tracer.serviceName == "something else"
tracer.writer instanceof LoggingWriter
}
def "specify overrides via env vars"() {
when:
environmentVariables.set(propToEnvName(PREFIX + SERVICE_NAME), "still something else")
environmentVariables.set(propToEnvName(PREFIX + WRITER_TYPE), LoggingWriter.simpleName)
def tracer = new DDTracer()
then:
tracer.serviceName == "still something else"
tracer.writer instanceof LoggingWriter
}
def "sys props override env vars"() {
when:
environmentVariables.set(propToEnvName(PREFIX + SERVICE_NAME), "still something else")
environmentVariables.set(propToEnvName(PREFIX + WRITER_TYPE), ListWriter.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")
def tracer = new DDTracer()
then:
tracer.serviceName == "what we actually want"
tracer.writer.toString() == "DDAgentWriter { api=DDApi { tracesEndpoint=http://somewhere:9999/v0.3/traces } }"
}
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 } }"
tracer.spanContextDecorators.size() == 12
}
def "verify mapping configs on tracer"() {
setup:
System.setProperty(PREFIX + SERVICE_MAPPING, mapString)
System.setProperty(PREFIX + SPAN_TAGS, mapString)
System.setProperty(PREFIX + HEADER_TAGS, mapString)
when:
def tracer = new DDTracer()
def taggedHeaders = tracer.registry.codecs.values().first().taggedHeaders
then:
tracer.defaultSpanTags == map
tracer.serviceNameMappings == map
taggedHeaders == map
where:
mapString | map
"a:1, a:2, a:3" | [a: "3"]
"a:b,c:d,e:" | [a: "b", c: "d"]
}
def "verify single override on #source for #key"() {
when:
System.setProperty(PREFIX + key, value)
def tracer = new DDTracer()
then:
tracer."$source".toString() == expected
where:
source | key | value | expected
"writer" | "default" | "default" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://localhost:8126/v0.3/traces } }"
"writer" | "writer.type" | "LoggingWriter" | "LoggingWriter { }"
"writer" | "agent.host" | "somethingelse" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://somethingelse:8126/v0.3/traces } }"
"writer" | "agent.port" | "9999" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://localhost:9999/v0.3/traces } }"
}
def "parsing valid string returns a map"() {
expect:
parseMap(str, "test") == map
where:
str | map
"a:" | [:]
"a:a;" | [a: "a;"]
"a:1, a:2, a:3" | [a: "3"]
"a:b,c:d,e:" | [a: "b", c: "d"]
"key 1!:va|ue_1," | ["key 1!": "va|ue_1"]
" key1 :value1 ,\t key2: value2" | [key1: "value1", key2: "value2"]
}
def "parsing an invalid string returns an empty map"() {
expect:
parseMap(str, "test") == [:]
where:
str | _
null | _
"" | _
"1" | _
"a" | _
"a,1" | _
"in:val:id" | _
"a:b:c:d" | _
"a:b,c,d" | _
"!a" | _
}
}

View File

@ -0,0 +1,97 @@
package datadog.trace
import datadog.opentracing.DDTracer
import datadog.trace.api.Config
import datadog.trace.common.sampling.AllSampler
import datadog.trace.common.sampling.RateByServiceSampler
import datadog.trace.common.writer.LoggingWriter
import org.junit.Rule
import org.junit.contrib.java.lang.system.EnvironmentVariables
import org.junit.contrib.java.lang.system.RestoreSystemProperties
import spock.lang.Specification
import static datadog.trace.api.Config.HEADER_TAGS
import static datadog.trace.api.Config.PREFIX
import static datadog.trace.api.Config.PRIORITY_SAMPLING
import static datadog.trace.api.Config.SERVICE_MAPPING
import static datadog.trace.api.Config.SPAN_TAGS
import static datadog.trace.api.Config.WRITER_TYPE
class DDTracerTest extends Specification {
@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties()
@Rule
public final EnvironmentVariables environmentVariables = new EnvironmentVariables()
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 } }"
tracer.spanContextDecorators.size() == 12
}
def "verify overriding sampler"() {
setup:
System.setProperty(PREFIX + PRIORITY_SAMPLING, "true")
when:
def tracer = new DDTracer(new Config())
then:
tracer.sampler instanceof RateByServiceSampler
}
def "verify overriding writer"() {
setup:
System.setProperty(PREFIX + WRITER_TYPE, "LoggingWriter")
when:
def tracer = new DDTracer(new Config())
then:
tracer.writer instanceof LoggingWriter
}
def "verify mapping configs on tracer"() {
setup:
System.setProperty(PREFIX + SERVICE_MAPPING, mapString)
System.setProperty(PREFIX + SPAN_TAGS, mapString)
System.setProperty(PREFIX + HEADER_TAGS, mapString)
when:
def tracer = new DDTracer(new Config())
def taggedHeaders = tracer.registry.codecs.values().first().taggedHeaders
then:
tracer.defaultSpanTags == map
tracer.serviceNameMappings == map
taggedHeaders == map
where:
mapString | map
"a:1, a:2, a:3" | [a: "3"]
"a:b,c:d,e:" | [a: "b", c: "d"]
}
def "verify single override on #source for #key"() {
when:
System.setProperty(PREFIX + key, value)
def tracer = new DDTracer(new Config())
then:
tracer."$source".toString() == expected
where:
source | key | value | expected
"writer" | "default" | "default" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://localhost:8126/v0.3/traces } }"
"writer" | "writer.type" | "LoggingWriter" | "LoggingWriter { }"
"writer" | "agent.host" | "somethingelse" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://somethingelse:8126/v0.3/traces } }"
"writer" | "agent.port" | "9999" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://localhost:9999/v0.3/traces } }"
}
}

View File

@ -4,7 +4,6 @@ import datadog.opentracing.DDSpanContext
import datadog.opentracing.DDTracer
import datadog.opentracing.PendingTrace
import datadog.trace.api.sampling.PrioritySampling
import datadog.trace.common.writer.DDAgentWriter
import datadog.trace.common.writer.DDApi
import datadog.trace.common.writer.ListWriter
import spock.lang.Specification
@ -12,6 +11,9 @@ import spock.lang.Specification
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import static datadog.trace.api.Config.DEFAULT_AGENT_HOST
import static datadog.trace.api.Config.DEFAULT_AGENT_PORT
class DDApiIntegrationTest {
static class DDApiIntegrationV4Test extends Specification {
static final WRITER = new ListWriter()
@ -31,7 +33,7 @@ class DDApiIntegrationTest {
new PendingTrace(TRACER, "1", [:]),
TRACER)
def api = new DDApi(DDAgentWriter.DEFAULT_HOSTNAME, DDAgentWriter.DEFAULT_PORT, v4())
def api = new DDApi(DEFAULT_AGENT_HOST, DEFAULT_AGENT_PORT, v4())
def endpoint = new AtomicReference<String>(null)
def agentResponse = new AtomicReference<String>(null)