Define the concept of applicationRootSpanTags and apply only to root
tags
MODIFY
dd-trace-api/src/main/java/datadog/trace/api/Config.java
dd-trace-api/src/main/java/datadog/trace/api/Config.java
package datadog.trace.api;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -113,6 +115,9 @@ public class Config {
B3
}
+ /** A tag intended for internal use only, hence not added to the public api DDTags class. */
+ private static final String INTERNAL_HOST_NAME = "_dd.hostname";
+
/**
* this is a random UUID that gets generated on JVM start up and is attached to every root span
* and every JMX metric that is sent out.
@@ -150,10 +155,7 @@ public class Config {
@Getter private final boolean logsInjectionEnabled;
- // If `true` the hostname will be detected and added to the root span's metadata
- // Note: this temporarily non final as a conversation is in place related to how to improve
- // testability under different configuration scenarios.
- @Getter private boolean reportHostName;
+ @Getter private final boolean reportHostName;
// Read order: System Properties -> Env Variables, [-> default value]
// Visible for testing
@@ -318,6 +320,15 @@ public class Config {
log.debug("New instance: {}", this);
}
+ /** @return A map of tags to be applied only to the currently tracing application root span. */
+ public Map<String, String> getApplicationRootSpanTags() {
+ final Map<String, String> result = newHashMap(reportHostName ? 1 : 0);
+ if (reportHostName) {
+ result.put(INTERNAL_HOST_NAME, getHostname());
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
public Map<String, String> getMergedSpanTags() {
// DO not include runtimeId into span tags: we only want that added to the root span
final Map<String, String> result = newHashMap(globalTags.size() + spanTags.size());
@@ -646,6 +657,28 @@ public class Config {
return Collections.unmodifiableSet(result);
}
+ // Fields used to cache detected hostName which is a time consuming operation.
+ private String hostName = null;
+ private boolean hostNameDetected = false;
+
+ /**
+ * Returns the detected hostname. This operation is time consuming and the first time this method
+ * is called will take some time. Hostname is cached for subsequent calls.
+ */
+ public String getHostname() {
+ if (!this.hostNameDetected) {
+ try {
+ this.hostName = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ // If we are not able to detect the hostname we do not throw an exception.
+ } finally {
+ this.hostNameDetected = true;
+ }
+ }
+
+ return this.hostName;
+ }
+
// This has to be placed after all other static fields to give them a chance to initialize
private static final Config INSTANCE = new Config();
@@ -660,13 +693,4 @@ public class Config {
return new Config(properties, INSTANCE);
}
}
-
- /**
- * Note: this is a workaround as a conversation related to improve testability of services under
- * different configuration scenarios is in progress.
- */
- public void refreshDetectHostnameProperty() {
- this.reportHostName =
- getBooleanSettingFromEnvironment(TRACE_REPORT_HOSTNAME, DEFAULT_TRACE_REPORT_HOSTNAME);
- }
}

MODIFY
dd-trace-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy
dd-trace-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy
@@ -658,4 +658,27 @@ class ConfigTest extends Specification {
listString | list
"" | []
}
+
+ def "verify hostname not added to root span tags by default"() {
+ setup:
+ Properties properties = new Properties()
+
+ when:
+ def config = Config.get(properties)
+
+ then:
+ !config.applicationRootSpanTags.containsKey('_dd.hostname')
+ }
+
+ def "verify configuration to add hostname to root span tags"() {
+ setup:
+ Properties properties = new Properties()
+ properties.setProperty(TRACE_REPORT_HOSTNAME, 'true')
+
+ when:
+ def config = Config.get(properties)
+
+ then:
+ config.applicationRootSpanTags.get('_dd.hostname') == InetAddress.localHost.hostName
+ }
}

MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
@@ -26,8 +26,6 @@ import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
import java.io.Closeable;
import java.lang.ref.WeakReference;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
@@ -50,6 +48,8 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
/** Tags required to link apm traces to runtime metrics */
final Map<String, String> runtimeTags;
+ /** A set of tags that are added only to the application's root span */
+ private final Map<String, String> applicationRootSpanTags;
/** A set of tags that are added to every span */
private final Map<String, String> defaultSpanTags;
/** A configured mapping of service names to update with new values */
@@ -80,15 +80,6 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
private final HttpCodec.Injector injector;
private final HttpCodec.Extractor extractor;
- /** A tag intended for internal use only, hence not added to the public api DDTags class. */
- private static final String INTERNAL_HOST_NAME = "_dd.hostname";
-
- /**
- * hostname is expensive to calculate so we detect it only when creating the tracer and we cache
- * it for future reuse.
- */
- private String hostname;
-
/** By default, report to local agent and collect all traces. */
public DDTracer() {
this(Config.get());
@@ -117,6 +108,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
Writer.Builder.forConfig(config),
Sampler.Builder.forConfig(config),
config.getRuntimeTags(),
+ config.getApplicationRootSpanTags(),
config.getMergedSpanTags(),
config.getServiceMapping(),
config.getHeaderTags(),
@@ -138,6 +130,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
Collections.<String, String>emptyMap(),
Collections.<String, String>emptyMap(),
Collections.<String, String>emptyMap(),
+ Collections.<String, String>emptyMap(),
0);
}
@@ -151,6 +144,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
writer,
Sampler.Builder.forConfig(config),
config.getRuntimeTags(),
+ config.getApplicationRootSpanTags(),
config.getMergedSpanTags(),
config.getServiceMapping(),
config.getHeaderTags(),
@@ -166,6 +160,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
final Writer writer,
final Sampler sampler,
final String runtimeId,
+ final Map<String, String> applicationRootSpanTags,
final Map<String, String> defaultSpanTags,
final Map<String, String> serviceNameMappings,
final Map<String, String> taggedHeaders) {
@@ -174,6 +169,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
writer,
sampler,
customRuntimeTags(runtimeId),
+ applicationRootSpanTags,
defaultSpanTags,
serviceNameMappings,
taggedHeaders,
@@ -189,6 +185,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
final Writer writer,
final Sampler sampler,
final Map<String, String> runtimeTags,
+ final Map<String, String> applicationRootSpanTags,
final Map<String, String> defaultSpanTags,
final Map<String, String> serviceNameMappings,
final Map<String, String> taggedHeaders) {
@@ -197,6 +194,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
writer,
sampler,
runtimeTags,
+ applicationRootSpanTags,
defaultSpanTags,
serviceNameMappings,
taggedHeaders,
@@ -208,6 +206,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
final Writer writer,
final Sampler sampler,
final Map<String, String> runtimeTags,
+ final Map<String, String> applicationRootSpanTags,
final Map<String, String> defaultSpanTags,
final Map<String, String> serviceNameMappings,
final Map<String, String> taggedHeaders,
@@ -221,6 +220,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
this.writer = writer;
this.writer.start();
this.sampler = sampler;
+ this.applicationRootSpanTags = applicationRootSpanTags;
this.defaultSpanTags = defaultSpanTags;
this.runtimeTags = runtimeTags;
this.serviceNameMappings = serviceNameMappings;
@@ -256,8 +256,6 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
// Ensure that PendingTrace.SPAN_CLEANER is initialized in this thread:
// FIXME: add test to verify the span cleaner thread is started with this call.
PendingTrace.initialize();
-
- cacheHostName();
}
@Override
@@ -383,7 +381,6 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
// TODO: current trace implementation doesn't guarantee that first span is the root span
// We may want to reconsider way this check is done.
if (!writtenTrace.isEmpty() && sampler.sample(writtenTrace.get(0))) {
- applyHostNameDetection(writtenTrace);
writer.write(writtenTrace);
}
}
@@ -616,6 +613,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
final PendingTrace parentTrace;
final int samplingPriority;
final String origin;
+ final boolean isApplicationRootSpan = parent == null || !(parent instanceof DDSpanContext);
final DDSpanContext context;
SpanContext parentContext = parent;
@@ -678,6 +676,10 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
final String operationName = this.operationName != null ? this.operationName : resourceName;
+ if (isApplicationRootSpan) {
+ applyTagsIfNotDefined(tags, applicationRootSpanTags);
+ }
+
// some attributes are inherited from the parent
context =
new DDSpanContext(
@@ -727,6 +729,28 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
return context;
}
+
+ /**
+ * Merge a map of tag 'candidates' into a 'destination' map of tags never overwriting original
+ * values.
+ *
+ * @param destination The map of tags where candidate tags will be merged in
+ * @param candidates The potential tags that will be added if not already defined
+ */
+ private void applyTagsIfNotDefined(
+ Map<String, Object> destination, Map<String, String> candidates) {
+ if (null == destination || null == candidates) {
+ return;
+ }
+
+ for (Map.Entry<String, String> entry : candidates.entrySet()) {
+ if (destination.containsKey(entry.getKey())) {
+ continue;
+ }
+
+ destination.put(entry.getKey(), entry.getValue());
+ }
+ }
}
private static class ShutdownHook extends Thread {
@@ -744,57 +768,4 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
}
}
}
-
- /**
- * Hostname is expensive to retrieve so we provide a way to calculate it and cache it in the
- * tracer itself for further reuse.
- */
- private void cacheHostName() {
-
- // Host name detection can be disabled via configuration
- if (!Config.get().isReportHostName()) {
- return;
- }
-
- try {
- this.hostname = InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException e) {
- // If we are not able to detect the hostname we do not throw an exception.
- }
- }
-
- /**
- * Sets the internal hostname tag on a root span if appropriate. The following acceptance criteria
- * apply: 1) Users should not be able to overwrite this value, 2) It has to be done only on the
- * root span, 3) It is not guaranteed that the first span in the list is the root span.
- *
- * @param spans
- */
- private void applyHostNameDetection(List<DDSpan> spans) {
-
- // Host name detection can be disabled via configuration
- if (!Config.get().isReportHostName()) {
- return;
- }
-
- // Every time we set a tag all the decorators have to be executed so if we already set the
- // hostname on the root of a specific span, using this registry we can avoid setting the tag
- // again, hence executing all decorators again to obtain the same result.
- Set<MutableSpan> alreadyTrackedRootSpans = new HashSet<>();
- for (DDSpan span : spans) {
- MutableSpan rootSpan = span.getRootSpan();
-
- // Only root spans get the hostname applied
- if (null == rootSpan) {
- continue;
- }
-
- if (alreadyTrackedRootSpans.contains(rootSpan)) {
- continue;
- }
- alreadyTrackedRootSpans.add(rootSpan);
-
- rootSpan.setTag(INTERNAL_HOST_NAME, hostname);
- }
- }
}

MODIFY
dd-trace-ot/src/test/groovy/datadog/trace/DDTracerTest.groovy
dd-trace-ot/src/test/groovy/datadog/trace/DDTracerTest.groovy
@@ -149,35 +149,18 @@ class DDTracerTest extends Specification {
PRIORITY_SAMPLING | "false"
}
- def "tracer does not set the host name by default"() {
+ def "root tags are applied only to root spans"() {
setup:
- def tracer = new DDTracer('my_service', new ListWriter(), new AllSampler())
+ def tracer = new DDTracer('my_service', new ListWriter(), new AllSampler(), '', ['only_root': 'value'], [:], [:], [:])
+ def root = tracer.buildSpan('my_root').start()
+ def child = tracer.buildSpan('my_child').asChildOf(root).start()
- when:
- DDSpan root = tracer.buildSpan('my_root').start()
- DDSpan child = tracer.buildSpan('my_child').asChildOf(root).start()
- child.finish()
- root.finish()
-
- then:
- !root.context().tags.containsKey(INTERNAL_HOST_NAME)
- !child.context().tags.containsKey(INTERNAL_HOST_NAME)
- }
-
- def "tracer sets the host name if activated only on root span"() {
- setup:
- System.setProperty('dd.trace.report-hostname', 'true')
- Config.get().refreshDetectHostnameProperty()
- def tracer = new DDTracer('my_service', new ListWriter(), new AllSampler())
+ expect:
+ root.context().tags.containsKey('only_root')
+ !child.context().tags.containsKey('only_root')
- when:
- DDSpan root = tracer.buildSpan('my_root').start()
- DDSpan child = tracer.buildSpan('my_child').asChildOf(root).start()
+ cleanup:
child.finish()
root.finish()
-
- then:
- root.context().tags.get(INTERNAL_HOST_NAME) == InetAddress.localHost.hostName
- !child.context().tags.containsKey(INTERNAL_HOST_NAME)
}
}

Define the concept of applicationRootSpanTags and apply only to root
tags