Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Collect process tags for tracing
  • Loading branch information
amarziali committed Apr 28, 2025
commit bc6d1ba68fc5963a4e9d319ae215bd4df02fa4e2
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) String[
+ "datadog.trace.api.Platform:rerun,"
+ "datadog.trace.api.Platform$Captured:build_time,"
+ "datadog.trace.api.env.CapturedEnvironment:build_time,"
+ "datadog.trace.api.env.CapturedEnvironment$ProcessInfo:build_time,"
+ "datadog.trace.api.ConfigCollector:rerun,"
+ "datadog.trace.api.ConfigDefaults:build_time,"
+ "datadog.trace.api.ConfigOrigin:build_time,"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.Config;
import datadog.trace.api.ProcessTags;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import java.util.Arrays;
import net.bytebuddy.asm.Advice;
import org.springframework.core.env.ConfigurableEnvironment;

Expand Down Expand Up @@ -60,6 +62,17 @@ public static void afterEnvironmentPostProcessed(
final String applicationName = environment.getProperty("spring.application.name");
if (applicationName != null && !applicationName.isEmpty()) {
AgentTracer.get().updatePreferredServiceName(applicationName);
ProcessTags.addTag("springboot.application", applicationName);
}
final String[] profiles = environment.getActiveProfiles();
System.err.println(Arrays.toString(profiles));
if (profiles != null && profiles.length > 0) {
ProcessTags.addTag("springboot.profile", profiles[0]);
} else {
final String[] defaultProfiles = environment.getDefaultProfiles();
if (defaultProfiles != null && defaultProfiles.length > 0) {
ProcessTags.addTag("springboot.profile", defaultProfiles[0]);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import datadog.trace.api.ProcessTags

import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace

import datadog.trace.agent.test.AgentTestRunner
Expand All @@ -10,6 +12,7 @@ class SpringBootApplicationTest extends AgentTestRunner {
protected void configurePreAgent() {
super.configurePreAgent()
}

static class BeanWhoTraces implements InitializingBean {

@Override
Expand All @@ -32,15 +35,19 @@ class SpringBootApplicationTest extends AgentTestRunner {
}
}
})
and:
def processTags = ProcessTags.getTagsForSerialization()
assert processTags.toString() =~ ".+,springboot.application:$expectedService,springboot.profile:$expectedProfile"
context != null
cleanup:
context?.stop()
where:
expectedService | args
"application-name-from-args" | new String[]{
"--spring.application.name=application-name-from-args"
expectedService | expectedProfile | args
"application-name-from-args" | "prod" | new String[]{
"--spring.application.name=application-name-from-args",
"--spring.profiles.active=prod,common",
}
"application-name-from-properties" | new String[0] // will load from properties
"application-name-from-properties" | "default" | new String[0] // will load from properties
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import datadog.trace.agent.tooling.bytebuddy.matcher.GlobalIgnores
import datadog.trace.api.Config
import datadog.trace.api.DDSpanId
import datadog.trace.api.IdGenerationStrategy
import datadog.trace.api.ProcessTags
import datadog.trace.api.StatsDClient
import datadog.trace.api.TraceConfig
import datadog.trace.api.WellKnownTags
Expand Down Expand Up @@ -509,6 +510,7 @@ abstract class AgentTestRunner extends DDSpecification implements AgentBuilder.L
ActiveSubsystems.APPSEC_ACTIVE = true
}
InstrumentationErrors.resetErrorCount()
ProcessTags.reset()
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ public final class ConfigDefaults {
static final int DEFAULT_TELEMETRY_DEPENDENCY_RESOLUTION_QUEUE_SIZE = 100000;

static final Set<String> DEFAULT_TRACE_EXPERIMENTAL_FEATURES_ENABLED =
new HashSet<>(asList("DD_TAGS", "DD_LOGS_INJECTION"));
new HashSet<>(
asList("DD_TAGS", "DD_LOGS_INJECTION", "EXPERIMENTAL_COLLECT_PROCESS_TAGS_ENABLED"));

static final boolean DEFAULT_TRACE_128_BIT_TRACEID_GENERATION_ENABLED = true;
static final boolean DEFAULT_TRACE_128_BIT_TRACEID_LOGGING_ENABLED = true;
Expand Down
1 change: 1 addition & 0 deletions dd-trace-api/src/main/java/datadog/trace/api/DDTags.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,5 @@ public class DDTags {
public static final String DECISION_MAKER_INHERITED = "_dd.dm.inherited";
public static final String DECISION_MAKER_SERVICE = "_dd.dm.service";
public static final String DECISION_MAKER_RESOURCE = "_dd.dm.resource";
public static final String PROCESS_TAGS = "_dd.process";
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public final class GeneralConfig {
@Deprecated // Use dd.tags instead
public static final String GLOBAL_TAGS = "trace.global.tags";

public static final String EXPERIMENTAL_COLLECT_PROCESS_TAGS_ENABLED =
"experimental.collect.tags.enabled";

public static final String LOG_LEVEL = "log.level";
public static final String TRACE_DEBUG = "trace.debug";
public static final String TRACE_TRIAGE = "trace.triage";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package datadog.trace.core.tagprocessor;

import static datadog.trace.api.DDTags.PROCESS_TAGS;

import datadog.trace.api.ProcessTags;
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
import datadog.trace.core.DDSpanContext;
import java.util.List;
import java.util.Map;

public class ProcessTagsAdder implements TagsPostProcessor {
@Override
public Map<String, Object> processTags(
Map<String, Object> unsafeTags, DDSpanContext spanContext, List<AgentSpanLink> spanLinks) {
final CharSequence processTags = ProcessTags.getTagsForSerialization();
if (processTags != null) {
unsafeTags.put(PROCESS_TAGS, processTags);
}
return unsafeTags;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public final class TagsPostProcessorFactory {

private static class Lazy {
private static TagsPostProcessor create() {
final List<TagsPostProcessor> processors = new ArrayList<>(4);
final List<TagsPostProcessor> processors = new ArrayList<>(7);
processors.add(new PeerServiceCalculator());
if (addBaseService) {
processors.add(new BaseServiceAdder(Config.get().getServiceName()));
Expand All @@ -32,6 +32,9 @@ private static TagsPostProcessor create() {
if (Config.get().isAddSpanPointers("aws")) {
processors.add(new SpanPointersProcessor());
}
if (Config.get().isExperimentalCollectProcessTagsEnabled()) {
processors.add(new ProcessTagsAdder());
}
return new PostProcessorChain(
processors.toArray(processors.toArray(new TagsPostProcessor[0])));
}
Expand All @@ -52,6 +55,7 @@ public static void withAddBaseService(boolean enabled) {
addBaseService = enabled;
Lazy.instance = Lazy.create();
}

/**
* Mostly used for test purposes.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package datadog.trace.core.tagprocessor

import datadog.trace.api.ProcessTags
import datadog.trace.core.DDSpanContext
import datadog.trace.test.util.DDSpecification

class ProcessTagsAdderTest extends DDSpecification {
def "should add _dd.process"() {
setup:
def adder = new ProcessTagsAdder()
def spanContext = Mock(DDSpanContext)

when:
def enrichedTags = adder.processTags([:], spanContext, [])

then:
def firstValue = enrichedTags.get("_dd.process")
assert !firstValue.toString().isEmpty()

when:
ProcessTags.addTag("test", "value")
enrichedTags = adder.processTags([:], spanContext, [])

then:
assert enrichedTags.get("_dd.process").toString() == "$firstValue,test:value"
}
}
7 changes: 7 additions & 0 deletions internal-api/src/main/java/datadog/trace/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ public static String getHostName() {
private final boolean peerServiceDefaultsEnabled;
private final Map<String, String> peerServiceComponentOverrides;
private final boolean removeIntegrationServiceNamesEnabled;
private final boolean experimentalCollectProcessTagsEnabled;
private final Map<String, String> peerServiceMapping;
private final Map<String, String> serviceMapping;
private final Map<String, String> tags;
Expand Down Expand Up @@ -846,6 +847,8 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins
// feature flag to remove fake services in v0
removeIntegrationServiceNamesEnabled =
configProvider.getBoolean(TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED, false);
experimentalCollectProcessTagsEnabled =
configProvider.getBoolean(EXPERIMENTAL_COLLECT_PROCESS_TAGS_ENABLED, false);

peerServiceMapping = configProvider.getMergedMap(TRACE_PEER_SERVICE_MAPPING);

Expand Down Expand Up @@ -2091,6 +2094,10 @@ public Set<String> getExperimentalFeaturesEnabled() {
return experimentalFeaturesEnabled;
}

public boolean isExperimentalCollectProcessTagsEnabled() {
return experimentalCollectProcessTagsEnabled;
}

public boolean isTraceEnabled() {
return instrumenterConfig.isTraceEnabled();
}
Expand Down
120 changes: 120 additions & 0 deletions internal-api/src/main/java/datadog/trace/api/ProcessTags.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package datadog.trace.api;

import datadog.trace.api.env.CapturedEnvironment;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
import datadog.trace.util.TraceUtils;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ProcessTags {
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessTags.class);

private static class Lazy {
static final Map<String, String> TAGS = loadTags();
static volatile UTF8BytesString serializedForm;

private static Map<String, String> loadTags() {
Map<String, String> tags = new LinkedHashMap<>();
try {
fillBaseTags(tags);
fillJbossTags(tags);
} catch (Throwable t) {
LOGGER.debug("Unable to calculate default process tags", t);
}
return tags;
}

private static void insertSysPropIfPresent(
Map<String, String> tags, String propKey, String tagKey) {
String value = System.getProperty(propKey);
if (value != null) {
tags.put(tagKey, value);
}
}

private static boolean insertLastPathSegmentIfPresent(
Map<String, String> tags, String path, String tagKey) {
if (path == null || path.isEmpty()) {
return false;
}
try {
final Path p = Paths.get(path).getFileName();
if (p != null) {
tags.put(tagKey, p.toString());
return true;
}
} catch (Throwable ignored) {
}
return false;
}

private static void fillBaseTags(Map<String, String> tags) {
final CapturedEnvironment.ProcessInfo processInfo =
CapturedEnvironment.get().getProcessInfo();
if (processInfo.mainClass != null) {
tags.put("entrypoint.name", processInfo.mainClass);
}
if (processInfo.jarFile != null) {
final String jarName = processInfo.jarFile.getName();
tags.put("entrypoint.name", jarName.substring(0, jarName.length() - 4)); // strip .jar
insertLastPathSegmentIfPresent(tags, processInfo.jarFile.getParent(), "entrypoint.basedir");
}

insertLastPathSegmentIfPresent(tags, System.getProperty("user.dir"), "entrypoint.workdir");
}

private static void fillJbossTags(Map<String, String> tags) {
if (insertLastPathSegmentIfPresent(
tags, System.getProperty("jboss.home.dir"), "jboss.home")) {
insertSysPropIfPresent(tags, "jboss.server.name", "server.name");
tags.put(
"jboss.mode",
System.getProperties().containsKey("[Standalone]") ? "standalone" : "domain");
}
}

static synchronized UTF8BytesString calculateSerializedForm() {
if (serializedForm == null && !TAGS.isEmpty()) {
serializedForm =
UTF8BytesString.create(
TAGS.entrySet().stream()
.map(entry -> entry.getKey() + ":" + TraceUtils.normalizeTag(entry.getValue()))
.collect(Collectors.joining(",")));
}
return serializedForm;
}
}

private ProcessTags() {}

// need to be synchronized on writing. As optimization, it does not need to be sync on read.
public static synchronized void addTag(String key, String value) {
Lazy.TAGS.put(key, value);
Lazy.serializedForm = null;
}

public static UTF8BytesString getTagsForSerialization() {
final UTF8BytesString serializedForm = Lazy.serializedForm;
if (serializedForm != null) {
return serializedForm;
}
return Lazy.calculateSerializedForm();
}

/** Visible for testing. */
static void empty() {
Lazy.TAGS.clear();
Lazy.serializedForm = null;
}

/** Visible for testing. */
static void reset() {
empty();
Lazy.TAGS.putAll(Lazy.loadTags());
}
}
Loading