dd-trace 5.96.0 → 5.97.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.
Files changed (114) hide show
  1. package/index.d.ts +34 -0
  2. package/package.json +9 -7
  3. package/packages/datadog-esbuild/index.js +20 -9
  4. package/packages/datadog-instrumentations/src/child_process.js +7 -17
  5. package/packages/datadog-instrumentations/src/crypto.js +1 -2
  6. package/packages/datadog-instrumentations/src/cucumber.js +4 -1
  7. package/packages/datadog-instrumentations/src/cypress-config.js +324 -0
  8. package/packages/datadog-instrumentations/src/cypress.js +86 -4
  9. package/packages/datadog-instrumentations/src/dns.js +1 -2
  10. package/packages/datadog-instrumentations/src/express.js +4 -4
  11. package/packages/datadog-instrumentations/src/fs.js +27 -29
  12. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  13. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +41 -13
  14. package/packages/datadog-instrumentations/src/helpers/hook.js +31 -6
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -19
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -13
  17. package/packages/datadog-instrumentations/src/helpers/register.js +103 -142
  18. package/packages/datadog-instrumentations/src/http/client.js +2 -3
  19. package/packages/datadog-instrumentations/src/http/server.js +2 -5
  20. package/packages/datadog-instrumentations/src/http2/client.js +1 -3
  21. package/packages/datadog-instrumentations/src/http2/server.js +1 -3
  22. package/packages/datadog-instrumentations/src/jest.js +13 -4
  23. package/packages/datadog-instrumentations/src/limitd-client.js +1 -1
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +4 -1
  25. package/packages/datadog-instrumentations/src/net.js +2 -8
  26. package/packages/datadog-instrumentations/src/pino.js +1 -1
  27. package/packages/datadog-instrumentations/src/playwright.js +4 -1
  28. package/packages/datadog-instrumentations/src/prisma.js +1 -2
  29. package/packages/datadog-instrumentations/src/selenium.js +4 -1
  30. package/packages/datadog-instrumentations/src/sequelize.js +1 -1
  31. package/packages/datadog-instrumentations/src/url.js +1 -3
  32. package/packages/datadog-instrumentations/src/vitest.js +5 -1
  33. package/packages/datadog-instrumentations/src/vm.js +1 -3
  34. package/packages/datadog-plugin-aws-sdk/src/base.js +4 -3
  35. package/packages/datadog-plugin-cucumber/src/index.js +7 -3
  36. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +57 -5
  37. package/packages/datadog-plugin-graphql/src/resolve.js +1 -1
  38. package/packages/datadog-plugin-jest/src/index.js +4 -2
  39. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +31 -4
  40. package/packages/datadog-plugin-mocha/src/index.js +5 -2
  41. package/packages/datadog-plugin-next/src/index.js +2 -14
  42. package/packages/datadog-plugin-openai/src/services.js +1 -0
  43. package/packages/datadog-webpack/index.js +3 -3
  44. package/packages/dd-trace/index.js +12 -10
  45. package/packages/dd-trace/src/agent/url.js +2 -2
  46. package/packages/dd-trace/src/aiguard/sdk.js +4 -0
  47. package/packages/dd-trace/src/appsec/blocking.js +3 -0
  48. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +1 -1
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  51. package/packages/dd-trace/src/appsec/remote_config.js +1 -0
  52. package/packages/dd-trace/src/appsec/sdk/index.js +4 -0
  53. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +6 -1
  54. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  55. package/packages/dd-trace/src/config/defaults.js +316 -146
  56. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -1
  57. package/packages/dd-trace/src/config/helper.js +59 -10
  58. package/packages/dd-trace/src/config/index.js +569 -1505
  59. package/packages/dd-trace/src/config/parsers.js +256 -0
  60. package/packages/dd-trace/src/config/remote_config.js +59 -2
  61. package/packages/dd-trace/src/config/supported-configurations.json +350 -433
  62. package/packages/dd-trace/src/crashtracking/crashtracker.js +7 -1
  63. package/packages/dd-trace/src/crashtracking/index.js +1 -7
  64. package/packages/dd-trace/src/debugger/index.js +1 -1
  65. package/packages/dd-trace/src/dogstatsd.js +12 -9
  66. package/packages/dd-trace/src/encode/0.4.js +1 -1
  67. package/packages/dd-trace/src/exporters/agent/writer.js +7 -1
  68. package/packages/dd-trace/src/exporters/common/request.js +9 -0
  69. package/packages/dd-trace/src/exporters/common/writer.js +12 -2
  70. package/packages/dd-trace/src/heap_snapshots.js +3 -0
  71. package/packages/dd-trace/src/index.js +5 -2
  72. package/packages/dd-trace/src/lambda/runtime/ritm.js +6 -6
  73. package/packages/dd-trace/src/llmobs/index.js +4 -1
  74. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -1
  75. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +60 -12
  76. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +4 -2
  77. package/packages/dd-trace/src/llmobs/sdk.js +12 -8
  78. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  79. package/packages/dd-trace/src/llmobs/tagger.js +9 -6
  80. package/packages/dd-trace/src/llmobs/writers/base.js +2 -0
  81. package/packages/dd-trace/src/llmobs/writers/util.js +3 -0
  82. package/packages/dd-trace/src/log/index.js +26 -55
  83. package/packages/dd-trace/src/log/writer.js +7 -19
  84. package/packages/dd-trace/src/noop/proxy.js +8 -0
  85. package/packages/dd-trace/src/opentelemetry/logs/index.js +1 -1
  86. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  87. package/packages/dd-trace/src/opentracing/propagation/text_map.js +9 -4
  88. package/packages/dd-trace/src/payload-tagging/config/index.js +6 -5
  89. package/packages/dd-trace/src/plugin_manager.js +8 -6
  90. package/packages/dd-trace/src/plugins/ci_plugin.js +4 -0
  91. package/packages/dd-trace/src/plugins/plugin.js +7 -4
  92. package/packages/dd-trace/src/process-tags/index.js +3 -0
  93. package/packages/dd-trace/src/profiler.js +27 -2
  94. package/packages/dd-trace/src/profiling/config.js +73 -241
  95. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -4
  96. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +6 -2
  97. package/packages/dd-trace/src/profiling/profiler.js +56 -44
  98. package/packages/dd-trace/src/profiling/profilers/events.js +2 -3
  99. package/packages/dd-trace/src/profiling/profilers/wall.js +89 -6
  100. package/packages/dd-trace/src/profiling/ssi-heuristics.js +4 -1
  101. package/packages/dd-trace/src/propagation-hash/index.js +2 -1
  102. package/packages/dd-trace/src/proxy.js +32 -3
  103. package/packages/dd-trace/src/remote_config/index.js +3 -0
  104. package/packages/dd-trace/src/require-package-json.js +8 -4
  105. package/packages/dd-trace/src/ritm.js +58 -26
  106. package/packages/dd-trace/src/runtime_metrics/index.js +3 -0
  107. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +3 -0
  108. package/packages/dd-trace/src/sampler.js +1 -1
  109. package/packages/dd-trace/src/standalone/index.js +3 -0
  110. package/packages/dd-trace/src/telemetry/index.js +2 -3
  111. package/packages/dd-trace/src/telemetry/send-data.js +5 -19
  112. package/packages/dd-trace/src/telemetry/session-propagation.js +19 -44
  113. package/packages/dd-trace/src/telemetry/telemetry.js +28 -171
  114. package/packages/dd-trace/src/util.js +0 -9
@@ -108,6 +108,8 @@ class NativeWallProfiler {
108
108
  #captureSpanData = false
109
109
  #codeHotspotsEnabled = false
110
110
  #cpuProfilingEnabled = false
111
+ #customLabelsActive = false
112
+ #customLabelKeys
111
113
  #endpointCollectionEnabled = false
112
114
  #flushIntervalMillis = 0
113
115
  #logger
@@ -245,7 +247,24 @@ class NativeWallProfiler {
245
247
  // context -- we simply can't tell which one it might've been across all
246
248
  // possible async context frames.
247
249
  if (this.#asyncContextFrameEnabled) {
248
- this.#pprof.time.setContext(sampleContext)
250
+ if (this.#customLabelsActive) {
251
+ // Custom labels may be active in this async context. The current CPED
252
+ // context could be a 2-element array [profilingContext, customLabels].
253
+ // Replace the profiling context while preserving the custom labels.
254
+ // This flag is monotonic (once set, stays true) because async
255
+ // continuations from runWithLabels can fire at any time after the
256
+ // synchronous runWithLabels call has returned.
257
+ const current = this.#pprof.time.getContext()
258
+ if (Array.isArray(current)) {
259
+ if (current[0] !== sampleContext) {
260
+ this.#pprof.time.setContext([sampleContext, current[1]])
261
+ }
262
+ } else if (current !== sampleContext) {
263
+ this.#pprof.time.setContext(sampleContext)
264
+ }
265
+ } else {
266
+ this.#pprof.time.setContext(sampleContext)
267
+ }
249
268
  } else {
250
269
  const sampleCount = this._profilerState[kSampleCount]
251
270
  if (sampleCount !== this._lastSampleCount) {
@@ -344,6 +363,13 @@ class NativeWallProfiler {
344
363
  const lowCardinalityLabels = Object.keys(getThreadLabels())
345
364
  lowCardinalityLabels.push(TRACE_ENDPOINT_LABEL)
346
365
 
366
+ // Custom labels are expected to be low-cardinality (e.g. customer tier, region)
367
+ if (this.#customLabelKeys) {
368
+ for (const key of this.#customLabelKeys) {
369
+ lowCardinalityLabels.push(key)
370
+ }
371
+ }
372
+
347
373
  const profile = this.#pprof.time.stop(restart, this.#boundGenerateLabels, lowCardinalityLabels)
348
374
 
349
375
  if (restart) {
@@ -383,7 +409,29 @@ class NativeWallProfiler {
383
409
  return getThreadLabels()
384
410
  }
385
411
 
386
- const labels = { ...getThreadLabels() }
412
+ // Native profiler doesn't set context.context for some samples, such as idle samples or when
413
+ // the context was otherwise unavailable when the sample was taken. Note that with ACF, we don't
414
+ // use the "ref" indirection.
415
+ let ref
416
+ let customLabels
417
+ const cctx = context.context
418
+ if (this.#asyncContextFrameEnabled) {
419
+ // When custom labels are active with ACF, context.context is a 2-element array:
420
+ // [profilingContext, customLabels]. Otherwise it's a plain object.
421
+ if (Array.isArray(cctx)) {
422
+ [ref, customLabels] = cctx
423
+ } else {
424
+ ref = cctx
425
+ }
426
+ } else {
427
+ ref = cctx?.ref
428
+ }
429
+
430
+ // Custom labels are spread first so that internal labels always take
431
+ // precedence and overwrite them.
432
+ const labels = customLabels === undefined
433
+ ? { ...getThreadLabels() }
434
+ : { ...customLabels, ...getThreadLabels() }
387
435
 
388
436
  if (this.#timelineEnabled) {
389
437
  // Incoming timestamps are in microseconds, we emit nanos.
@@ -395,10 +443,6 @@ class NativeWallProfiler {
395
443
  labels['async id'] = asyncId
396
444
  }
397
445
 
398
- // Native profiler doesn't set context.context for some samples, such as idle samples or when
399
- // the context was otherwise unavailable when the sample was taken. Note that with async context
400
- // frame, we don't use the "ref" indirection.
401
- const ref = this.#asyncContextFrameEnabled ? context.context : context.context?.ref
402
446
  if (typeof ref !== 'object') {
403
447
  return labels
404
448
  }
@@ -421,6 +465,45 @@ class NativeWallProfiler {
421
465
  return labels
422
466
  }
423
467
 
468
+ /**
469
+ * Sets the custom label keys used for pprof low-cardinality deduplication.
470
+ * Called once by the top-level Profiler when keys are declared.
471
+ *
472
+ * @param {Iterable<string>} keys
473
+ */
474
+ setCustomLabelKeys (keys) {
475
+ this.#customLabelKeys = keys
476
+ }
477
+
478
+ /**
479
+ * Runs a function with custom profiling labels attached to all wall profiler
480
+ * samples taken during its execution. Labels are key-value pairs that appear
481
+ * in the pprof output and can be used to filter flame graphs in the Datadog UI.
482
+ *
483
+ * Requires AsyncContextFrame (ACF) to be enabled. Supports nesting: inner
484
+ * calls merge labels with outer calls, with inner values taking precedence.
485
+ *
486
+ * @param {Record<string, string | number>} labels - Custom labels to attach
487
+ * @param {function(): T} fn - Function to execute with the labels
488
+ * @returns {T} The return value of fn
489
+ * @template T
490
+ */
491
+ runWithLabels (labels, fn) {
492
+ if (!this.#asyncContextFrameEnabled || !this.#withContexts) {
493
+ return fn()
494
+ }
495
+
496
+ // Read current context; merge custom labels if already in a runWithLabels scope
497
+ const current = this.#pprof.time.getContext()
498
+ const isCurrentArray = Array.isArray(current)
499
+ const customLabels = isCurrentArray ? { ...current[1], ...labels } : labels
500
+
501
+ const profilingContext = (isCurrentArray ? current[0] : current) ?? {}
502
+
503
+ this.#customLabelsActive = true
504
+ return this.#pprof.time.runWithContext([profilingContext, customLabels], fn)
505
+ }
506
+
424
507
  profile (restart) {
425
508
  return this.#stop(restart)
426
509
  }
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('dc-polyfill')
3
+ const dc = /** @type {typeof import('diagnostics_channel')} */ (require('dc-polyfill'))
4
4
  const log = require('../log')
5
5
 
6
6
  // If the process lives for at least 30 seconds, it's considered long-lived
@@ -10,6 +10,9 @@ const DEFAULT_LONG_LIVED_THRESHOLD = 30_000
10
10
  * This class embodies the SSI profiler-triggering heuristics under SSI.
11
11
  */
12
12
  class SSIHeuristics {
13
+ /**
14
+ * @param {import('../config/config-base')} config - Tracer configuration
15
+ */
13
16
  constructor (config) {
14
17
  const longLivedThreshold = config.profiling.longLivedThreshold || DEFAULT_LONG_LIVED_THRESHOLD
15
18
  if (typeof longLivedThreshold !== 'number' || longLivedThreshold <= 0) {
@@ -17,11 +17,12 @@ class PropagationHashManager {
17
17
  _cachedHash = null
18
18
  _cachedHashString = null
19
19
  _cachedHashBase64 = null
20
+ /** @type {import('../config/config-base') | null} */
20
21
  _config = null
21
22
 
22
23
  /**
23
24
  * Configure the propagation hash manager with tracer config
24
- * @param {object} config - Tracer configuration
25
+ * @param {import('../config/config-base')} config - Tracer configuration
25
26
  */
26
27
  configure (config) {
27
28
  this._config = config
@@ -27,9 +27,12 @@ class LazyModule {
27
27
  this.provider = provider
28
28
  }
29
29
 
30
- enable (...args) {
30
+ /**
31
+ * @param {import('./config/config-base')} config - Tracer configuration
32
+ */
33
+ enable (config, ...args) {
31
34
  this.module = this.provider()
32
- this.module.enable(...args)
35
+ this.module.enable(config, ...args)
33
36
  }
34
37
 
35
38
  disable () {
@@ -238,12 +241,16 @@ class Tracer extends NoopProxy {
238
241
  getDynamicInstrumentationClient(config)
239
242
  }
240
243
  } catch (e) {
241
- log.error('Error initialising tracer', e)
244
+ log.error('Error initializing tracer', e)
245
+ // TODO: Should we stop everything started so far?
242
246
  }
243
247
 
244
248
  return this
245
249
  }
246
250
 
251
+ /**
252
+ * @param {import('./config/config-base')} config - Tracer configuration
253
+ */
247
254
  _startProfiler (config) {
248
255
  // do not stop tracer initialization if the profiler fails to be imported
249
256
  try {
@@ -257,6 +264,9 @@ class Tracer extends NoopProxy {
257
264
  }
258
265
  }
259
266
 
267
+ /**
268
+ * @param {import('./config/config-base')} config - Tracer configuration
269
+ */
260
270
  #updateTracing (config) {
261
271
  if (config.tracing !== false) {
262
272
  if (config.appsec.enabled) {
@@ -330,6 +340,25 @@ class Tracer extends NoopProxy {
330
340
  }
331
341
  }
332
342
 
343
+ /**
344
+ * @override
345
+ */
346
+ get profiling () {
347
+ // Lazily require the profiler module and cache the result. If profiling
348
+ // is not enabled, runWithLabels still works as a passthrough (just calls fn()).
349
+ const profilerModule = require('./profiler')
350
+ const profiling = {
351
+ setCustomLabelKeys (keys) {
352
+ profilerModule.setCustomLabelKeys(keys)
353
+ },
354
+ runWithLabels (labels, fn) {
355
+ return profilerModule.runWithLabels(labels, fn)
356
+ },
357
+ }
358
+ Reflect.defineProperty(this, 'profiling', { value: profiling, configurable: true, enumerable: true })
359
+ return profiling
360
+ }
361
+
333
362
  /**
334
363
  * @override
335
364
  */
@@ -25,6 +25,9 @@ class RemoteConfig {
25
25
  #products = new Set()
26
26
  #batchHandlers = new Map()
27
27
 
28
+ /**
29
+ * @param {import('../config/config-base')} config - Tracer configuration
30
+ */
28
31
  constructor (config) {
29
32
  const pollInterval = Math.floor(config.remoteConfig.pollInterval * 1000)
30
33
 
@@ -21,10 +21,14 @@ function requirePackageJson (name, module) {
21
21
  }
22
22
  for (const modulePath of module.paths) {
23
23
  const candidate = path.join(modulePath, name, 'package.json')
24
- try {
25
- return JSON.parse(fs.readFileSync(candidate, 'utf8'))
26
- } catch {
27
- continue
24
+ // fs.existsSync is faster than fs.readFileSync due to not throwing an error if the file does not exist.
25
+ // The race condition should also not matter here as the time window is very small.
26
+ if (fs.existsSync(candidate)) {
27
+ try {
28
+ return JSON.parse(fs.readFileSync(candidate, 'utf8'))
29
+ } catch {
30
+ continue
31
+ }
28
32
  }
29
33
  }
30
34
  throw new Error(`could not find ${name}/package.json`)
@@ -22,19 +22,44 @@ let patchedRequire = null
22
22
  const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
23
23
  const moduleLoadEndChannel = dc.channel('dd-trace:moduleLoadEnd')
24
24
 
25
+ function stripNodePrefix (name) {
26
+ if (typeof name !== 'string') return name
27
+ return name.startsWith('node:') ? name.slice(5) : name
28
+ }
29
+
30
+ const builtinModules = new Set(Module.builtinModules.map(stripNodePrefix))
31
+
32
+ function isBuiltinModuleName (name) {
33
+ if (typeof name !== 'string') return false
34
+ return builtinModules.has(stripNodePrefix(name))
35
+ }
36
+
37
+ function normalizeModuleName (name) {
38
+ if (typeof name !== 'string') return name
39
+ const stripped = stripNodePrefix(name)
40
+ return builtinModules.has(stripped) ? stripped : name
41
+ }
42
+
43
+ /**
44
+ * @overload
45
+ * @param {string[]} modules list of modules to hook into
46
+ * @param {object} options hook options
47
+ * @param {Function} onrequire callback to be executed upon encountering module
48
+ */
49
+ /**
50
+ * @overload
51
+ * @param {string[]} modules list of modules to hook into
52
+ * @param {Function} onrequire callback to be executed upon encountering module
53
+ */
25
54
  function Hook (modules, options, onrequire) {
26
55
  if (!(this instanceof Hook)) return new Hook(modules, options, onrequire)
27
- if (typeof modules === 'function') {
28
- onrequire = modules
29
- modules = null
30
- options = {}
31
- } else if (typeof options === 'function') {
56
+ if (typeof options === 'function') {
32
57
  onrequire = options
33
58
  options = {}
34
59
  }
35
60
 
36
- modules = modules || []
37
- options = options || {}
61
+ modules ??= []
62
+ options ??= {}
38
63
 
39
64
  this.modules = modules
40
65
  this.options = options
@@ -63,32 +88,34 @@ function Hook (modules, options, onrequire) {
63
88
  */
64
89
  let filename
65
90
  try {
66
- // @ts-expect-error Module._resolveFilename is not typed
91
+ // @ts-expect-error - Module._resolveFilename is not typed
67
92
  filename = Module._resolveFilename(request, this)
68
93
  } catch {
69
94
  return _origRequire.apply(this, arguments)
70
95
  }
71
- const core = !filename.includes(path.sep)
96
+
97
+ const builtin = isBuiltinModuleName(filename)
98
+ const moduleId = builtin ? normalizeModuleName(filename) : filename
72
99
  let name, basedir, hooks
73
100
  // return known patched modules immediately
74
- if (cache[filename]) {
75
- const externalCacheEntry = require.cache[filename]
101
+ if (cache[moduleId]) {
76
102
  // require.cache was potentially altered externally
77
- if (externalCacheEntry && externalCacheEntry.exports !== cache[filename].original) {
78
- return externalCacheEntry.exports
103
+ const cacheEntry = require.cache[filename]
104
+ if (cacheEntry && cacheEntry.exports !== cache[filename].original) {
105
+ return cacheEntry.exports
79
106
  }
80
107
 
81
- return cache[filename].exports
108
+ return cache[moduleId].exports
82
109
  }
83
110
 
84
111
  // Check if this module has a patcher in-progress already.
85
112
  // Otherwise, mark this module as patching in-progress.
86
- const patched = patching[filename]
113
+ const patched = patching[moduleId]
87
114
  if (patched) {
88
115
  // If it's already patched, just return it as-is.
89
116
  return origRequire.apply(this, arguments)
90
117
  }
91
- patching[filename] = true
118
+ patching[moduleId] = true
92
119
 
93
120
  const payload = {
94
121
  filename,
@@ -107,12 +134,12 @@ function Hook (modules, options, onrequire) {
107
134
 
108
135
  // The module has already been loaded,
109
136
  // so the patching mark can be cleaned up.
110
- delete patching[filename]
137
+ delete patching[moduleId]
111
138
 
112
- if (core) {
113
- hooks = moduleHooks[filename]
139
+ if (builtin) {
140
+ hooks = moduleHooks[moduleId]
114
141
  if (!hooks) return exports // abort if module name isn't on whitelist
115
- name = filename
142
+ name = moduleId
116
143
  } else {
117
144
  const inAWSLambda = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined
118
145
  const hasLambdaHandler = getValueFromEnvSources('DD_LAMBDA_HANDLER') !== undefined
@@ -129,7 +156,8 @@ function Hook (modules, options, onrequire) {
129
156
  hooks = moduleHooks[name]
130
157
  if (!hooks) return exports // abort if module name isn't on whitelist
131
158
 
132
- // @ts-expect-error Module._resolveLookupPaths is not typed
159
+ // figure out if this is the main module file, or a file inside the module
160
+ // @ts-expect-error - Module._resolveLookupPaths is meant to be internal and is not typed
133
161
  const paths = Module._resolveLookupPaths(name, this, true)
134
162
  if (!paths) {
135
163
  // abort if _resolveLookupPaths return null
@@ -138,7 +166,7 @@ function Hook (modules, options, onrequire) {
138
166
 
139
167
  let res
140
168
  try {
141
- // @ts-expect-error Module._findPath is not typed
169
+ // @ts-expect-error - Module._findPath is meant to be internal and is not typed
142
170
  res = Module._findPath(name, [basedir, ...paths])
143
171
  } catch {
144
172
  // case where the file specified in package.json "main" doesn't exist
@@ -163,17 +191,21 @@ function Hook (modules, options, onrequire) {
163
191
 
164
192
  // ensure that the cache entry is assigned a value before calling
165
193
  // onrequire, in case calling onrequire requires the same module.
166
- cache[filename] = { exports }
167
- cache[filename].original = exports
194
+ cache[moduleId] = { exports }
195
+ cache[moduleId].original = exports
168
196
 
169
197
  for (const hook of hooks) {
170
- cache[filename].exports = hook(cache[filename].exports, name, basedir)
198
+ cache[moduleId].exports = hook(cache[moduleId].exports, name, basedir)
171
199
  }
172
200
 
173
- return cache[filename].exports
201
+ return cache[moduleId].exports
174
202
  }
175
203
  }
176
204
 
205
+ /**
206
+ * Reset the Ritm hook. This is used to reset the hook after a test.
207
+ * TODO: Remove this and instead use proxyquire to reset the hook.
208
+ */
177
209
  Hook.reset = function () {
178
210
  Module.prototype.require = origRequire
179
211
  patchedRequire = null
@@ -14,6 +14,9 @@ const noop = runtimeMetrics = {
14
14
  }
15
15
 
16
16
  module.exports = {
17
+ /**
18
+ * @param {import('../config/config-base')} config - Tracer configuration
19
+ */
17
20
  start (config) {
18
21
  if (!config?.runtimeMetrics.enabled) return
19
22
 
@@ -35,6 +35,9 @@ let eventLoopDelayObserver = null
35
35
  // https://github.com/DataDog/dogweb/blob/prod/integration/node/node_metadata.csv
36
36
 
37
37
  module.exports = {
38
+ /**
39
+ * @param {import('../config/config-base')} config - Tracer configuration
40
+ */
38
41
  start (config) {
39
42
  this.stop()
40
43
  const clientConfig = DogStatsDClient.generateClientConfig(config)
@@ -42,7 +42,7 @@ class Sampler {
42
42
  /**
43
43
  * Determines whether a trace/span should be sampled based on the configured sampling rate.
44
44
  *
45
- * @param {Span|SpanContext} span - The span or span context to evaluate.
45
+ * @param {import("../../..").Span|import("../../..").SpanContext} span - The span or span context to evaluate.
46
46
  * @returns {boolean} `true` if the trace/span should be sampled, otherwise `false`.
47
47
  */
48
48
  isSampled (span) {
@@ -11,6 +11,9 @@ const startCh = channel('dd-trace:span:start')
11
11
  const injectCh = channel('dd-trace:span:inject')
12
12
  const extractCh = channel('dd-trace:span:extract')
13
13
 
14
+ /**
15
+ * @param {import('../config/config-base')} config - Tracer configuration
16
+ */
14
17
  function configure (config) {
15
18
  if (startCh.hasSubscribers) startCh.unsubscribe(onSpanStart)
16
19
  if (injectCh.hasSubscribers) injectCh.unsubscribe(onSpanInject)
@@ -5,15 +5,14 @@ let telemetry
5
5
  // Lazy load the telemetry module to avoid the performance impact of loading it unconditionally
6
6
  module.exports = {
7
7
  start (config, ...args) {
8
+ if (!config.telemetry.enabled) return
8
9
  telemetry ??= require('./telemetry')
9
10
  telemetry.start(config, ...args)
10
11
  },
11
- stop () {
12
- telemetry?.stop()
13
- },
14
12
  // This might be called before `start` so we have to trigger loading the
15
13
  // underlying module here as well.
16
14
  updateConfig (changes, config, ...args) {
15
+ if (!config.telemetry.enabled) return
17
16
  telemetry ??= require('./telemetry')
18
17
  telemetry.updateConfig(changes, config, ...args)
19
18
  },
@@ -62,19 +62,6 @@ const { getValueFromEnvSources } = require('../config/helper')
62
62
  * kernel_name?: string
63
63
  * } & Record<string, unknown>} TelemetryHost
64
64
  */
65
- /**
66
- * @typedef {{
67
- * hostname?: string,
68
- * port?: string | number,
69
- * url?: string | URL,
70
- * site?: string,
71
- * apiKey?: string,
72
- * isCiVisibility?: boolean,
73
- * spanAttributeSchema?: string,
74
- * tags: Record<string, string>,
75
- * telemetry?: { debug?: boolean }
76
- * }} TelemetryConfig
77
- */
78
65
  /**
79
66
  * @callback SendDataCallback
80
67
  * @param {Error | null | undefined} error
@@ -85,23 +72,22 @@ const { getValueFromEnvSources } = require('../config/helper')
85
72
  let agentTelemetry = true
86
73
 
87
74
  /**
88
- * @param {TelemetryConfig} config
75
+ * @param {import('../config/config-base')} config
89
76
  * @param {TelemetryApplication} application
90
77
  * @param {TelemetryRequestType} reqType
91
78
  * @returns {Record<string, string>}
92
79
  */
93
80
  function getHeaders (config, application, reqType) {
94
- const sessionId = config.tags['runtime-id']
95
81
  const headers = {
96
82
  'content-type': 'application/json',
97
83
  'dd-telemetry-api-version': 'v2',
98
84
  'dd-telemetry-request-type': reqType,
99
85
  'dd-client-library-language': application.language_name,
100
86
  'dd-client-library-version': application.tracer_version,
101
- 'dd-session-id': sessionId,
87
+ 'dd-session-id': config.tags['runtime-id'],
102
88
  }
103
- if (config.rootSessionId && config.rootSessionId !== sessionId) {
104
- headers['dd-root-session-id'] = config.rootSessionId
89
+ if (config.DD_ROOT_JS_SESSION_ID) {
90
+ headers['dd-root-session-id'] = config.DD_ROOT_JS_SESSION_ID
105
91
  }
106
92
  const debug = config.telemetry && config.telemetry.debug
107
93
  if (debug) {
@@ -141,7 +127,7 @@ function getPayload (payload) {
141
127
 
142
128
  // TODO(BridgeAR): Simplify this code. A lot does not need to be recalculated on every call.
143
129
  /**
144
- * @param {TelemetryConfig} config
130
+ * @param {import('../config/config-base')} config
145
131
  * @param {TelemetryApplication} application
146
132
  * @param {TelemetryHost} host
147
133
  * @param {TelemetryRequestType} reqType
@@ -1,53 +1,37 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('dc-polyfill')
4
-
3
+ const dc = /** @type {typeof import('diagnostics_channel')} */ (require('dc-polyfill'))
5
4
  const childProcessChannel = dc.tracingChannel('datadog:child_process:execution')
6
5
 
7
6
  let subscribed = false
8
- let rootSessionId
9
7
  let runtimeId
10
8
 
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
- }
9
+ function isOptionsObject (value) {
10
+ return value != null && typeof value === 'object' && !Array.isArray(value) && value
19
11
  }
20
12
 
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 }
13
+ function getEnvWithRuntimeId (env) {
14
+ // eslint-disable-next-line eslint-rules/eslint-process-env
15
+ return { ...(env ?? process.env), DD_ROOT_JS_SESSION_ID: runtimeId }
32
16
  }
33
17
 
34
18
  function onChildProcessStart (context) {
35
- if (!context.callArgs) return
36
-
37
19
  const args = context.callArgs
38
- const { index, exists } = findOptionsIndex(args, context.shell)
20
+ if (!args) return
39
21
 
40
- if (exists) {
41
- args[index] = { ...args[index], env: injectSessionEnv(args[index].env) }
22
+ const index = Array.isArray(args[1]) || (!context.shell && !isOptionsObject(args[1])) ? 2 : 1
23
+ const options = isOptionsObject(args[index]) ? args[index] : undefined
24
+
25
+ if (options) {
26
+ args[index] = { ...options, env: getEnvWithRuntimeId(options.env) }
42
27
  return
43
28
  }
44
29
 
45
- const opts = { env: injectSessionEnv(null) }
46
-
47
- if (!context.shell && !Array.isArray(args[1])) {
30
+ if (index === 2 && !Array.isArray(args[1])) {
48
31
  args.splice(1, 0, [])
49
32
  }
50
33
 
34
+ const opts = { env: getEnvWithRuntimeId() }
51
35
  if (typeof args[index] === 'function') {
52
36
  args.splice(index, 0, opts)
53
37
  } else {
@@ -55,24 +39,15 @@ function onChildProcessStart (context) {
55
39
  }
56
40
  }
57
41
 
58
- const handler = { start: onChildProcessStart }
59
-
60
42
  function start (config) {
61
43
  if (!config.telemetry?.enabled || subscribed) return
62
44
  subscribed = true
63
45
 
64
- rootSessionId = config.rootSessionId
65
- runtimeId = config.tags['runtime-id']
66
-
67
- childProcessChannel.subscribe(handler)
68
- }
46
+ runtimeId = config.DD_ROOT_JS_SESSION_ID || config.tags['runtime-id']
69
47
 
70
- function stop () {
71
- if (!subscribed) return
72
- childProcessChannel.unsubscribe(handler)
73
- subscribed = false
74
- rootSessionId = undefined
75
- runtimeId = undefined
48
+ childProcessChannel.subscribe(
49
+ /** @type {import('diagnostics_channel').TracingChannelSubscribers<object>} */ ({ start: onChildProcessStart })
50
+ )
76
51
  }
77
52
 
78
- module.exports = { start, stop, _onChildProcessStart: onChildProcessStart }
53
+ module.exports = { start }