dd-trace 5.67.0 → 5.69.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 (132) hide show
  1. package/LICENSE-3rdparty.csv +6 -4
  2. package/README.md +0 -2
  3. package/ci/init.js +52 -54
  4. package/ext/exporters.d.ts +2 -1
  5. package/ext/exporters.js +2 -1
  6. package/index.d.ts +240 -3
  7. package/initialize.mjs +1 -1
  8. package/package.json +17 -11
  9. package/packages/datadog-core/src/storage.js +14 -13
  10. package/packages/datadog-esbuild/index.js +118 -26
  11. package/packages/datadog-instrumentations/src/aws-sdk.js +42 -4
  12. package/packages/datadog-instrumentations/src/azure-functions.js +1 -1
  13. package/packages/datadog-instrumentations/src/azure-service-bus.js +1 -1
  14. package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
  15. package/packages/datadog-instrumentations/src/connect.js +6 -2
  16. package/packages/datadog-instrumentations/src/cucumber.js +31 -6
  17. package/packages/datadog-instrumentations/src/express.js +5 -6
  18. package/packages/datadog-instrumentations/src/fastify.js +3 -3
  19. package/packages/datadog-instrumentations/src/helpers/hook.js +28 -15
  20. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  21. package/packages/datadog-instrumentations/src/helpers/instrument.js +15 -5
  22. package/packages/datadog-instrumentations/src/helpers/register.js +10 -3
  23. package/packages/datadog-instrumentations/src/http2/client.js +1 -0
  24. package/packages/datadog-instrumentations/src/http2/server.js +0 -1
  25. package/packages/datadog-instrumentations/src/ioredis.js +12 -1
  26. package/packages/datadog-instrumentations/src/jest.js +48 -36
  27. package/packages/datadog-instrumentations/src/limitd-client.js +2 -1
  28. package/packages/datadog-instrumentations/src/mocha/main.js +15 -7
  29. package/packages/datadog-instrumentations/src/mocha/utils.js +3 -0
  30. package/packages/datadog-instrumentations/src/mongoose.js +2 -1
  31. package/packages/datadog-instrumentations/src/oracledb.js +19 -13
  32. package/packages/datadog-instrumentations/src/pg.js +9 -5
  33. package/packages/datadog-instrumentations/src/pino.js +18 -6
  34. package/packages/datadog-instrumentations/src/playwright.js +15 -1
  35. package/packages/datadog-instrumentations/src/sequelize.js +1 -1
  36. package/packages/datadog-instrumentations/src/vitest.js +155 -62
  37. package/packages/datadog-plugin-ai/src/tracing.js +3 -3
  38. package/packages/datadog-plugin-aws-sdk/src/base.js +23 -8
  39. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +2 -2
  40. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +101 -2
  41. package/packages/datadog-plugin-aws-sdk/src/util.js +1 -1
  42. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/index.js +6 -0
  43. package/packages/datadog-plugin-cucumber/src/index.js +4 -56
  44. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +6 -3
  45. package/packages/datadog-plugin-cypress/src/support.js +4 -0
  46. package/packages/datadog-plugin-express/src/code_origin.js +2 -2
  47. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -2
  48. package/packages/datadog-plugin-jest/src/index.js +0 -21
  49. package/packages/datadog-plugin-mocha/src/index.js +3 -57
  50. package/packages/datadog-plugin-mongodb-core/src/index.js +38 -12
  51. package/packages/datadog-plugin-playwright/src/index.js +11 -5
  52. package/packages/datadog-plugin-vitest/src/index.js +5 -1
  53. package/packages/datadog-plugin-ws/src/close.js +1 -1
  54. package/packages/datadog-plugin-ws/src/producer.js +6 -1
  55. package/packages/datadog-plugin-ws/src/receiver.js +6 -1
  56. package/packages/dd-trace/src/aiguard/client.js +25 -0
  57. package/packages/dd-trace/src/aiguard/noop.js +9 -0
  58. package/packages/dd-trace/src/aiguard/sdk.js +173 -0
  59. package/packages/dd-trace/src/aiguard/tags.js +11 -0
  60. package/packages/dd-trace/src/appsec/iast/path-line.js +21 -4
  61. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +1 -1
  62. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +6 -3
  63. package/packages/dd-trace/src/appsec/stack_trace.js +20 -1
  64. package/packages/dd-trace/src/appsec/telemetry/waf.js +2 -2
  65. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -4
  66. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +11 -3
  67. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -1
  68. package/packages/dd-trace/src/config-helper.js +8 -1
  69. package/packages/dd-trace/src/config.js +92 -304
  70. package/packages/dd-trace/src/config_defaults.js +191 -0
  71. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -1
  72. package/packages/dd-trace/src/datastreams/fnv.js +2 -2
  73. package/packages/dd-trace/src/datastreams/index.js +23 -1
  74. package/packages/dd-trace/src/datastreams/writer.js +3 -2
  75. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
  76. package/packages/dd-trace/src/dogstatsd.js +4 -3
  77. package/packages/dd-trace/src/encode/0.4.js +1 -5
  78. package/packages/dd-trace/src/exporter.js +1 -0
  79. package/packages/dd-trace/src/exporters/agent/index.js +3 -2
  80. package/packages/dd-trace/src/exporters/agent/writer.js +1 -1
  81. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +3 -2
  82. package/packages/dd-trace/src/exporters/common/request.js +2 -1
  83. package/packages/dd-trace/src/exporters/span-stats/index.js +3 -2
  84. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  85. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +15 -4
  86. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +20 -7
  87. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +40 -13
  88. package/packages/dd-trace/src/llmobs/plugins/openai.js +7 -1
  89. package/packages/dd-trace/src/llmobs/tagger.js +8 -0
  90. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  91. package/packages/dd-trace/src/log/index.js +27 -16
  92. package/packages/dd-trace/src/log/log.js +29 -5
  93. package/packages/dd-trace/src/log/writer.js +5 -5
  94. package/packages/dd-trace/src/noop/proxy.js +4 -0
  95. package/packages/dd-trace/src/noop/span.js +1 -0
  96. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  97. package/packages/dd-trace/src/opentracing/span.js +19 -5
  98. package/packages/dd-trace/src/payload-tagging/config/index.js +16 -0
  99. package/packages/dd-trace/src/payload-tagging/index.js +26 -15
  100. package/packages/dd-trace/src/payload-tagging/tagging.js +17 -8
  101. package/packages/dd-trace/src/pkg.js +3 -1
  102. package/packages/dd-trace/src/plugin_manager.js +20 -2
  103. package/packages/dd-trace/src/plugins/ci_plugin.js +97 -3
  104. package/packages/dd-trace/src/plugins/composite.js +3 -0
  105. package/packages/dd-trace/src/plugins/index.js +2 -0
  106. package/packages/dd-trace/src/plugins/plugin.js +67 -0
  107. package/packages/dd-trace/src/plugins/util/git-cache.js +129 -0
  108. package/packages/dd-trace/src/plugins/util/git.js +41 -27
  109. package/packages/dd-trace/src/plugins/util/test.js +56 -27
  110. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  111. package/packages/dd-trace/src/priority_sampler.js +70 -46
  112. package/packages/dd-trace/src/profiler.js +4 -1
  113. package/packages/dd-trace/src/profiling/config.js +73 -42
  114. package/packages/dd-trace/src/profiling/profiler.js +3 -1
  115. package/packages/dd-trace/src/profiling/profilers/events.js +3 -8
  116. package/packages/dd-trace/src/profiling/profilers/space.js +1 -0
  117. package/packages/dd-trace/src/profiling/profilers/wall.js +196 -117
  118. package/packages/dd-trace/src/proxy.js +15 -0
  119. package/packages/dd-trace/src/rate_limiter.js +26 -1
  120. package/packages/dd-trace/src/remote_config/capabilities.js +5 -0
  121. package/packages/dd-trace/src/remote_config/manager.js +3 -2
  122. package/packages/dd-trace/src/sampling_rule.js +124 -2
  123. package/packages/dd-trace/src/span_sampler.js +19 -0
  124. package/packages/dd-trace/src/standalone/product.js +9 -0
  125. package/packages/dd-trace/src/standalone/tracesource.js +16 -1
  126. package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +13 -0
  127. package/packages/dd-trace/src/startup-log.js +21 -2
  128. package/packages/dd-trace/src/supported-configurations.json +9 -0
  129. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  130. package/packages/dd-trace/src/util.js +1 -1
  131. package/register.js +1 -1
  132. package/version.js +4 -2
@@ -49,6 +49,13 @@ const { SAMPLING_RULE_DECISION } = require('../../constants')
49
49
  const { AUTO_KEEP } = require('../../../../../ext/priority')
50
50
  const { version: ddTraceVersion } = require('../../../../../package.json')
51
51
 
52
+ /**
53
+ * JSDoc types for test environment metadata helpers.
54
+ *
55
+ * @typedef {{ service?: string, isServiceUserProvided?: boolean }} TestEnvironmentConfig
56
+ * @typedef {Record<string, string|number|undefined>} TestEnvironmentMetadata
57
+ */
58
+
52
59
  // session tags
53
60
  const TEST_SESSION_NAME = 'test_session.name'
54
61
 
@@ -119,6 +126,10 @@ const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
119
126
  // playwright worker variables
120
127
  const PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE = 90
121
128
 
129
+ // vitest worker variables
130
+ const VITEST_WORKER_TRACE_PAYLOAD_CODE = 100
131
+ const VITEST_WORKER_LOGS_PAYLOAD_CODE = 102
132
+
122
133
  // Early flake detection util strings
123
134
  const EFD_STRING = "Retried by Datadog's Early Flake Detection"
124
135
  const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + String.raw` \(#\d+\): `, 'g')
@@ -218,6 +229,8 @@ module.exports = {
218
229
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
219
230
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
220
231
  PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
232
+ VITEST_WORKER_TRACE_PAYLOAD_CODE,
233
+ VITEST_WORKER_LOGS_PAYLOAD_CODE,
221
234
  TEST_SOURCE_START,
222
235
  TEST_SKIPPED_BY_ITR,
223
236
  TEST_IS_NEW,
@@ -325,6 +338,10 @@ function validateUrl (url) {
325
338
  }
326
339
  }
327
340
 
341
+ /**
342
+ * @param {TestEnvironmentMetadata} metadata
343
+ * @returns {TestEnvironmentMetadata}
344
+ */
328
345
  function removeInvalidMetadata (metadata) {
329
346
  return Object.keys(metadata).reduce((filteredTags, tag) => {
330
347
  if (tag === GIT_REPOSITORY_URL && !validateGitRepositoryUrl(metadata[GIT_REPOSITORY_URL])) {
@@ -438,37 +455,49 @@ function checkShaDiscrepancies (ciMetadata, userProvidedGitMetadata) {
438
455
  )
439
456
  }
440
457
 
441
- function getTestEnvironmentMetadata (testFramework, config) {
442
- // TODO: eventually these will come from the tracer (generally available)
458
+ /**
459
+ * Build environment metadata for tests by merging CI, Git, runtime/OS and user-provided metadata.
460
+ *
461
+ * @param {string=} testFramework
462
+ * @param {TestEnvironmentConfig=} config
463
+ * @returns {TestEnvironmentMetadata}
464
+ */
465
+ function getTestEnvironmentMetadata (testFramework, config, shouldSkipGitMetadataExtraction = false) {
443
466
  const ciMetadata = getCIMetadata()
444
- const {
445
- [GIT_COMMIT_SHA]: commitSHA,
446
- [GIT_BRANCH]: branch,
447
- [GIT_REPOSITORY_URL]: repositoryUrl,
448
- [GIT_TAG]: tag,
449
- [GIT_COMMIT_AUTHOR_NAME]: authorName,
450
- [GIT_COMMIT_AUTHOR_EMAIL]: authorEmail,
451
- [GIT_COMMIT_MESSAGE]: commitMessage,
452
- [CI_WORKSPACE_PATH]: ciWorkspacePath,
453
- [GIT_COMMIT_HEAD_SHA]: headCommitSha
454
- } = ciMetadata
455
-
456
- const gitMetadata = getGitMetadata({
457
- commitSHA,
458
- branch,
459
- repositoryUrl,
460
- tag,
461
- authorName,
462
- authorEmail,
463
- commitMessage,
464
- ciWorkspacePath,
465
- headCommitSha
466
- })
467
-
468
467
  const userProvidedGitMetadata = getUserProviderGitMetadata()
469
468
 
470
- checkShaDiscrepancies(ciMetadata, userProvidedGitMetadata)
469
+ let gitMetadata = {}
470
+
471
+ // We don't execute git in test framework workers since the information is in the parent process
472
+ // and git metadata does not affect the execution of the tests
473
+ if (!shouldSkipGitMetadataExtraction) {
474
+ checkShaDiscrepancies(ciMetadata, userProvidedGitMetadata)
475
+
476
+ const {
477
+ [GIT_COMMIT_SHA]: commitSHA,
478
+ [GIT_BRANCH]: branch,
479
+ [GIT_REPOSITORY_URL]: repositoryUrl,
480
+ [GIT_TAG]: tag,
481
+ [GIT_COMMIT_AUTHOR_NAME]: authorName,
482
+ [GIT_COMMIT_AUTHOR_EMAIL]: authorEmail,
483
+ [GIT_COMMIT_MESSAGE]: commitMessage,
484
+ [CI_WORKSPACE_PATH]: ciWorkspacePath,
485
+ [GIT_COMMIT_HEAD_SHA]: headCommitSha
486
+ } = ciMetadata
487
+ gitMetadata = getGitMetadata({
488
+ commitSHA,
489
+ branch,
490
+ repositoryUrl,
491
+ tag,
492
+ authorName,
493
+ authorEmail,
494
+ commitMessage,
495
+ ciWorkspacePath,
496
+ headCommitSha
497
+ })
498
+ }
471
499
 
500
+ /** @type {TestEnvironmentMetadata} */
472
501
  const runtimeAndOSMetadata = getRuntimeAndOSMetadata()
473
502
 
474
503
  const metadata = {
@@ -387,7 +387,7 @@ const web = {
387
387
 
388
388
  return function (statusCode, statusMessage, headers) {
389
389
  headers = typeof statusMessage === 'string' ? headers : statusMessage
390
- headers = Object.assign(res.getHeaders(), headers)
390
+ headers = { ...res.getHeaders(), ...headers }
391
391
 
392
392
  if (req.method.toLowerCase() === 'options' && isOriginAllowed(req, headers)) {
393
393
  addAllowHeaders(req, res, headers)
@@ -46,8 +46,9 @@ const defaultSampler = new Sampler(AUTO_KEEP)
46
46
  * @class PrioritySampler
47
47
  * @typedef {import('./opentracing/span')} DatadogSpan
48
48
  * @typedef {import('./opentracing/span_context')} DatadogSpanContext
49
- * @typedef {import('./standalone/product')} PRODUCTS
49
+ * @typedef {{ id: number, mechanism?: number }} Product
50
50
  * @typedef {2|-1|1|0} SamplingPriority Empirically defined sampling priorities.
51
+ * @typedef {import('./sampling_rule')|Record<string, unknown>} SamplingRuleLike
51
52
  */
52
53
  class PrioritySampler {
53
54
  /**
@@ -55,12 +56,12 @@ class PrioritySampler {
55
56
  *
56
57
  * @typedef {Object} SamplingConfig
57
58
  * @property {number} [sampleRate] - The default sample rate for traces.
58
- * @property {string} [provenance] - The provenance of the sampling rule (e.g., "customer", "dynamic").
59
+ * @property {string} [provenance] - Optional rule provenance ("customer" or "dynamic").
59
60
  * @property {number} [rateLimit=100] - The maximum number of traces to sample per second.
60
- * @property {Array<SamplingRule>} [rules=[]] - An array of sampling rules to apply.
61
+ * @property {Array<import('./sampling_rule')>|Array<Record<string, unknown>>} [rules=[]] - Sampling rules or configs.
61
62
  *
62
63
  * @param {string} env - The environment name (e.g., "production", "staging").
63
- * @param {SamplingConfig} config - The configuration object for sampling.
64
+ * @param {SamplingConfig} [config] - The configuration object for sampling.
64
65
  */
65
66
  constructor (env, config) {
66
67
  this.configure(env, config)
@@ -69,22 +70,22 @@ class PrioritySampler {
69
70
 
70
71
  /**
71
72
  *
72
- * @param env {string}
73
- * @param opts {SamplingConfig}
73
+ * @param {string} env
74
+ * @param {SamplingConfig} config
74
75
  */
75
- configure (env, opts = {}) {
76
- const { sampleRate, provenance, rateLimit = 100, rules } = opts
76
+ configure (env, config = {}) {
77
+ const { sampleRate, provenance, rateLimit = 100, rules } = config
77
78
  this._env = env
78
79
  this._rules = this.#normalizeRules(rules || [], sampleRate, rateLimit, provenance)
79
80
  this._limiter = new RateLimiter(rateLimit)
80
81
 
81
- log.trace(env, opts)
82
+ log.trace(env, config)
82
83
  setSamplingRules(this._rules)
83
84
  }
84
85
 
85
86
  /**
86
- * @param span {DatadogSpan}
87
- * @returns {boolean}
87
+ * @param {DatadogSpan} span
88
+ * @returns {boolean} True if the trace should be sampled based on priority.
88
89
  */
89
90
  isSampled (span) {
90
91
  const priority = this._getPriorityFromAuto(span)
@@ -93,9 +94,10 @@ class PrioritySampler {
93
94
  }
94
95
 
95
96
  /**
97
+ * Assigns a sampling priority to a span if not already set.
96
98
  *
97
- * @param span {DatadogSpan}
98
- * @param auto {boolean}
99
+ * @param {DatadogSpan} span
100
+ * @param {boolean} [auto=true] - Whether to use automatic sampling if no manual tags are present.
99
101
  * @returns {void}
100
102
  */
101
103
  sample (span, auto = true) {
@@ -125,8 +127,9 @@ class PrioritySampler {
125
127
  }
126
128
 
127
129
  /**
130
+ * Updates agent-provided sampling rates keyed by `service:,env:`.
128
131
  *
129
- * @param rates {Record<string, number>}
132
+ * @param {Record<string, number>} rates
130
133
  * @returns {void}
131
134
  */
132
135
  update (rates) {
@@ -145,8 +148,9 @@ class PrioritySampler {
145
148
  }
146
149
 
147
150
  /**
151
+ * Validates that a sampling priority value is one of the allowed constants.
148
152
  *
149
- * @param samplingPriority {SamplingPriority}
153
+ * @param {SamplingPriority|undefined} samplingPriority
150
154
  * @returns {boolean}
151
155
  */
152
156
  validate (samplingPriority) {
@@ -162,10 +166,11 @@ class PrioritySampler {
162
166
  }
163
167
 
164
168
  /**
169
+ * Explicitly sets the priority and mechanism for the span's trace.
165
170
  *
166
- * @param span {DatadogSpan}
167
- * @param samplingPriority {SamplingPriority}
168
- * @param product {import('./standalone/product')}
171
+ * @param {DatadogSpan} span
172
+ * @param {SamplingPriority} samplingPriority
173
+ * @param {Product} [product]
169
174
  */
170
175
  setPriority (span, samplingPriority, product) {
171
176
  if (!span || !this.validate(samplingPriority)) return
@@ -189,17 +194,21 @@ class PrioritySampler {
189
194
  }
190
195
 
191
196
  /**
197
+ * Returns the span context, accepting either a span or a span context.
192
198
  *
193
- * @param span {DatadogSpan}
199
+ * @param {DatadogSpan|DatadogSpanContext} span
194
200
  * @returns {DatadogSpanContext}
195
201
  */
196
202
  _getContext (span) {
197
- return typeof span.context === 'function' ? span.context() : span
203
+ return typeof /** @type {DatadogSpan} */ (span).context === 'function'
204
+ ? /** @type {DatadogSpan} */ (span).context()
205
+ : /** @type {DatadogSpanContext} */ (span)
198
206
  }
199
207
 
200
208
  /**
209
+ * Computes priority using rules and agent rates when no manual tag is present.
201
210
  *
202
- * @param span {DatadogSpan}
211
+ * @param {DatadogSpan} span
203
212
  * @returns {SamplingPriority}
204
213
  */
205
214
  _getPriorityFromAuto (span) {
@@ -212,11 +221,12 @@ class PrioritySampler {
212
221
  }
213
222
 
214
223
  /**
215
- *
216
- * @param tags {Record<string, symbol | unknown>}
224
+ * Computes priority from manual sampling tags if present.
217
225
  * Included for compatibility with {@link import('./standalone/tracesource_priority_sampler')._getPriorityFromTags}
218
- * @param _context {DatadogSpanContext}
219
- * @returns {SamplingPriority}
226
+ *
227
+ * @param {Record<string, unknown>} tags
228
+ * @param {DatadogSpanContext} _context
229
+ * @returns {SamplingPriority|undefined}
220
230
  */
221
231
  _getPriorityFromTags (tags, _context) {
222
232
  if (Object.hasOwn(tags, MANUAL_KEEP) && tags[MANUAL_KEEP] !== false) {
@@ -224,19 +234,23 @@ class PrioritySampler {
224
234
  } else if (Object.hasOwn(tags, MANUAL_DROP) && tags[MANUAL_DROP] !== false) {
225
235
  return USER_REJECT
226
236
  }
227
- const priority = Number.parseInt(tags[SAMPLING_PRIORITY], 10)
228
-
229
- if (priority === 1 || priority === 2) {
230
- return USER_KEEP
231
- } else if (priority === 0 || priority === -1) {
232
- return USER_REJECT
237
+ const rawPriority = tags[SAMPLING_PRIORITY]
238
+ if (rawPriority !== undefined) {
239
+ const priority = Number.parseInt(String(rawPriority), 10)
240
+
241
+ if (priority === 1 || priority === 2) {
242
+ return USER_KEEP
243
+ } else if (priority === 0 || priority === -1) {
244
+ return USER_REJECT
245
+ }
233
246
  }
234
247
  }
235
248
 
236
249
  /**
250
+ * Applies a matching rule and rate limit to compute the sampling priority.
237
251
  *
238
- * @param context {DatadogSpanContext}
239
- * @param rule {SamplingRule}
252
+ * @param {DatadogSpanContext} context
253
+ * @param {import('./sampling_rule')} rule
240
254
  * @returns {SamplingPriority}
241
255
  */
242
256
  #getPriorityByRule (context, rule) {
@@ -251,12 +265,14 @@ class PrioritySampler {
251
265
  }
252
266
 
253
267
  /**
268
+ * Checks if the rate limiter allows sampling for the current window and
269
+ * records the effective rate on the trace.
254
270
  *
255
- * @param context {DatadogSpanContext}
271
+ * @param {DatadogSpanContext} context
256
272
  * @returns {boolean}
257
- * @private
258
273
  */
259
274
  _isSampledByRateLimit (context) {
275
+ // TODO: Change underscored properties to private ones.
260
276
  const allowed = this._limiter.isAllowed()
261
277
 
262
278
  context._trace[SAMPLING_LIMIT_DECISION] = this._limiter.effectiveRate()
@@ -265,12 +281,14 @@ class PrioritySampler {
265
281
  }
266
282
 
267
283
  /**
284
+ * Computes priority using agent-provided sampling rates.
268
285
  *
269
- * @param context {DatadogSpanContext}
286
+ * @param {DatadogSpanContext} context
270
287
  * @returns {SamplingPriority}
271
288
  */
272
289
  #getPriorityByAgent (context) {
273
290
  const key = `service:${context._tags[SERVICE_NAME]},env:${this._env}`
291
+ // TODO: Change underscored properties to private ones.
274
292
  const sampler = this._samplers[key] || this._samplers[DEFAULT_KEY]
275
293
 
276
294
  context._trace[SAMPLING_AGENT_DECISION] = sampler.rate()
@@ -281,8 +299,9 @@ class PrioritySampler {
281
299
  }
282
300
 
283
301
  /**
302
+ * Tags the trace with a decision maker when priority is keep, or removes it otherwise.
284
303
  *
285
- * @param span {DatadogSpan}
304
+ * @param {DatadogSpan} span
286
305
  * @returns {void}
287
306
  */
288
307
  #addDecisionMaker (span) {
@@ -301,11 +320,13 @@ class PrioritySampler {
301
320
  }
302
321
 
303
322
  /**
304
- * @param {Record<string, unknown>[] | Record<string, unknown>} rules - The sampling rules to normalize.
305
- * @param {number} sampleRate
323
+ * Normalizes rule inputs to SamplingRule instances, applying defaults.
324
+ *
325
+ * @param {Array<SamplingRuleLike>|SamplingRuleLike} rules - Rules to normalize.
326
+ * @param {number|undefined} sampleRate
306
327
  * @param {number} rateLimit
307
- * @param {string} provenance
308
- * @returns {SamplingRule[]}
328
+ * @param {string|undefined} provenance
329
+ * @returns {Array<import('./sampling_rule')>}
309
330
  */
310
331
  #normalizeRules (rules, sampleRate, rateLimit, provenance) {
311
332
  rules = Array.isArray(rules) ? rules.flat() : [rules]
@@ -314,7 +335,7 @@ class PrioritySampler {
314
335
 
315
336
  const result = []
316
337
  for (const rule of rules) {
317
- const sampleRate = Number.parseFloat(rule.sampleRate)
338
+ const sampleRate = Number.parseFloat(String(rule.sampleRate))
318
339
  // TODO(BridgeAR): Debug logging invalid rules fails our tests.
319
340
  // Should we definitely not know about these?
320
341
  if (!Number.isNaN(sampleRate)) {
@@ -325,11 +346,13 @@ class PrioritySampler {
325
346
  }
326
347
 
327
348
  /**
349
+ * Finds the first matching rule for the given span.
328
350
  *
329
- * @param span {DatadogSpan}
330
- * @returns {SamplingRule|undefined}
351
+ * @param {DatadogSpan} span
352
+ * @returns {import('./sampling_rule')|undefined}
331
353
  */
332
354
  #findRule (span) {
355
+ // TODO: Change underscored properties to private ones.
333
356
  for (const rule of this._rules) {
334
357
  // Rule is a special object with a .match() property.
335
358
  // It has nothing to do with a regular expression.
@@ -339,9 +362,10 @@ class PrioritySampler {
339
362
  }
340
363
 
341
364
  /**
365
+ * Convenience helper to keep a trace with an optional product mechanism.
342
366
  *
343
- * @param span {DatadogSpan}
344
- * @param product {import('./standalone/product')}
367
+ * @param {DatadogSpan} span
368
+ * @param {Product} [product]
345
369
  */
346
370
  static keepTrace (span, product) {
347
371
  span?._prioritySampler?.setPriority(span, USER_KEEP, product)
@@ -10,6 +10,8 @@ module.exports = {
10
10
  start: config => {
11
11
  const { service, version, env, url, hostname, port, tags, repositoryUrl, commitSHA, injectionEnabled } = config
12
12
  const { enabled, sourceMap, exporters } = config.profiling
13
+ const { heartbeatInterval } = config.telemetry
14
+
13
15
  const logger = {
14
16
  debug: (message) => log.debug(message),
15
17
  info: (message) => log.info(message),
@@ -39,7 +41,8 @@ module.exports = {
39
41
  repositoryUrl,
40
42
  commitSHA,
41
43
  libraryInjected,
42
- activation
44
+ activation,
45
+ heartbeatInterval
43
46
  })
44
47
  },
45
48
 
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const coalesce = require('koalas')
4
3
  const os = require('os')
5
4
  const path = require('path')
6
5
  const { URL, format, pathToFileURL } = require('url')
6
+ const satisfies = require('semifies')
7
7
  const { AgentExporter } = require('./exporters/agent')
8
8
  const { FileExporter } = require('./exporters/file')
9
9
  const { ConsoleLogger } = require('./loggers/console')
@@ -16,6 +16,7 @@ const { tagger } = require('./tagger')
16
16
  const { isFalse, isTrue } = require('../util')
17
17
  const { getAzureTagsFromMetadata, getAzureAppMetadata } = require('../azure_metadata')
18
18
  const { getEnvironmentVariables } = require('../config-helper')
19
+ const defaults = require('../config_defaults')
19
20
 
20
21
  class Config {
21
22
  constructor (options = {}) {
@@ -41,27 +42,26 @@ class Config {
41
42
  DD_PROFILING_TIMELINE_ENABLED,
42
43
  DD_PROFILING_UPLOAD_PERIOD,
43
44
  DD_PROFILING_UPLOAD_TIMEOUT,
45
+ DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED,
44
46
  DD_PROFILING_V8_PROFILER_BUG_WORKAROUND,
45
47
  DD_PROFILING_WALLTIME_ENABLED,
46
48
  DD_SERVICE,
47
49
  DD_TAGS,
48
50
  DD_TRACE_AGENT_PORT,
49
51
  DD_TRACE_AGENT_URL,
50
- DD_VERSION
52
+ DD_VERSION,
53
+ NODE_OPTIONS
51
54
  } = getEnvironmentVariables()
52
55
 
53
- const env = coalesce(options.env, DD_ENV)
56
+ const env = options.env ?? DD_ENV
54
57
  const service = options.service || DD_SERVICE || 'node'
55
58
  const host = os.hostname()
56
- const version = coalesce(options.version, DD_VERSION)
59
+ const version = options.version ?? DD_VERSION
57
60
  // Must be longer than one minute so pad with five seconds
58
- const flushInterval = coalesce(options.interval, Number(DD_PROFILING_UPLOAD_PERIOD) * 1000, 65 * 1000)
59
- const uploadTimeout = coalesce(options.uploadTimeout,
60
- Number(DD_PROFILING_UPLOAD_TIMEOUT), 60 * 1000)
61
- const sourceMap = coalesce(options.sourceMap,
62
- DD_PROFILING_SOURCE_MAP, true)
63
- const pprofPrefix = coalesce(options.pprofPrefix,
64
- DD_PROFILING_PPROF_PREFIX, '')
61
+ const flushInterval = options.interval ?? (Number(DD_PROFILING_UPLOAD_PERIOD) * 1000 || 65 * 1000)
62
+ const uploadTimeout = options.uploadTimeout ?? (Number(DD_PROFILING_UPLOAD_TIMEOUT) || 60 * 1000)
63
+ const sourceMap = options.sourceMap ?? DD_PROFILING_SOURCE_MAP ?? true
64
+ const pprofPrefix = options.pprofPrefix ?? DD_PROFILING_PPROF_PREFIX ?? ''
65
65
 
66
66
  this.service = service
67
67
  this.env = env
@@ -104,21 +104,21 @@ class Config {
104
104
  this.flushInterval = flushInterval
105
105
  this.uploadTimeout = uploadTimeout
106
106
  this.sourceMap = sourceMap
107
- this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
108
- this.endpointCollectionEnabled = isTrue(coalesce(options.endpointCollection,
109
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED, samplingContextsAvailable))
107
+ this.debugSourceMaps = isTrue(options.debugSourceMaps ?? DD_PROFILING_DEBUG_SOURCE_MAPS)
108
+ this.endpointCollectionEnabled = isTrue(options.endpointCollection ??
109
+ DD_PROFILING_ENDPOINT_COLLECTION_ENABLED ?? samplingContextsAvailable)
110
110
  checkOptionWithSamplingContextAllowed(this.endpointCollectionEnabled, 'Endpoint collection')
111
111
 
112
112
  this.pprofPrefix = pprofPrefix
113
- this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
114
- DD_PROFILING_V8_PROFILER_BUG_WORKAROUND, true))
115
- const hostname = coalesce(options.hostname, DD_AGENT_HOST) || 'localhost'
116
- const port = coalesce(options.port, DD_TRACE_AGENT_PORT) || 8126
117
- this.url = new URL(coalesce(options.url, DD_TRACE_AGENT_URL, format({
113
+ this.v8ProfilerBugWorkaroundEnabled = isTrue(options.v8ProfilerBugWorkaround ??
114
+ DD_PROFILING_V8_PROFILER_BUG_WORKAROUND ?? true)
115
+ const hostname = (options.hostname ?? DD_AGENT_HOST) || defaults.hostname
116
+ const port = (options.port ?? DD_TRACE_AGENT_PORT) || defaults.port
117
+ this.url = new URL(options.url ?? DD_TRACE_AGENT_URL ?? format({
118
118
  protocol: 'http:',
119
119
  hostname,
120
120
  port
121
- })))
121
+ }))
122
122
 
123
123
  this.libraryInjected = options.libraryInjected
124
124
  this.activation = options.activation
@@ -129,17 +129,17 @@ class Config {
129
129
  // OOM monitoring does not work well on Windows, so it is disabled by default.
130
130
  const oomMonitoringSupported = process.platform !== 'win32'
131
131
 
132
- const oomMonitoringEnabled = isTrue(coalesce(options.oomMonitoring,
133
- DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED, oomMonitoringSupported))
132
+ const oomMonitoringEnabled = isTrue(options.oomMonitoring ??
133
+ DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED ?? oomMonitoringSupported)
134
134
  checkOptionAllowed(oomMonitoringEnabled, 'OOM monitoring', oomMonitoringSupported)
135
135
 
136
- const heapLimitExtensionSize = coalesce(options.oomHeapLimitExtensionSize,
137
- Number(DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE), 0)
138
- const maxHeapExtensionCount = coalesce(options.oomMaxHeapExtensionCount,
139
- Number(DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT), 0)
136
+ const heapLimitExtensionSize = options.oomHeapLimitExtensionSize ??
137
+ (Number(DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE) || 0)
138
+ const maxHeapExtensionCount = options.oomMaxHeapExtensionCount ??
139
+ (Number(DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT) || 0)
140
140
  const exportStrategies = oomMonitoringEnabled
141
- ? ensureOOMExportStrategies(coalesce(options.oomExportStrategies, DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES,
142
- [oomExportStrategies.PROCESS]), this)
141
+ ? ensureOOMExportStrategies(options.oomExportStrategies ?? DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES ??
142
+ [oomExportStrategies.PROCESS], this)
143
143
  : []
144
144
  const exportCommand = oomMonitoringEnabled ? buildExportCommand(this) : undefined
145
145
  this.oomMonitoring = {
@@ -156,26 +156,29 @@ class Config {
156
156
  DD_PROFILING_PROFILERS
157
157
  })
158
158
 
159
- this.timelineEnabled = isTrue(coalesce(options.timelineEnabled,
160
- DD_PROFILING_TIMELINE_ENABLED, samplingContextsAvailable))
159
+ this.timelineEnabled = isTrue(
160
+ options.timelineEnabled ?? DD_PROFILING_TIMELINE_ENABLED ?? samplingContextsAvailable
161
+ )
161
162
  checkOptionWithSamplingContextAllowed(this.timelineEnabled, 'Timeline view')
162
- this.timelineSamplingEnabled = isTrue(coalesce(options.timelineSamplingEnabled,
163
- DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, true))
163
+ this.timelineSamplingEnabled = isTrue(
164
+ options.timelineSamplingEnabled ?? DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED ?? true
165
+ )
164
166
 
165
- this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
166
- DD_PROFILING_CODEHOTSPOTS_ENABLED, samplingContextsAvailable))
167
+ this.codeHotspotsEnabled = isTrue(
168
+ options.codeHotspotsEnabled ?? DD_PROFILING_CODEHOTSPOTS_ENABLED ?? samplingContextsAvailable
169
+ )
167
170
  checkOptionWithSamplingContextAllowed(this.codeHotspotsEnabled, 'Code hotspots')
168
171
 
169
- this.cpuProfilingEnabled = isTrue(coalesce(options.cpuProfilingEnabled,
170
- DD_PROFILING_CPU_ENABLED,
171
- samplingContextsAvailable))
172
+ this.cpuProfilingEnabled = isTrue(
173
+ options.cpuProfilingEnabled ?? DD_PROFILING_CPU_ENABLED ?? samplingContextsAvailable
174
+ )
172
175
  checkOptionWithSamplingContextAllowed(this.cpuProfilingEnabled, 'CPU profiling')
173
176
 
174
- this.samplingInterval = coalesce(options.samplingInterval, 1e3 / 99) // 99hz in millis
177
+ this.samplingInterval = options.samplingInterval || 1e3 / 99 // 99hz in millis
175
178
 
176
- this.heapSamplingInterval = coalesce(options.heapSamplingInterval,
177
- Number(DD_PROFILING_HEAP_SAMPLING_INTERVAL))
178
- const uploadCompression0 = coalesce(options.uploadCompression, DD_PROFILING_DEBUG_UPLOAD_COMPRESSION, 'on')
179
+ this.heapSamplingInterval = options.heapSamplingInterval ??
180
+ (Number(DD_PROFILING_HEAP_SAMPLING_INTERVAL) || 512 * 1024)
181
+ const uploadCompression0 = options.uploadCompression ?? DD_PROFILING_DEBUG_UPLOAD_COMPRESSION ?? 'on'
179
182
  let [uploadCompression, level0] = uploadCompression0.split('-')
180
183
  if (!['on', 'off', 'gzip', 'zstd'].includes(uploadCompression)) {
181
184
  this.logger.warn(`Invalid profile upload compression method "${uploadCompression0}". Will use "on".`)
@@ -209,6 +212,34 @@ class Config {
209
212
 
210
213
  this.uploadCompression = { method: uploadCompression, level }
211
214
 
215
+ const that = this
216
+ function turnOffAsyncContextFrame (msg) {
217
+ that.logger.warn(
218
+ `DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED was set ${msg}, it will have no effect.`)
219
+ that.asyncContextFrameEnabled = false
220
+ }
221
+
222
+ const hasExecArg = (arg) => process.execArgv.includes(arg) || String(NODE_OPTIONS).includes(arg)
223
+
224
+ this.asyncContextFrameEnabled = isTrue(options.useAsyncContextFrame ?? DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED)
225
+ if (this.asyncContextFrameEnabled) {
226
+ if (satisfies(process.versions.node, '>=24.0.0')) {
227
+ if (hasExecArg('--no-async-context-frame')) {
228
+ turnOffAsyncContextFrame('with --no-async-context-frame')
229
+ }
230
+ } else if (satisfies(process.versions.node, '>=23.0.0')) {
231
+ if (!hasExecArg('--experimental-async-context-frame')) {
232
+ turnOffAsyncContextFrame('without --experimental-async-context-frame')
233
+ }
234
+ } else {
235
+ // NOTE: technically, this should work starting with 22.7.0 which is when
236
+ // AsyncContextFrame debuted, but it would require a change in pprof-nodejs too.
237
+ turnOffAsyncContextFrame('but it requires at least Node.js 23')
238
+ }
239
+ }
240
+
241
+ this.heartbeatInterval = options.heartbeatInterval || 60 * 1000 // 1 minute
242
+
212
243
  this.profilers = ensureProfilers(profilers, this)
213
244
  }
214
245
  }
@@ -220,7 +251,7 @@ function getProfilers ({
220
251
  }) {
221
252
  // First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to wall + space
222
253
  // Use a Set to avoid duplicates
223
- const profilers = new Set(coalesce(DD_PROFILING_PROFILERS, 'wall,space').split(','))
254
+ const profilers = new Set((DD_PROFILING_PROFILERS ?? 'wall,space').split(','))
224
255
 
225
256
  // Add/remove wall depending on the value of DD_PROFILING_WALLTIME_ENABLED
226
257
  if (DD_PROFILING_WALLTIME_ENABLED != null) {
@@ -110,11 +110,13 @@ class Profiler extends EventEmitter {
110
110
  }
111
111
  break
112
112
  case 'zstd':
113
- if (typeof zlib.zstdCompress === 'function') {
113
+ if (typeof zlib.zstdCompress === 'function') { // eslint-disable-line n/no-unsupported-features/node-builtins
114
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
114
115
  this.#compressionFn = promisify(zlib.zstdCompress)
115
116
  if (clevel !== undefined) {
116
117
  this.#compressionOptions = {
117
118
  params: {
119
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
118
120
  [zlib.constants.ZSTD_c_compressionLevel]: clevel
119
121
  }
120
122
  }
@@ -41,14 +41,9 @@ function labelFromStrStr (stringTable, keyStr, valStr) {
41
41
  return labelFromStr(stringTable, stringTable.dedup(keyStr), valStr)
42
42
  }
43
43
 
44
- function getSamplingIntervalMillis (options) {
45
- return (options.samplingInterval || 1e3 / 99) // 99Hz
46
- }
47
-
48
44
  function getMaxSamples (options) {
49
- const cpuSamplingInterval = getSamplingIntervalMillis(options)
50
- const flushInterval = options.flushInterval || 65 * 1e3 // 60 seconds
51
- const maxCpuSamples = flushInterval / cpuSamplingInterval
45
+ const flushInterval = options.flushInterval || 65 * 1e3 // 65 seconds
46
+ const maxCpuSamples = flushInterval / options.samplingInterval
52
47
 
53
48
  // The lesser of max parallelism and libuv thread pool size, plus one so we can detect
54
49
  // oversubscription on libuv thread pool, plus another one for GC.
@@ -399,7 +394,7 @@ class EventsProfiler {
399
394
 
400
395
  const eventHandler = event => this.#eventSerializer.addEvent(event)
401
396
  const eventFilter = options.timelineSamplingEnabled
402
- ? createPoissonProcessSamplingFilter(getSamplingIntervalMillis(options))
397
+ ? createPoissonProcessSamplingFilter(options.samplingInterval)
403
398
  : () => true
404
399
  const filteringEventHandler = event => {
405
400
  if (eventFilter(event)) {