Skip to content

Commit 460bf14

Browse files
authored
collect and propagate process tags in first span of each chunk (#6733)
- collects basic process tags - tag serializer - send in first chunk span every time we send traces to the agent - create DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED=false to configure - TODO: as a follow-up this should only add tags to the first span of the first chunk of a network request
1 parent 237ea96 commit 460bf14

File tree

8 files changed

+458
-7
lines changed

8 files changed

+458
-7
lines changed

packages/dd-trace/src/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ class Config {
454454
DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS,
455455
DD_ENV,
456456
DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED,
457+
DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED,
457458
DD_PROFILING_ENABLED,
458459
DD_GRPC_CLIENT_ERROR_STATUSES,
459460
DD_GRPC_SERVER_ERROR_STATUSES,
@@ -658,6 +659,7 @@ class Config {
658659
DD_APM_TRACING_ENABLED ??
659660
(DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED && isFalse(DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED))
660661
)
662+
this.#setBoolean(target, 'propagateProcessTags.enabled', DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED)
661663
this.#setString(target, 'appKey', DD_APP_KEY)
662664
this.#setBoolean(target, 'appsec.apiSecurity.enabled', DD_API_SECURITY_ENABLED && isTrue(DD_API_SECURITY_ENABLED))
663665
target['appsec.apiSecurity.sampleDelay'] = maybeFloat(DD_API_SECURITY_SAMPLE_DELAY)

packages/dd-trace/src/config_defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ module.exports = {
161161
plugins: true,
162162
port: '8126',
163163
'profiling.enabled': undefined,
164+
'propagateProcessTags.enabled': undefined,
164165
'profiling.exporters': 'agent',
165166
'profiling.sourceMap': true,
166167
'profiling.longLivedThreshold': undefined,
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'use strict'
2+
3+
const path = require('node:path')
4+
const pkg = require('../pkg')
5+
6+
const CURRENT_WORKING_DIRECTORY = process.cwd()
7+
const ENTRYPOINT_PATH = require.main?.filename || ''
8+
9+
const TRACING_FIELD_NAME = '_dd.tags.process'
10+
const DSM_FIELD_NAME = 'ProcessTags'
11+
const PROFILING_FIELD_NAME = 'process_tags'
12+
13+
module.exports.TRACING_FIELD_NAME = TRACING_FIELD_NAME
14+
module.exports.DSM_FIELD_NAME = DSM_FIELD_NAME
15+
module.exports.PROFILING_FIELD_NAME = PROFILING_FIELD_NAME
16+
17+
// TODO CRASH_TRACKING_FIELD_NAME /process_tags /application/process_tags
18+
// TODO: TELEMETRY_FIELD_NAME /application/process_tags
19+
// TODO: DYNAMIC_INSTRUMENTATION_FIELD_NAME process_tags
20+
// TODO: CLIENT_TRACE_STATISTICS_FIELD_NAME process_tags
21+
// TODO: REMOTE_CONFIG_FIELD_NAME process_tags
22+
23+
// $ cd /foo/bar && node baz/banana.js
24+
// entrypoint.workdir = bar
25+
// entrypoint.name = banana
26+
// entrypoint.type = script
27+
// entrypoint.basedir = baz
28+
// package.json.name = <from package.json>
29+
30+
module.exports = function getProcessTags () {
31+
// this list is sorted alphabetically for consistent serialization
32+
const tags = [
33+
// the parent directory name of the entrypoint script, e.g. /foo/bar/baz/banana.js -> baz
34+
['entrypoint.basedir', ENTRYPOINT_PATH === '' ? undefined : path.basename(path.dirname(ENTRYPOINT_PATH))],
35+
36+
// the entrypoint script filename without the extension, e.g. /foo/bar/baz/banana.js -> banana
37+
['entrypoint.name', path.basename(ENTRYPOINT_PATH, path.extname(ENTRYPOINT_PATH)) || undefined],
38+
39+
// always script for JavaScript applications
40+
['entrypoint.type', 'script'],
41+
42+
// last segment of the current working directory, e.g. /foo/bar/baz/ -> baz
43+
['entrypoint.workdir', path.basename(CURRENT_WORKING_DIRECTORY) || undefined],
44+
45+
// the .name field from the application's package.json
46+
['package.json.name', pkg.name || undefined]
47+
]
48+
49+
const serialized = serialize(tags)
50+
51+
return {
52+
tags,
53+
serialized
54+
}
55+
}
56+
57+
function serialize (tags) {
58+
const intermediary = []
59+
for (const [name, value] of tags) {
60+
// if we don't have a value we should send nothing at all
61+
if (value === undefined) continue
62+
intermediary.push(`${name}:${sanitize(value)}`)
63+
}
64+
return intermediary.join(',')
65+
}
66+
67+
module.exports.serialize = serialize
68+
69+
/**
70+
* Sanitize a process tag value
71+
*
72+
* @param {string} value
73+
* @returns {string}
74+
*/
75+
function sanitize (value) {
76+
return String(value)
77+
.toLowerCase()
78+
.replaceAll(/[^a-zA-Z0-9/_.-]+/g, '_')
79+
}
80+
81+
module.exports.sanitize = sanitize

packages/dd-trace/src/span_format.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const tags = require('../../../ext/tags')
55
const id = require('./id')
66
const { isError } = require('./util')
77
const { registerExtraService } = require('./service-naming/extra-services')
8+
const { TRACING_FIELD_NAME } = require('./process-tags')
89

910
const SAMPLING_PRIORITY_KEY = constants.SAMPLING_PRIORITY_KEY
1011
const SAMPLING_RULE_DECISION = constants.SAMPLING_RULE_DECISION
@@ -32,13 +33,13 @@ const map = {
3233
'resource.name': 'resource'
3334
}
3435

35-
function format (span, isChunkRoot) {
36+
function format (span, isFirstSpanInChunk = false, tagForFirstSpanInChunk = false) {
3637
const formatted = formatSpan(span)
3738

3839
extractSpanLinks(formatted, span)
3940
extractSpanEvents(formatted, span)
4041
extractRootTags(formatted, span)
41-
extractChunkTags(formatted, span, isChunkRoot)
42+
extractChunkTags(formatted, span, isFirstSpanInChunk, tagForFirstSpanInChunk)
4243
extractTags(formatted, span)
4344

4445
return formatted
@@ -192,10 +193,14 @@ function extractRootTags (formattedSpan, span) {
192193
addTag({}, formattedSpan.metrics, TOP_LEVEL_KEY, 1)
193194
}
194195

195-
function extractChunkTags (formattedSpan, span, isChunkRoot) {
196+
function extractChunkTags (formattedSpan, span, isFirstSpanInChunk, tagForFirstSpanInChunk) {
196197
const context = span.context()
197198

198-
if (!isChunkRoot) return
199+
if (!isFirstSpanInChunk) return
200+
201+
if (tagForFirstSpanInChunk) {
202+
addTag(formattedSpan.meta, formattedSpan.metrics, TRACING_FIELD_NAME, tagForFirstSpanInChunk)
203+
}
199204

200205
for (const [key, value] of Object.entries(context._trace.tags)) {
201206
addTag(formattedSpan.meta, formattedSpan.metrics, key, value)

packages/dd-trace/src/span_processor.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const spanFormat = require('./span_format')
55
const SpanSampler = require('./span_sampler')
66
const GitMetadataTagger = require('./git_metadata_tagger')
77
const { getEnvironmentVariable } = require('./config-helper')
8+
const getProcessTags = require('./process-tags')
89

910
const startedSpans = new WeakSet()
1011
const finishedSpans = new WeakSet()
@@ -24,6 +25,10 @@ class SpanProcessor {
2425

2526
this._spanSampler = new SpanSampler(config.sampler)
2627
this._gitMetadataTagger = new GitMetadataTagger(config)
28+
29+
this._processTags = config.propagateProcessTags?.enabled
30+
? getProcessTags().serialized
31+
: false
2732
}
2833

2934
sample (span) {
@@ -49,14 +54,14 @@ class SpanProcessor {
4954
this.sample(span)
5055
this._gitMetadataTagger.tagGitMetadata(spanContext)
5156

52-
let isChunkRoot = true
57+
let isFirstSpanInChunk = true
5358

5459
for (const span of started) {
5560
if (span._duration === undefined) {
5661
active.push(span)
5762
} else {
58-
const formattedSpan = spanFormat(span, isChunkRoot)
59-
isChunkRoot = false
63+
const formattedSpan = spanFormat(span, isFirstSpanInChunk, this._processTags)
64+
isFirstSpanInChunk = false
6065
this._stats?.onSpanFinished(formattedSpan)
6166
formatted.push(formattedSpan)
6267
}

packages/dd-trace/src/supported-configurations.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"DD_ENV": ["A"],
7272
"DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED": ["A"],
7373
"DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED": ["A"],
74+
"DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED": ["A"],
7475
"DD_EXPERIMENTAL_TEST_OPT_GIT_CACHE_ENABLED": ["A"],
7576
"DD_EXPERIMENTAL_TEST_OPT_GIT_CACHE_DIR": ["A"],
7677
"DD_EXTERNAL_ENV": ["A"],

0 commit comments

Comments
 (0)