dd-trace 5.91.0 → 5.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/packages/datadog-instrumentations/src/child_process.js +14 -8
- package/packages/datadog-instrumentations/src/graphql.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
- package/packages/datadog-plugin-graphql/src/execute.js +2 -2
- package/packages/datadog-plugin-graphql/src/resolve.js +22 -35
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +2 -0
- package/packages/dd-trace/src/config/index.js +6 -0
- package/packages/dd-trace/src/config/supported-configurations.json +2 -2
- package/packages/dd-trace/src/crashtracking/index.js +7 -1
- package/packages/dd-trace/src/encode/agentless-json.js +4 -1
- package/packages/dd-trace/src/exporters/common/docker.js +1 -0
- package/packages/dd-trace/src/exporters/common/request.js +26 -17
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiling/profiler.js +78 -47
- package/packages/dd-trace/src/proxy.js +4 -3
- package/packages/dd-trace/src/telemetry/send-data.js +5 -0
- package/packages/dd-trace/src/telemetry/session-propagation.js +78 -0
- package/packages/dd-trace/src/telemetry/telemetry.js +3 -0
- package/packages/dd-trace/src/tracer_metadata.js +10 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.93.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -138,12 +138,12 @@
|
|
|
138
138
|
"import-in-the-middle": "^3.0.0"
|
|
139
139
|
},
|
|
140
140
|
"optionalDependencies": {
|
|
141
|
-
"@datadog/libdatadog": "0.
|
|
141
|
+
"@datadog/libdatadog": "0.9.2",
|
|
142
142
|
"@datadog/native-appsec": "11.0.1",
|
|
143
143
|
"@datadog/native-iast-taint-tracking": "4.1.0",
|
|
144
144
|
"@datadog/native-metrics": "3.1.1",
|
|
145
145
|
"@datadog/openfeature-node-server": "^1.1.0",
|
|
146
|
-
"@datadog/pprof": "5.
|
|
146
|
+
"@datadog/pprof": "5.14.0",
|
|
147
147
|
"@datadog/wasm-js-rewriter": "5.0.1",
|
|
148
148
|
"@opentelemetry/api": ">=1.0.0 <1.10.0",
|
|
149
149
|
"@opentelemetry/api-logs": "<1.0.0",
|
|
@@ -12,7 +12,7 @@ const {
|
|
|
12
12
|
const childProcessChannel = dc.tracingChannel('datadog:child_process:execution')
|
|
13
13
|
|
|
14
14
|
// ignored exec method because it calls to execFile directly
|
|
15
|
-
const execAsyncMethods = ['execFile', 'spawn']
|
|
15
|
+
const execAsyncMethods = ['execFile', 'spawn', 'fork']
|
|
16
16
|
|
|
17
17
|
const names = ['child_process', 'node:child_process']
|
|
18
18
|
|
|
@@ -97,8 +97,10 @@ function wrapChildProcessSyncMethod (returnError, shell = false) {
|
|
|
97
97
|
return childProcessMethod.apply(this, arguments)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
const
|
|
100
|
+
const callArgs = [...arguments]
|
|
101
|
+
const childProcessInfo = normalizeArgs(callArgs, shell)
|
|
101
102
|
const context = createContextFromChildProcessInfo(childProcessInfo)
|
|
103
|
+
context.callArgs = callArgs
|
|
102
104
|
|
|
103
105
|
return childProcessChannel.start.runStores(context, () => {
|
|
104
106
|
try {
|
|
@@ -108,7 +110,7 @@ function wrapChildProcessSyncMethod (returnError, shell = false) {
|
|
|
108
110
|
return returnError(error, context)
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
const result = childProcessMethod.apply(this,
|
|
113
|
+
const result = childProcessMethod.apply(this, context.callArgs)
|
|
112
114
|
context.result = result
|
|
113
115
|
|
|
114
116
|
return result
|
|
@@ -131,9 +133,11 @@ function wrapChildProcessCustomPromisifyMethod (customPromisifyMethod, shell) {
|
|
|
131
133
|
return customPromisifyMethod.apply(this, arguments)
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
const
|
|
136
|
+
const callArgs = [...arguments]
|
|
137
|
+
const childProcessInfo = normalizeArgs(callArgs, shell)
|
|
135
138
|
|
|
136
139
|
const context = createContextFromChildProcessInfo(childProcessInfo)
|
|
140
|
+
context.callArgs = callArgs
|
|
137
141
|
|
|
138
142
|
const { start, end, asyncStart, asyncEnd, error } = childProcessChannel
|
|
139
143
|
start.publish(context)
|
|
@@ -143,7 +147,7 @@ function wrapChildProcessCustomPromisifyMethod (customPromisifyMethod, shell) {
|
|
|
143
147
|
result = Promise.reject(context.abortController.signal.reason || new Error('Aborted'))
|
|
144
148
|
} else {
|
|
145
149
|
try {
|
|
146
|
-
result = customPromisifyMethod.apply(this,
|
|
150
|
+
result = customPromisifyMethod.apply(this, context.callArgs)
|
|
147
151
|
} catch (error) {
|
|
148
152
|
context.error = error
|
|
149
153
|
error.publish(context)
|
|
@@ -181,9 +185,11 @@ function wrapChildProcessAsyncMethod (ChildProcess, shell = false) {
|
|
|
181
185
|
return childProcessMethod.apply(this, arguments)
|
|
182
186
|
}
|
|
183
187
|
|
|
184
|
-
const
|
|
188
|
+
const callArgs = [...arguments]
|
|
189
|
+
const childProcessInfo = normalizeArgs(callArgs, shell)
|
|
185
190
|
|
|
186
191
|
const context = createContextFromChildProcessInfo(childProcessInfo)
|
|
192
|
+
context.callArgs = callArgs
|
|
187
193
|
return childProcessChannel.start.runStores(context, () => {
|
|
188
194
|
let childProcess
|
|
189
195
|
if (context.abortController.signal.aborted) {
|
|
@@ -194,7 +200,7 @@ function wrapChildProcessAsyncMethod (ChildProcess, shell = false) {
|
|
|
194
200
|
const error = context.abortController.signal.reason || new Error('Aborted')
|
|
195
201
|
childProcess.emit('error', error)
|
|
196
202
|
|
|
197
|
-
const cb =
|
|
203
|
+
const cb = context.callArgs[context.callArgs.length - 1]
|
|
198
204
|
if (typeof cb === 'function') {
|
|
199
205
|
cb(error)
|
|
200
206
|
}
|
|
@@ -202,7 +208,7 @@ function wrapChildProcessAsyncMethod (ChildProcess, shell = false) {
|
|
|
202
208
|
childProcess.emit('close')
|
|
203
209
|
})
|
|
204
210
|
} else {
|
|
205
|
-
childProcess = childProcessMethod.apply(this,
|
|
211
|
+
childProcess = childProcessMethod.apply(this, context.callArgs)
|
|
206
212
|
}
|
|
207
213
|
|
|
208
214
|
if (childProcess) {
|
|
@@ -277,7 +277,7 @@ function assertField (rootCtx, info, args) {
|
|
|
277
277
|
let field = fields[pathString]
|
|
278
278
|
|
|
279
279
|
if (!field) {
|
|
280
|
-
const fieldCtx = { info, rootCtx, args }
|
|
280
|
+
const fieldCtx = { info, rootCtx, args, path, pathString }
|
|
281
281
|
startResolveCh.publish(fieldCtx)
|
|
282
282
|
field = fields[pathString] = {
|
|
283
283
|
error: null,
|
|
@@ -154,6 +154,11 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
154
154
|
})
|
|
155
155
|
|
|
156
156
|
this.finish(ctx)
|
|
157
|
+
|
|
158
|
+
if (IS_SERVERLESS) {
|
|
159
|
+
const peerStore = storage('peerServerless').getStore()
|
|
160
|
+
if (peerStore) delete peerStore.peerHostname
|
|
161
|
+
}
|
|
157
162
|
})
|
|
158
163
|
|
|
159
164
|
this.addBind(`apm:aws:response:start:${this.serviceIdentifier}`, ctx => {
|
|
@@ -58,8 +58,8 @@ function addVariableTags (config, span, variableValues) {
|
|
|
58
58
|
|
|
59
59
|
if (variableValues && config.variables) {
|
|
60
60
|
const variables = config.variables(variableValues)
|
|
61
|
-
for (const param
|
|
62
|
-
tags[`graphql.variables.${param}`] =
|
|
61
|
+
for (const [param, value] of Object.entries(variables)) {
|
|
62
|
+
tags[`graphql.variables.${param}`] = value
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -10,13 +10,13 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
10
10
|
static operation = 'resolve'
|
|
11
11
|
|
|
12
12
|
start (fieldCtx) {
|
|
13
|
-
const { info, rootCtx, args } = fieldCtx
|
|
13
|
+
const { info, rootCtx, args, path: pathAsArray, pathString } = fieldCtx
|
|
14
14
|
|
|
15
|
-
const path = getPath(
|
|
15
|
+
const path = getPath(this.config, pathAsArray)
|
|
16
16
|
|
|
17
17
|
// we need to get the parent span to the field if it exists for correct span parenting
|
|
18
18
|
// of nested fields
|
|
19
|
-
const parentField = getParentField(rootCtx,
|
|
19
|
+
const parentField = getParentField(rootCtx, pathString)
|
|
20
20
|
const childOf = parentField?.ctx?.currentStore?.span
|
|
21
21
|
|
|
22
22
|
fieldCtx.parent = parentField
|
|
@@ -76,9 +76,9 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
76
76
|
super(...args)
|
|
77
77
|
|
|
78
78
|
this.addTraceSub('updateField', (ctx) => {
|
|
79
|
-
const { field,
|
|
79
|
+
const { field, error, path: pathAsArray } = ctx
|
|
80
80
|
|
|
81
|
-
const path = getPath(
|
|
81
|
+
const path = getPath(this.config, pathAsArray)
|
|
82
82
|
|
|
83
83
|
if (!shouldInstrument(this.config, path)) return
|
|
84
84
|
|
|
@@ -118,28 +118,14 @@ function shouldInstrument (config, path) {
|
|
|
118
118
|
return config.depth < 0 || config.depth >= depth
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function getPath (
|
|
122
|
-
|
|
123
|
-
? withCollapse(
|
|
124
|
-
:
|
|
125
|
-
return responsePathAsArray(info && info.path)
|
|
121
|
+
function getPath (config, pathAsArray) {
|
|
122
|
+
return config.collapse
|
|
123
|
+
? withCollapse(pathAsArray)
|
|
124
|
+
: pathAsArray
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
let curr = path
|
|
131
|
-
while (curr) {
|
|
132
|
-
flattened.push(curr.key)
|
|
133
|
-
curr = curr.prev
|
|
134
|
-
}
|
|
135
|
-
return flattened.reverse()
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function withCollapse (responsePathAsArray) {
|
|
139
|
-
return function () {
|
|
140
|
-
return responsePathAsArray.apply(this, arguments)
|
|
141
|
-
.map(segment => typeof segment === 'number' ? '*' : segment)
|
|
142
|
-
}
|
|
127
|
+
function withCollapse (pathAsArray) {
|
|
128
|
+
return pathAsArray.map(segment => typeof segment === 'number' ? '*' : segment)
|
|
143
129
|
}
|
|
144
130
|
|
|
145
131
|
function getResolverInfo (info, args) {
|
|
@@ -173,19 +159,20 @@ function getResolverInfo (info, args) {
|
|
|
173
159
|
return resolverInfo
|
|
174
160
|
}
|
|
175
161
|
|
|
176
|
-
function getParentField (parentCtx,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
162
|
+
function getParentField (parentCtx, pathToString) {
|
|
163
|
+
let current = pathToString
|
|
164
|
+
|
|
165
|
+
while (current) {
|
|
166
|
+
const lastJoin = current.lastIndexOf('.')
|
|
167
|
+
if (lastJoin === -1) break
|
|
168
|
+
|
|
169
|
+
current = current.slice(0, lastJoin)
|
|
170
|
+
const field = parentCtx.fields[current]
|
|
171
|
+
|
|
172
|
+
if (field) return field
|
|
182
173
|
}
|
|
183
174
|
|
|
184
175
|
return null
|
|
185
176
|
}
|
|
186
177
|
|
|
187
|
-
function getField (parentCtx, path) {
|
|
188
|
-
return parentCtx.fields[path.join('.')]
|
|
189
|
-
}
|
|
190
|
-
|
|
191
178
|
module.exports = GraphQLResolvePlugin
|
|
@@ -211,7 +211,7 @@ function getHeaders (config) {
|
|
|
211
211
|
if (typeof header === 'string') {
|
|
212
212
|
const separatorIndex = header.indexOf(':')
|
|
213
213
|
result.push(separatorIndex === -1
|
|
214
|
-
? [header, undefined]
|
|
214
|
+
? [header.toLowerCase(), undefined]
|
|
215
215
|
: [
|
|
216
216
|
header.slice(0, separatorIndex).toLowerCase(),
|
|
217
217
|
header.slice(separatorIndex + 1),
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const pkg = require('../pkg')
|
|
4
4
|
const { isFalse, isTrue } = require('../util')
|
|
5
|
+
const { DD_MAJOR } = require('../../../../version')
|
|
5
6
|
const { getEnvironmentVariable: getEnv } = require('./helper')
|
|
6
7
|
|
|
7
8
|
const {
|
|
@@ -117,6 +118,7 @@ const defaultsWithoutSupportedConfigurationEntry = {
|
|
|
117
118
|
// TODO: These entries should be removed. They are off by default
|
|
118
119
|
// because they rely on other configs.
|
|
119
120
|
const defaultsWithConditionalRuntimeBehavior = {
|
|
121
|
+
startupLogs: DD_MAJOR >= 6,
|
|
120
122
|
isGitUploadEnabled: false,
|
|
121
123
|
isImpactedTestsEnabled: false,
|
|
122
124
|
isIntelligentTestRunnerEnabled: false,
|
|
@@ -49,6 +49,8 @@ const VALID_PROPAGATION_BEHAVIOR_EXTRACT = new Set(['continue', 'restart', 'igno
|
|
|
49
49
|
const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error'])
|
|
50
50
|
const DEFAULT_OTLP_PORT = 4318
|
|
51
51
|
const RUNTIME_ID = uuid()
|
|
52
|
+
// eslint-disable-next-line eslint-rules/eslint-process-env -- internal propagation, not user config
|
|
53
|
+
const ROOT_SESSION_ID = process.env.DD_ROOT_JS_SESSION_ID || RUNTIME_ID
|
|
52
54
|
const NAMING_VERSIONS = new Set(['v0', 'v1'])
|
|
53
55
|
const DEFAULT_NAMING_VERSION = 'v0'
|
|
54
56
|
|
|
@@ -145,6 +147,8 @@ class Config {
|
|
|
145
147
|
'runtime-id': RUNTIME_ID,
|
|
146
148
|
})
|
|
147
149
|
|
|
150
|
+
this.rootSessionId = ROOT_SESSION_ID
|
|
151
|
+
|
|
148
152
|
if (this.isCiVisibility) {
|
|
149
153
|
tagger.add(this.tags, {
|
|
150
154
|
[ORIGIN_KEY]: 'ciapp-test',
|
|
@@ -1125,6 +1129,8 @@ class Config {
|
|
|
1125
1129
|
setBoolean(calc, 'reportHostname', true)
|
|
1126
1130
|
// Clear sampling rules - server-side sampling handles this
|
|
1127
1131
|
calc['sampler.rules'] = []
|
|
1132
|
+
// Agentless intake only accepts 64-bit trace IDs; disable 128-bit generation
|
|
1133
|
+
setBoolean(calc, 'traceId128BitGenerationEnabled', false)
|
|
1128
1134
|
}
|
|
1129
1135
|
|
|
1130
1136
|
if (this.#isCiVisibility()) {
|
|
@@ -744,9 +744,9 @@
|
|
|
744
744
|
],
|
|
745
745
|
"DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED": [
|
|
746
746
|
{
|
|
747
|
-
"implementation": "
|
|
747
|
+
"implementation": "B",
|
|
748
748
|
"type": "boolean",
|
|
749
|
-
"default": "
|
|
749
|
+
"default": "true",
|
|
750
750
|
"configurationNames": [
|
|
751
751
|
"propagateProcessTags.enabled"
|
|
752
752
|
]
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { existsSync } = require('node:fs')
|
|
3
4
|
const { isMainThread } = require('worker_threads')
|
|
4
5
|
const log = require('../log')
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
// libdatadog v29 crashtracker segfaults during init on ARM64 musl (Alpine).
|
|
8
|
+
// The segfault bypasses JS try/catch so we must avoid loading it entirely.
|
|
9
|
+
// See: https://github.com/DataDog/libdatadog-nodejs/issues/114
|
|
10
|
+
const isArm64Musl = process.arch === 'arm64' && existsSync('/etc/alpine-release')
|
|
11
|
+
|
|
12
|
+
if (isMainThread && !isArm64Musl) {
|
|
7
13
|
try {
|
|
8
14
|
module.exports = require('./crashtracker')
|
|
9
15
|
} catch (e) {
|
|
@@ -16,6 +16,9 @@ const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
|
16
16
|
function formatSpan (span, isFirstSpan) {
|
|
17
17
|
span = normalizeSpan(truncateSpan(span, false))
|
|
18
18
|
|
|
19
|
+
// Remove _dd.p.tid (the upper 64 bits of a 128-bit trace ID) since trace_id is truncated to lower 64 bits
|
|
20
|
+
delete span.meta['_dd.p.tid']
|
|
21
|
+
|
|
19
22
|
if (span.span_events) {
|
|
20
23
|
span.meta.events = JSON.stringify(span.span_events)
|
|
21
24
|
delete span.span_events
|
|
@@ -45,7 +48,7 @@ function formatSpan (span, isFirstSpan) {
|
|
|
45
48
|
*/
|
|
46
49
|
function spanToJSON (span) {
|
|
47
50
|
const result = {
|
|
48
|
-
trace_id: span.trace_id.toString(16).toLowerCase(),
|
|
51
|
+
trace_id: span.trace_id.toString(16).toLowerCase().slice(-16),
|
|
49
52
|
span_id: span.span_id.toString(16).toLowerCase(),
|
|
50
53
|
parent_id: span.parent_id.toString(16).toLowerCase(),
|
|
51
54
|
name: span.name,
|
|
@@ -14,9 +14,9 @@ const { urlToHttpOptions } = require('./url-to-http-options-polyfill')
|
|
|
14
14
|
const docker = require('./docker')
|
|
15
15
|
const { httpAgent, httpsAgent } = require('./agents')
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const maxActiveBufferSize = 1024 * 1024 * 64
|
|
18
18
|
|
|
19
|
-
let
|
|
19
|
+
let activeBufferSize = 0
|
|
20
20
|
|
|
21
21
|
function parseUrl (urlObjOrString) {
|
|
22
22
|
if (urlObjOrString !== null && typeof urlObjOrString === 'object') return urlToHttpOptions(urlObjOrString)
|
|
@@ -50,7 +50,22 @@ function request (data, options, callback) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
if (data instanceof Readable) {
|
|
54
|
+
const chunks = []
|
|
55
|
+
|
|
56
|
+
data
|
|
57
|
+
.on('data', (data) => {
|
|
58
|
+
chunks.push(data)
|
|
59
|
+
})
|
|
60
|
+
.on('end', () => {
|
|
61
|
+
request(Buffer.concat(chunks), options, callback)
|
|
62
|
+
})
|
|
63
|
+
.on('error', (err) => {
|
|
64
|
+
callback(err)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return
|
|
68
|
+
}
|
|
54
69
|
|
|
55
70
|
// The timeout should be kept low to avoid excessive queueing.
|
|
56
71
|
const timeout = options.timeout || 2000
|
|
@@ -58,12 +73,10 @@ function request (data, options, callback) {
|
|
|
58
73
|
const client = isSecure ? https : http
|
|
59
74
|
let dataArray = data
|
|
60
75
|
|
|
61
|
-
if (!
|
|
62
|
-
|
|
63
|
-
dataArray = [data]
|
|
64
|
-
}
|
|
65
|
-
options.headers['Content-Length'] = byteLength(dataArray)
|
|
76
|
+
if (!Array.isArray(data)) {
|
|
77
|
+
dataArray = [data]
|
|
66
78
|
}
|
|
79
|
+
options.headers['Content-Length'] = byteLength(dataArray)
|
|
67
80
|
|
|
68
81
|
docker.inject(options.headers)
|
|
69
82
|
|
|
@@ -126,14 +139,14 @@ function request (data, options, callback) {
|
|
|
126
139
|
return callback(null)
|
|
127
140
|
}
|
|
128
141
|
|
|
129
|
-
|
|
142
|
+
activeBufferSize += options.headers['Content-Length'] ?? 0
|
|
130
143
|
|
|
131
144
|
storage('legacy').run({ noop: true }, () => {
|
|
132
145
|
let finished = false
|
|
133
146
|
const finalize = () => {
|
|
134
147
|
if (finished) return
|
|
135
148
|
finished = true
|
|
136
|
-
|
|
149
|
+
activeBufferSize -= options.headers['Content-Length'] ?? 0
|
|
137
150
|
}
|
|
138
151
|
|
|
139
152
|
const req = client.request(options, (res) => onResponse(res, finalize))
|
|
@@ -158,12 +171,8 @@ function request (data, options, callback) {
|
|
|
158
171
|
}
|
|
159
172
|
})
|
|
160
173
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
} else {
|
|
164
|
-
for (const buffer of dataArray) req.write(buffer)
|
|
165
|
-
req.end()
|
|
166
|
-
}
|
|
174
|
+
for (const buffer of dataArray) req.write(buffer)
|
|
175
|
+
req.end()
|
|
167
176
|
})
|
|
168
177
|
}
|
|
169
178
|
|
|
@@ -183,7 +192,7 @@ function byteLength (data) {
|
|
|
183
192
|
|
|
184
193
|
Object.defineProperty(request, 'writable', {
|
|
185
194
|
get () {
|
|
186
|
-
return
|
|
195
|
+
return activeBufferSize < maxActiveBufferSize
|
|
187
196
|
},
|
|
188
197
|
})
|
|
189
198
|
|
|
@@ -37,13 +37,16 @@ const {
|
|
|
37
37
|
const DEFAULT_KEY = 'service:,env:'
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Formats a sampling rate as a string with up to 6
|
|
40
|
+
* Formats a sampling rate as a string with up to 6 decimal digits and no trailing zeros.
|
|
41
41
|
*
|
|
42
42
|
* @param {number} rate
|
|
43
|
-
* @returns {string}
|
|
44
43
|
*/
|
|
45
44
|
function formatKnuthRate (rate) {
|
|
46
|
-
|
|
45
|
+
const string = Number(rate).toFixed(6)
|
|
46
|
+
for (let i = string.length - 1; i > 0; i--) {
|
|
47
|
+
if (string[i] === '0') continue
|
|
48
|
+
return string.slice(0, i + (string[i] === '.' ? 0 : 1))
|
|
49
|
+
}
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
const defaultSampler = new Sampler(AUTO_KEEP)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { EventEmitter } = require('events')
|
|
4
|
-
const { promisify } = require('util')
|
|
5
|
-
const zlib = require('zlib')
|
|
6
4
|
const dc = require('dc-polyfill')
|
|
7
5
|
const crashtracker = require('../crashtracking')
|
|
8
6
|
const log = require('../log')
|
|
@@ -34,6 +32,14 @@ function findWebSpan (startedSpans, spanId) {
|
|
|
34
32
|
return false
|
|
35
33
|
}
|
|
36
34
|
|
|
35
|
+
const MISSING_SOURCE_MAPS_TOKEN = 'dd:has-missing-map-files'
|
|
36
|
+
|
|
37
|
+
function profileHasMissingSourceMaps (profile) {
|
|
38
|
+
const strings = profile?.stringTable?.strings
|
|
39
|
+
if (!strings) return false
|
|
40
|
+
return profile.comment?.some(idx => strings[idx] === MISSING_SOURCE_MAPS_TOKEN) ?? false
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
function processInfo (infos, info, type) {
|
|
38
44
|
if (Object.keys(info).length > 0) {
|
|
39
45
|
infos[type] = info
|
|
@@ -42,6 +48,7 @@ function processInfo (infos, info, type) {
|
|
|
42
48
|
|
|
43
49
|
class Profiler extends EventEmitter {
|
|
44
50
|
#compressionFn
|
|
51
|
+
#compressionFnInitialized = false
|
|
45
52
|
#compressionOptions
|
|
46
53
|
#config
|
|
47
54
|
#enabled = false
|
|
@@ -50,7 +57,6 @@ class Profiler extends EventEmitter {
|
|
|
50
57
|
#logger
|
|
51
58
|
#profileSeq = 0
|
|
52
59
|
#spanFinishListener
|
|
53
|
-
#sourceMapCount = 0
|
|
54
60
|
#timer
|
|
55
61
|
|
|
56
62
|
constructor () {
|
|
@@ -116,11 +122,13 @@ class Profiler extends EventEmitter {
|
|
|
116
122
|
reportHostname,
|
|
117
123
|
}
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
try {
|
|
126
|
+
return this._start(options)
|
|
127
|
+
} catch (err) {
|
|
120
128
|
logError(logger, 'Error starting profiler. For troubleshooting tips, see ' +
|
|
121
129
|
'<https://dtdg.co/nodejs-profiler-troubleshooting>', err)
|
|
122
130
|
return false
|
|
123
|
-
}
|
|
131
|
+
}
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
get enabled () {
|
|
@@ -131,7 +139,50 @@ class Profiler extends EventEmitter {
|
|
|
131
139
|
logError(this.#logger, err)
|
|
132
140
|
}
|
|
133
141
|
|
|
134
|
-
|
|
142
|
+
#getCompressionFn () {
|
|
143
|
+
if (!this.#compressionFnInitialized) {
|
|
144
|
+
this.#compressionFnInitialized = true
|
|
145
|
+
try {
|
|
146
|
+
const { promisify } = require('util')
|
|
147
|
+
const zlib = require('zlib')
|
|
148
|
+
const { method, level: clevel } = this.#config.uploadCompression
|
|
149
|
+
switch (method) {
|
|
150
|
+
case 'gzip':
|
|
151
|
+
this.#compressionFn = promisify(zlib.gzip)
|
|
152
|
+
if (clevel !== undefined) {
|
|
153
|
+
this.#compressionOptions = {
|
|
154
|
+
level: clevel,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
break
|
|
158
|
+
case 'zstd':
|
|
159
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
160
|
+
if (typeof zlib.zstdCompress === 'function') {
|
|
161
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
162
|
+
this.#compressionFn = promisify(zlib.zstdCompress)
|
|
163
|
+
if (clevel !== undefined) {
|
|
164
|
+
this.#compressionOptions = {
|
|
165
|
+
params: {
|
|
166
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
167
|
+
[zlib.constants.ZSTD_c_compressionLevel]: clevel,
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
const zstdCompress = require('@datadog/libdatadog').load('datadog-js-zstd').zstd_compress
|
|
173
|
+
const level = clevel ?? 0 // 0 is zstd default compression level
|
|
174
|
+
this.#compressionFn = (buffer) => Promise.resolve(Buffer.from(zstdCompress(buffer, level)))
|
|
175
|
+
}
|
|
176
|
+
break
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
this.#logError(err)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return this.#compressionFn
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_start (options) {
|
|
135
186
|
if (this.enabled) return true
|
|
136
187
|
|
|
137
188
|
const config = this.#config = new Config(options)
|
|
@@ -148,45 +199,21 @@ class Profiler extends EventEmitter {
|
|
|
148
199
|
setLogger(config.logger)
|
|
149
200
|
|
|
150
201
|
if (config.sourceMap) {
|
|
151
|
-
mapper =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const clevel = config.uploadCompression.level
|
|
163
|
-
switch (config.uploadCompression.method) {
|
|
164
|
-
case 'gzip':
|
|
165
|
-
this.#compressionFn = promisify(zlib.gzip)
|
|
166
|
-
if (clevel !== undefined) {
|
|
167
|
-
this.#compressionOptions = {
|
|
168
|
-
level: clevel,
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
break
|
|
172
|
-
case 'zstd':
|
|
173
|
-
if (typeof zlib.zstdCompress === 'function') { // eslint-disable-line n/no-unsupported-features/node-builtins
|
|
174
|
-
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
175
|
-
this.#compressionFn = promisify(zlib.zstdCompress)
|
|
176
|
-
if (clevel !== undefined) {
|
|
177
|
-
this.#compressionOptions = {
|
|
178
|
-
params: {
|
|
179
|
-
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
180
|
-
[zlib.constants.ZSTD_c_compressionLevel]: clevel,
|
|
181
|
-
},
|
|
182
|
-
}
|
|
202
|
+
mapper = new SourceMapper(config.debugSourceMaps)
|
|
203
|
+
mapper.loadDirectory(process.cwd())
|
|
204
|
+
.then(() => {
|
|
205
|
+
if (config.debugSourceMaps) {
|
|
206
|
+
const count = mapper.infoMap.size
|
|
207
|
+
this.#logger.debug(() => {
|
|
208
|
+
return count === 0
|
|
209
|
+
? 'Found no source maps'
|
|
210
|
+
: `Found source maps for following files: [${[...mapper.infoMap.keys()].join(', ')}]`
|
|
211
|
+
})
|
|
183
212
|
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
break
|
|
213
|
+
})
|
|
214
|
+
.catch((err) => {
|
|
215
|
+
this.#logError(err)
|
|
216
|
+
})
|
|
190
217
|
}
|
|
191
218
|
} catch (err) {
|
|
192
219
|
this.#logError(err)
|
|
@@ -295,7 +322,7 @@ class Profiler extends EventEmitter {
|
|
|
295
322
|
return {
|
|
296
323
|
serverless: this.serverless,
|
|
297
324
|
settings: this.#config.systemInfoReport,
|
|
298
|
-
|
|
325
|
+
hasMissingSourceMaps: false,
|
|
299
326
|
}
|
|
300
327
|
}
|
|
301
328
|
|
|
@@ -332,15 +359,19 @@ class Profiler extends EventEmitter {
|
|
|
332
359
|
|
|
333
360
|
const encodedProfiles = {}
|
|
334
361
|
const infos = this.#createInitialInfos()
|
|
362
|
+
const compressionFn = this.#getCompressionFn()
|
|
335
363
|
|
|
336
364
|
// encode and export asynchronously
|
|
337
365
|
await Promise.all(profiles.map(async ({ profiler, profile, info }) => {
|
|
338
366
|
try {
|
|
339
367
|
const encoded = await profiler.encode(profile)
|
|
340
|
-
const compressed = encoded instanceof Buffer &&
|
|
341
|
-
? await
|
|
368
|
+
const compressed = encoded instanceof Buffer && compressionFn !== undefined
|
|
369
|
+
? await compressionFn(encoded, this.#compressionOptions)
|
|
342
370
|
: encoded
|
|
343
371
|
encodedProfiles[profiler.type] = compressed
|
|
372
|
+
if (profileHasMissingSourceMaps(profile)) {
|
|
373
|
+
infos.hasMissingSourceMaps = true
|
|
374
|
+
}
|
|
344
375
|
processInfo(infos, info, profiler.type)
|
|
345
376
|
this.#logger.debug(() => {
|
|
346
377
|
const profileJson = JSON.stringify(profile, (_, value) => {
|
|
@@ -180,7 +180,7 @@ class Tracer extends NoopProxy {
|
|
|
180
180
|
if (config.profiling.enabled === 'true') {
|
|
181
181
|
this._profilerStarted = this._startProfiler(config)
|
|
182
182
|
} else {
|
|
183
|
-
this._profilerStarted =
|
|
183
|
+
this._profilerStarted = false
|
|
184
184
|
if (config.profiling.enabled === 'auto') {
|
|
185
185
|
const { SSIHeuristics } = require('./profiling/ssi-heuristics')
|
|
186
186
|
const ssiHeuristics = new SSIHeuristics(config)
|
|
@@ -252,6 +252,7 @@ class Tracer extends NoopProxy {
|
|
|
252
252
|
'Error starting profiler. For troubleshooting tips, see <https://dtdg.co/nodejs-profiler-troubleshooting>',
|
|
253
253
|
e
|
|
254
254
|
)
|
|
255
|
+
return false
|
|
255
256
|
}
|
|
256
257
|
}
|
|
257
258
|
|
|
@@ -329,11 +330,11 @@ class Tracer extends NoopProxy {
|
|
|
329
330
|
* @override
|
|
330
331
|
*/
|
|
331
332
|
profilerStarted () {
|
|
332
|
-
if (
|
|
333
|
+
if (this._profilerStarted === undefined) {
|
|
333
334
|
// injection hardening: this is only ever invoked from tests.
|
|
334
335
|
throw new Error('profilerStarted() must be called after init()')
|
|
335
336
|
}
|
|
336
|
-
return this._profilerStarted
|
|
337
|
+
return Promise.resolve(this._profilerStarted)
|
|
337
338
|
}
|
|
338
339
|
|
|
339
340
|
/**
|
|
@@ -91,12 +91,17 @@ let agentTelemetry = true
|
|
|
91
91
|
* @returns {Record<string, string>}
|
|
92
92
|
*/
|
|
93
93
|
function getHeaders (config, application, reqType) {
|
|
94
|
+
const sessionId = config.tags['runtime-id']
|
|
94
95
|
const headers = {
|
|
95
96
|
'content-type': 'application/json',
|
|
96
97
|
'dd-telemetry-api-version': 'v2',
|
|
97
98
|
'dd-telemetry-request-type': reqType,
|
|
98
99
|
'dd-client-library-language': application.language_name,
|
|
99
100
|
'dd-client-library-version': application.tracer_version,
|
|
101
|
+
'dd-session-id': sessionId,
|
|
102
|
+
}
|
|
103
|
+
if (config.rootSessionId && config.rootSessionId !== sessionId) {
|
|
104
|
+
headers['dd-root-session-id'] = config.rootSessionId
|
|
100
105
|
}
|
|
101
106
|
const debug = config.telemetry && config.telemetry.debug
|
|
102
107
|
if (debug) {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const dc = require('dc-polyfill')
|
|
4
|
+
|
|
5
|
+
const childProcessChannel = dc.tracingChannel('datadog:child_process:execution')
|
|
6
|
+
|
|
7
|
+
let subscribed = false
|
|
8
|
+
let rootSessionId
|
|
9
|
+
let runtimeId
|
|
10
|
+
|
|
11
|
+
function injectSessionEnv (existingEnv) {
|
|
12
|
+
// eslint-disable-next-line eslint-rules/eslint-process-env -- not in supported-configurations.json
|
|
13
|
+
const base = existingEnv == null ? process.env : existingEnv
|
|
14
|
+
return {
|
|
15
|
+
...base,
|
|
16
|
+
DD_ROOT_JS_SESSION_ID: rootSessionId,
|
|
17
|
+
DD_PARENT_JS_SESSION_ID: runtimeId,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function findOptionsIndex (args, shell) {
|
|
22
|
+
if (Array.isArray(args[1])) {
|
|
23
|
+
return { index: 2, exists: args[2] != null && typeof args[2] === 'object' }
|
|
24
|
+
}
|
|
25
|
+
if (args[1] != null && typeof args[1] === 'object') {
|
|
26
|
+
return { index: 1, exists: true }
|
|
27
|
+
}
|
|
28
|
+
if (!shell && args[2] != null && typeof args[2] === 'object') {
|
|
29
|
+
return { index: 2, exists: true }
|
|
30
|
+
}
|
|
31
|
+
return { index: shell ? 1 : 2, exists: false }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function onChildProcessStart (context) {
|
|
35
|
+
if (!context.callArgs) return
|
|
36
|
+
|
|
37
|
+
const args = context.callArgs
|
|
38
|
+
const { index, exists } = findOptionsIndex(args, context.shell)
|
|
39
|
+
|
|
40
|
+
if (exists) {
|
|
41
|
+
args[index] = { ...args[index], env: injectSessionEnv(args[index].env) }
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const opts = { env: injectSessionEnv(null) }
|
|
46
|
+
|
|
47
|
+
if (!context.shell && !Array.isArray(args[1])) {
|
|
48
|
+
args.splice(1, 0, [])
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof args[index] === 'function') {
|
|
52
|
+
args.splice(index, 0, opts)
|
|
53
|
+
} else {
|
|
54
|
+
args[index] = opts
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const handler = { start: onChildProcessStart }
|
|
59
|
+
|
|
60
|
+
function start (config) {
|
|
61
|
+
if (!config.telemetry?.enabled || subscribed) return
|
|
62
|
+
subscribed = true
|
|
63
|
+
|
|
64
|
+
rootSessionId = config.rootSessionId
|
|
65
|
+
runtimeId = config.tags['runtime-id']
|
|
66
|
+
|
|
67
|
+
childProcessChannel.subscribe(handler)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function stop () {
|
|
71
|
+
if (!subscribed) return
|
|
72
|
+
childProcessChannel.unsubscribe(handler)
|
|
73
|
+
subscribed = false
|
|
74
|
+
rootSessionId = undefined
|
|
75
|
+
runtimeId = undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { start, stop, _onChildProcessStart: onChildProcessStart }
|
|
@@ -12,6 +12,7 @@ const endpoints = require('./endpoints')
|
|
|
12
12
|
const { sendData } = require('./send-data')
|
|
13
13
|
const { manager: metricsManager } = require('./metrics')
|
|
14
14
|
const telemetryLogger = require('./logs')
|
|
15
|
+
const sessionPropagation = require('./session-propagation')
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* @typedef {Record<string, unknown>} TelemetryPayloadObject
|
|
@@ -370,6 +371,7 @@ function start (aConfig, thePluginManager) {
|
|
|
370
371
|
dependencies.start(config, application, host, getRetryData, updateRetryData)
|
|
371
372
|
telemetryLogger.start(config)
|
|
372
373
|
endpoints.start(config, application, host, getRetryData, updateRetryData)
|
|
374
|
+
sessionPropagation.start(config)
|
|
373
375
|
|
|
374
376
|
sendData(config, application, host, 'app-started', appStarted(config))
|
|
375
377
|
|
|
@@ -397,6 +399,7 @@ function stop () {
|
|
|
397
399
|
telemetryStopChannel.publish(getTelemetryData())
|
|
398
400
|
|
|
399
401
|
endpoints.stop()
|
|
402
|
+
sessionPropagation.stop()
|
|
400
403
|
config = undefined
|
|
401
404
|
}
|
|
402
405
|
|
|
@@ -11,13 +11,22 @@ function storeConfig (config) {
|
|
|
11
11
|
return
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
const { containerId } = require('./exporters/common/docker')
|
|
15
|
+
const processTags = require('./process-tags')
|
|
16
|
+
|
|
17
|
+
const processTagsSerialized = config.propagateProcessTags?.enabled
|
|
18
|
+
? (processTags.serialized || null)
|
|
19
|
+
: null
|
|
20
|
+
|
|
14
21
|
const metadata = new processDiscovery.TracerMetadata(
|
|
15
22
|
config.tags['runtime-id'],
|
|
16
23
|
tracerVersion,
|
|
17
24
|
config.hostname,
|
|
18
25
|
config.service || null,
|
|
19
26
|
config.env || null,
|
|
20
|
-
config.version || null
|
|
27
|
+
config.version || null,
|
|
28
|
+
processTagsSerialized,
|
|
29
|
+
containerId || null
|
|
21
30
|
)
|
|
22
31
|
|
|
23
32
|
return processDiscovery.storeMetadata(metadata)
|