dd-trace 5.91.0 → 5.92.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.91.0",
3
+ "version": "5.92.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -143,7 +143,7 @@
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.13.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 childProcessInfo = normalizeArgs(arguments, shell)
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, arguments)
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 childProcessInfo = normalizeArgs(arguments, shell)
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, arguments)
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 childProcessInfo = normalizeArgs(arguments, shell)
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 = arguments[arguments.length - 1]
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, arguments)
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,
@@ -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 in variables) {
62
- tags[`graphql.variables.${param}`] = 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(info, this.config)
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, pathToArray(info && info.path))
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, info, error } = ctx
79
+ const { field, error, path: pathAsArray } = ctx
80
80
 
81
- const path = getPath(info, this.config)
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 (info, config) {
122
- const responsePathAsArray = config.collapse
123
- ? withCollapse(pathToArray)
124
- : pathToArray
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 pathToArray (path) {
129
- const flattened = []
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, path) {
177
- for (let i = path.length - 1; i > 0; i--) {
178
- const field = getField(parentCtx, path.slice(0, i))
179
- if (field) {
180
- return field
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()) {
@@ -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,
@@ -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