dd-trace 5.105.0 → 5.107.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 (108) hide show
  1. package/index.d.ts +20 -1
  2. package/package.json +5 -7
  3. package/packages/datadog-core/src/storage.js +47 -48
  4. package/packages/datadog-esbuild/index.js +6 -1
  5. package/packages/datadog-instrumentations/src/ai.js +12 -3
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +3 -2
  7. package/packages/datadog-instrumentations/src/body-parser.js +5 -2
  8. package/packages/datadog-instrumentations/src/connect.js +3 -2
  9. package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
  10. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +19 -0
  11. package/packages/datadog-instrumentations/src/cucumber.js +319 -152
  12. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
  13. package/packages/datadog-instrumentations/src/express-session.js +12 -11
  14. package/packages/datadog-instrumentations/src/express.js +24 -20
  15. package/packages/datadog-instrumentations/src/fastify.js +18 -6
  16. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
  17. package/packages/datadog-instrumentations/src/http/client.js +9 -12
  18. package/packages/datadog-instrumentations/src/http/server.js +30 -16
  19. package/packages/datadog-instrumentations/src/http2/client.js +15 -12
  20. package/packages/datadog-instrumentations/src/http2/server.js +15 -8
  21. package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
  22. package/packages/datadog-instrumentations/src/jest.js +143 -73
  23. package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
  25. package/packages/datadog-instrumentations/src/multer.js +3 -2
  26. package/packages/datadog-instrumentations/src/mysql2.js +34 -0
  27. package/packages/datadog-instrumentations/src/net.js +8 -6
  28. package/packages/datadog-instrumentations/src/openai.js +19 -7
  29. package/packages/datadog-instrumentations/src/pg.js +19 -0
  30. package/packages/datadog-instrumentations/src/router.js +12 -10
  31. package/packages/datadog-instrumentations/src/vitest.js +29 -4
  32. package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
  33. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
  34. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
  35. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
  36. package/packages/datadog-plugin-cucumber/src/index.js +2 -0
  37. package/packages/datadog-plugin-cypress/src/support.js +31 -1
  38. package/packages/datadog-plugin-http/src/client.js +0 -3
  39. package/packages/datadog-plugin-http/src/server.js +11 -1
  40. package/packages/datadog-plugin-mocha/src/index.js +2 -0
  41. package/packages/datadog-plugin-pg/src/index.js +10 -0
  42. package/packages/dd-trace/src/aiguard/index.js +34 -15
  43. package/packages/dd-trace/src/aiguard/sdk.js +34 -3
  44. package/packages/dd-trace/src/aiguard/tags.js +6 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  46. package/packages/dd-trace/src/config/defaults.js +14 -0
  47. package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
  48. package/packages/dd-trace/src/config/helper.js +1 -0
  49. package/packages/dd-trace/src/config/index.js +5 -9
  50. package/packages/dd-trace/src/config/parsers.js +8 -0
  51. package/packages/dd-trace/src/config/supported-configurations.json +13 -6
  52. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  53. package/packages/dd-trace/src/datastreams/writer.js +1 -2
  54. package/packages/dd-trace/src/debugger/config.js +1 -1
  55. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
  56. package/packages/dd-trace/src/debugger/index.js +1 -2
  57. package/packages/dd-trace/src/dogstatsd.js +2 -3
  58. package/packages/dd-trace/src/encode/0.4.js +49 -41
  59. package/packages/dd-trace/src/encode/agentless-json.js +5 -1
  60. package/packages/dd-trace/src/encode/tags-processors.js +14 -0
  61. package/packages/dd-trace/src/exporters/agent/index.js +1 -2
  62. package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
  63. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
  64. package/packages/dd-trace/src/exporters/common/request.js +26 -0
  65. package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
  66. package/packages/dd-trace/src/id.js +15 -0
  67. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +91 -5
  68. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
  69. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
  70. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
  71. package/packages/dd-trace/src/llmobs/sdk.js +4 -1
  72. package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
  73. package/packages/dd-trace/src/llmobs/tagger.js +5 -3
  74. package/packages/dd-trace/src/llmobs/util.js +54 -0
  75. package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
  76. package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
  77. package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
  78. package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
  79. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
  80. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
  81. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
  82. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +25 -5
  83. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -10
  84. package/packages/dd-trace/src/opentracing/span.js +23 -18
  85. package/packages/dd-trace/src/opentracing/span_context.js +1 -3
  86. package/packages/dd-trace/src/opentracing/tracer.js +16 -12
  87. package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
  88. package/packages/dd-trace/src/priority_sampler.js +6 -5
  89. package/packages/dd-trace/src/profiling/config.js +11 -25
  90. package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
  91. package/packages/dd-trace/src/profiling/profiler.js +19 -9
  92. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -3
  93. package/packages/dd-trace/src/proxy.js +13 -10
  94. package/packages/dd-trace/src/remote_config/index.js +1 -2
  95. package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
  96. package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
  97. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
  98. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
  99. package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
  100. package/packages/dd-trace/src/span_format.js +33 -25
  101. package/packages/dd-trace/src/span_stats.js +1 -1
  102. package/packages/dd-trace/src/startup-log.js +1 -2
  103. package/packages/dd-trace/src/telemetry/send-data.js +1 -1
  104. package/packages/dd-trace/src/tracer.js +1 -1
  105. package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
  106. package/vendor/dist/shell-quote/index.js +1 -1
  107. package/packages/dd-trace/src/agent/url.js +0 -28
  108. package/scripts/preinstall.js +0 -34
@@ -284,7 +284,7 @@ class TextMapPropagator {
284
284
  (DD_MAJOR < 6 && this._hasPropagationStyle('inject', 'b3'))
285
285
  if (!hasB3multi) return
286
286
 
287
- carrier[b3TraceKey] = this._getB3TraceId(spanContext)
287
+ carrier[b3TraceKey] = spanContext._traceId.toTraceIdHex(spanContext._trace.tags['_dd.p.tid'])
288
288
  carrier[b3SpanKey] = spanContext._spanId.toString(16)
289
289
  carrier[b3SampledKey] = spanContext._sampling.priority >= AUTO_KEEP ? '1' : '0'
290
290
 
@@ -303,7 +303,7 @@ class TextMapPropagator {
303
303
  (DD_MAJOR >= 6 && this._hasPropagationStyle('inject', 'b3'))
304
304
  if (!hasB3SingleHeader) return
305
305
 
306
- const traceId = this._getB3TraceId(spanContext)
306
+ const traceId = spanContext._traceId.toTraceIdHex(spanContext._trace.tags['_dd.p.tid'])
307
307
  const spanId = spanContext._spanId.toString(16)
308
308
  const sampled = spanContext._sampling.priority >= AUTO_KEEP ? '1' : '0'
309
309
 
@@ -859,14 +859,6 @@ class TextMapPropagator {
859
859
  }
860
860
  }
861
861
 
862
- _getB3TraceId (spanContext) {
863
- if (spanContext._traceId.toBuffer().length <= 8 && spanContext._trace.tags['_dd.p.tid']) {
864
- return spanContext._trace.tags['_dd.p.tid'] + spanContext._traceId.toString(16)
865
- }
866
-
867
- return spanContext._traceId.toString(16)
868
- }
869
-
870
862
  /**
871
863
  * @param {number} traceparentSampled
872
864
  * @param {number|undefined} tracestateSamplingPriority
@@ -38,20 +38,26 @@ const tagsUpdateCh = channel('dd-trace:span:tags:update')
38
38
  // Module-scope so we don't allocate a fresh recursive closure on every
39
39
  // `addLink` / `addEvent`.
40
40
  /**
41
- * @param {Record<string, string>} out
41
+ * @param {Record<string, string> | undefined} out Accumulator, created lazily
42
+ * on the first surviving entry so an all-dropped set stays `undefined`.
42
43
  * @param {string} key
43
44
  * @param {unknown} value
45
+ * @returns {Record<string, string> | undefined}
44
46
  */
45
47
  function addArrayOrScalarAttribute (out, key, value) {
46
48
  if (Array.isArray(value)) {
47
49
  for (let i = 0; i < value.length; i++) {
48
- addArrayOrScalarAttribute(out, `${key}.${i}`, value[i])
50
+ out = addArrayOrScalarAttribute(out, `${key}.${i}`, value[i])
49
51
  }
50
- } else if (ALLOWED.has(typeof value)) {
52
+ return out
53
+ }
54
+ if (ALLOWED.has(typeof value)) {
55
+ out ??= {}
51
56
  out[key] = typeof value === 'string' ? value : String(value)
52
- } else {
53
- log.warn('Dropping span link attribute. It is not of an allowed type')
57
+ return out
54
58
  }
59
+ log.warn('Dropping span link attribute. It is not of an allowed type')
60
+ return out
55
61
  }
56
62
 
57
63
  function getIntegrationCounter (event, integration) {
@@ -79,12 +85,6 @@ class DatadogSpan {
79
85
 
80
86
  const operationName = fields.operationName
81
87
  const parent = fields.parent || null
82
- // Stay on `Object.assign({}, src)` for backportability: V8 12+ (Node 22 /
83
- // 24) inlines `{ ...src }` and beats `Object.assign` here, but on V8 10.2
84
- // / 11.3 (Node 18 / 20) the spread takes a generic runtime path and slows
85
- // `spans-finish-*` by ~140%. Revisit once those LTS lines drop.
86
- // eslint-disable-next-line prefer-object-spread
87
- const tags = Object.assign({}, fields.tags)
88
88
  const hostname = fields.hostname
89
89
 
90
90
  this.#parentTracer = tracer
@@ -106,7 +106,7 @@ class DatadogSpan {
106
106
 
107
107
  this._spanContext = this._createContext(parent, fields)
108
108
  this._spanContext._name = operationName
109
- Object.assign(this._spanContext.getTags(), tags)
109
+ if (fields.tags) Object.assign(this._spanContext.getTags(), fields.tags)
110
110
  this._spanContext._hostname = hostname
111
111
 
112
112
  this._spanContext._trace.started.push(this)
@@ -348,21 +348,24 @@ class DatadogSpan {
348
348
 
349
349
  /**
350
350
  * @param {Record<string, unknown>} [attributes]
351
+ * @returns {Record<string, string> | undefined} `undefined` when nothing
352
+ * survives, so `extractSpanLinks` omits the slot without an emptiness probe.
351
353
  */
352
354
  _sanitizeAttributes (attributes = {}) {
353
- /** @type {Record<string, string>} */
354
- const out = {}
355
+ let out
355
356
  for (const key of Object.keys(attributes)) {
356
- addArrayOrScalarAttribute(out, key, attributes[key])
357
+ out = addArrayOrScalarAttribute(out, key, attributes[key])
357
358
  }
358
359
  return out
359
360
  }
360
361
 
361
362
  /**
362
363
  * @param {Record<string, unknown>} [attributes]
364
+ * @returns {Record<string, unknown> | undefined} `undefined` when nothing
365
+ * survives, so the encoders skip the slot without an emptiness probe.
363
366
  */
364
367
  _sanitizeEventAttributes (attributes = {}) {
365
- const sanitizedAttributes = {}
368
+ let sanitizedAttributes
366
369
 
367
370
  for (const key of Object.keys(attributes)) {
368
371
  const value = attributes[key]
@@ -375,8 +378,10 @@ class DatadogSpan {
375
378
  log.warn('Dropping span event attribute. It is not of an allowed type')
376
379
  }
377
380
  }
381
+ sanitizedAttributes ??= {}
378
382
  sanitizedAttributes[key] = newArray
379
383
  } else if (ALLOWED.has(typeof value)) {
384
+ sanitizedAttributes ??= {}
380
385
  sanitizedAttributes[key] = value
381
386
  } else {
382
387
  log.warn('Dropping span event attribute. It is not of an allowed type')
@@ -389,7 +394,7 @@ class DatadogSpan {
389
394
  let spanContext
390
395
  let startTime
391
396
 
392
- let baggage = {}
397
+ let baggage
393
398
  const propagationBehavior = this.#parentTracer._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT
394
399
  if (parent && parent._isRemote && propagationBehavior !== 'continue') {
395
400
  baggage = parent._baggageItems
@@ -431,7 +436,7 @@ class DatadogSpan {
431
436
  }
432
437
 
433
438
  if (propagationBehavior === 'restart') {
434
- spanContext._baggageItems = baggage
439
+ spanContext._baggageItems = baggage ?? {}
435
440
  }
436
441
  }
437
442
 
@@ -46,9 +46,7 @@ class DatadogSpanContext {
46
46
 
47
47
  toTraceId (get128bitId = false) {
48
48
  if (get128bitId) {
49
- return this._traceId.toBuffer().length <= 8 && this._trace.tags[TRACE_ID_128]
50
- ? this._trace.tags[TRACE_ID_128] + this._traceId.toString(16).padStart(16, '0')
51
- : this._traceId.toString(16).padStart(32, '0')
49
+ return this._traceId.toTraceIdHex(this._trace.tags[TRACE_ID_128]).padStart(32, '0')
52
50
  }
53
51
  return this._traceId.toString(10)
54
52
  }
@@ -23,6 +23,8 @@ class DatadogTracer {
23
23
  constructor (config, prioritySampler) {
24
24
  this._config = config
25
25
  this._service = config.service
26
+ // Lowercased once for span_format's per-span base-service comparison.
27
+ this.serviceLower = typeof config.service === 'string' ? config.service.toLowerCase() : ''
26
28
  this._version = config.version
27
29
  this._env = config.env
28
30
  this._logInjection = config.logInjection
@@ -64,21 +66,9 @@ class DatadogTracer {
64
66
  ? getContext(options.childOf)
65
67
  : getParent(options.references)
66
68
 
67
- // as per spec, allow the setting of service name through options
68
- const tags = {
69
- 'service.name': options?.tags?.service ? String(options.tags.service) : this._service,
70
- }
71
-
72
- // As per unified service tagging spec if a span is created with a service name different from the global
73
- // service name it will not inherit the global version value
74
- if (options?.tags?.service && options.tags.service !== this._service) {
75
- options.tags.version = undefined
76
- }
77
-
78
69
  const span = new Span(this, this._processor, this._prioritySampler, {
79
70
  operationName: options.operationName || name,
80
71
  parent,
81
- tags,
82
72
  startTime: options.startTime,
83
73
  hostname: this._hostname,
84
74
  traceId128BitGenerationEnabled: this._traceId128BitGenerationEnabled,
@@ -86,6 +76,20 @@ class DatadogTracer {
86
76
  links: options.links,
87
77
  }, this._debug)
88
78
 
79
+ // As per unified service tagging spec if a span is created with a service name different from the global
80
+ // service name it will not inherit the global version value
81
+ const ctx = span.context()
82
+ if (options.tags?.service) {
83
+ if (options.tags.service !== this._service) options.tags.version = undefined
84
+ // as per spec, allow the setting of service name through options; set it
85
+ // after all tags are merged so config/options values take precedence
86
+ // eslint-disable-next-line eslint-rules/eslint-prefer-set-service-name
87
+ ctx.setTag('service.name', String(options.tags.service))
88
+ } else {
89
+ // eslint-disable-next-line eslint-rules/eslint-prefer-set-service-name
90
+ ctx.setTag('service.name', this._service)
91
+ }
92
+
89
93
  span.addTags(this._config.tags)
90
94
  span.addTags(options.tags)
91
95
 
@@ -143,6 +143,7 @@ module.exports = class CiPlugin extends Plugin {
143
143
  this.fileLineToProbeId = new Map()
144
144
  this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
145
145
  this._testSuiteSpansByTestSuite = new Map()
146
+ this._pendingWorkerTracesByTestSuite = new Map()
146
147
  this._pendingRequestErrorTags = []
147
148
 
148
149
  this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => {
@@ -352,52 +353,8 @@ module.exports = class CiPlugin extends Plugin {
352
353
  const formattedTraces = JSON.parse(traces)
353
354
 
354
355
  for (const trace of formattedTraces) {
355
- for (const span of trace) {
356
- span.span_id = id(span.span_id)
357
- span.trace_id = id(span.trace_id)
358
- span.parent_id = id(span.parent_id)
359
-
360
- if (span.name?.startsWith(`${this.constructor.id}.`)) {
361
- span.meta[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
362
- if (span.name === `${this.constructor.id}.test` || span.name === `${this.constructor.id}.test_suite`) {
363
- Object.assign(span.meta, getSessionItrSkippingEnabledTags(this.testSessionSpan))
364
- }
365
- // augment with git information (since it will not be available in the worker)
366
- for (const key in this.testEnvironmentMetadata) {
367
- // CAREFUL: this bypasses the metadata/metrics distinction
368
- // Be careful not to pass numbers in `meta`
369
- if (key.startsWith('git.')) {
370
- span.meta[key] = this.testEnvironmentMetadata[key]
371
- }
372
- }
373
- }
374
-
375
- // Only test hooks run in the cucumber worker, so the test events do not have the
376
- // test session, test module and test suite ids. We have to update them here.
377
- if (span.name === 'cucumber.test' || span.name === 'mocha.test') {
378
- const testSuite = span.meta[TEST_SUITE]
379
- const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuite)
380
- if (!testSuiteSpan) {
381
- log.warn('Test suite span not found for test span with test suite %s', testSuite)
382
- continue
383
- }
384
-
385
- const testSuiteTags = getTestSuiteLevelVisibilityTags(testSuiteSpan, this.constructor.id)
386
- span.meta = {
387
- ...span.meta,
388
- ...testSuiteTags,
389
- ...getSessionRequestErrorTags(this.testSessionSpan),
390
- }
391
- }
392
-
393
- // Jest and Vitest worker test spans are serialized in the worker and may not include
394
- // request error tags; add them from the session span in the main process.
395
- if ((span.name === 'jest.test' || span.name === 'vitest.test' || span.name === 'vitest.test_suite') &&
396
- this.testSessionSpan) {
397
- Object.assign(span.meta, getSessionRequestErrorTags(this.testSessionSpan))
398
- }
399
- }
400
- this.tracer._exporter.export(trace)
356
+ this._prepareWorkerTrace(trace)
357
+ this._exportWorkerTraceOrBuffer(trace)
401
358
  }
402
359
  })
403
360
 
@@ -502,6 +459,134 @@ module.exports = class CiPlugin extends Plugin {
502
459
  return getSessionItrSkippingEnabledTags(this.testSessionSpan)
503
460
  }
504
461
 
462
+ /**
463
+ * Normalizes worker trace identifiers and adds process-level metadata before exporting or buffering.
464
+ *
465
+ * @param {object[]} trace - Worker trace spans decoded from the worker payload.
466
+ */
467
+ _prepareWorkerTrace (trace) {
468
+ for (const span of trace) {
469
+ span.span_id = id(span.span_id)
470
+ span.trace_id = id(span.trace_id)
471
+ span.parent_id = id(span.parent_id)
472
+
473
+ if (span.name?.startsWith(`${this.constructor.id}.`)) {
474
+ span.meta[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
475
+ if (span.name === `${this.constructor.id}.test` || span.name === `${this.constructor.id}.test_suite`) {
476
+ Object.assign(span.meta, getSessionItrSkippingEnabledTags(this.testSessionSpan))
477
+ }
478
+ // augment with git information (since it will not be available in the worker)
479
+ for (const key in this.testEnvironmentMetadata) {
480
+ // CAREFUL: this bypasses the metadata/metrics distinction
481
+ // Be careful not to pass numbers in `meta`
482
+ if (key.startsWith('git.')) {
483
+ span.meta[key] = this.testEnvironmentMetadata[key]
484
+ }
485
+ }
486
+ }
487
+
488
+ // Jest and Vitest worker test spans are serialized in the worker and may not include
489
+ // request error tags; add them from the session span in the main process.
490
+ if ((span.name === 'jest.test' || span.name === 'vitest.test' || span.name === 'vitest.test_suite') &&
491
+ this.testSessionSpan) {
492
+ Object.assign(span.meta, getSessionRequestErrorTags(this.testSessionSpan))
493
+ }
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Adds suite-level CI Visibility tags to worker test spans when their suite span is available.
499
+ *
500
+ * @param {object[]} trace - Worker trace spans.
501
+ * @returns {string|undefined} Missing test suite name, if the trace cannot be exported yet.
502
+ */
503
+ _addSuiteTagsToWorkerTrace (trace) {
504
+ for (const span of trace) {
505
+ // Only test hooks run in Cucumber and Mocha workers, so the test events do not have the
506
+ // test session, test module and test suite ids. We have to update them in the main process.
507
+ if (span.name !== 'cucumber.test' && span.name !== 'mocha.test') continue
508
+
509
+ const testSuite = span.meta[TEST_SUITE]
510
+ const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuite)
511
+ if (!testSuiteSpan) return testSuite
512
+
513
+ const testSuiteTags = getTestSuiteLevelVisibilityTags(testSuiteSpan, this.constructor.id)
514
+ span.meta = {
515
+ ...span.meta,
516
+ ...testSuiteTags,
517
+ ...getSessionRequestErrorTags(this.testSessionSpan),
518
+ }
519
+ }
520
+ }
521
+
522
+ /**
523
+ * Stores a worker trace until the matching test suite span exists in the main process.
524
+ *
525
+ * @param {string} testSuite - Test suite path used as the pending trace key.
526
+ * @param {object[]} trace - Worker trace spans.
527
+ */
528
+ _bufferWorkerTrace (testSuite, trace) {
529
+ let pendingTraces = this._pendingWorkerTracesByTestSuite.get(testSuite)
530
+ if (!pendingTraces) {
531
+ pendingTraces = []
532
+ this._pendingWorkerTracesByTestSuite.set(testSuite, pendingTraces)
533
+ }
534
+ pendingTraces.push(trace)
535
+ }
536
+
537
+ /**
538
+ * Exports a worker trace immediately, or buffers it if suite-level tags cannot be added yet.
539
+ *
540
+ * @param {object[]} trace - Worker trace spans.
541
+ */
542
+ _exportWorkerTraceOrBuffer (trace) {
543
+ const missingTestSuite = this._addSuiteTagsToWorkerTrace(trace)
544
+ if (missingTestSuite) {
545
+ this._bufferWorkerTrace(missingTestSuite, trace)
546
+ return
547
+ }
548
+ this.tracer._exporter.export(trace)
549
+ }
550
+
551
+ /**
552
+ * Exports buffered worker traces for a suite after its suite span has been created.
553
+ *
554
+ * @param {string} testSuite - Test suite path that may now have pending worker traces.
555
+ */
556
+ _exportPendingWorkerTracesForTestSuite (testSuite) {
557
+ const pendingTraces = this._pendingWorkerTracesByTestSuite.get(testSuite)
558
+ if (!pendingTraces) return
559
+
560
+ this._pendingWorkerTracesByTestSuite.delete(testSuite)
561
+ for (const trace of pendingTraces) {
562
+ this._exportWorkerTraceOrBuffer(trace)
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Drains all buffered worker traces, falling back to the previous unaugmented export behavior
568
+ * if a matching suite span never appears.
569
+ */
570
+ _exportPendingWorkerTraces () {
571
+ if (!this._pendingWorkerTracesByTestSuite.size) return
572
+
573
+ const pendingTraces = new Set()
574
+ for (const traces of this._pendingWorkerTracesByTestSuite.values()) {
575
+ for (const trace of traces) {
576
+ pendingTraces.add(trace)
577
+ }
578
+ }
579
+ this._pendingWorkerTracesByTestSuite.clear()
580
+
581
+ for (const trace of pendingTraces) {
582
+ const missingTestSuite = this._addSuiteTagsToWorkerTrace(trace)
583
+ if (missingTestSuite) {
584
+ log.warn('Test suite span not found for test span with test suite %s', missingTestSuite)
585
+ }
586
+ this.tracer._exporter.export(trace)
587
+ }
588
+ }
589
+
505
590
  /**
506
591
  * @param {import('../config/config-base')} config - Tracer configuration
507
592
  * @param {boolean} shouldGetEnvironmentData - Whether to get environment data
@@ -333,11 +333,12 @@ class PrioritySampler {
333
333
  if (!trace.tags[DECISION_MAKER_KEY]) {
334
334
  trace.tags[DECISION_MAKER_KEY] = `-${mechanism}`
335
335
  }
336
- } else if (DECISION_MAKER_KEY in trace.tags) {
337
- // Guard the `delete` so the common drop path doesn't pay the V8
338
- // dictionary-mode transition unless a prior keep decision actually
339
- // set the tag.
340
- delete trace.tags[DECISION_MAKER_KEY]
336
+ } else if (trace.tags[DECISION_MAKER_KEY] !== undefined) {
337
+ // Clear by assigning undefined rather than deleting: `delete` drops
338
+ // trace.tags into V8 dictionary (slow) mode for the per-trace extract
339
+ // and propagation scans that follow. Both skip undefined values, so the
340
+ // emitted meta and injected headers are unchanged.
341
+ trace.tags[DECISION_MAKER_KEY] = undefined
341
342
  }
342
343
  }
343
344
 
@@ -5,16 +5,15 @@ const { pathToFileURL } = require('url')
5
5
 
6
6
  const satisfies = require('../../../../vendor/dist/semifies')
7
7
  const getGitMetadata = require('../git_metadata')
8
+ const log = require('../log')
8
9
  const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
9
10
  const { getIsAzureFunction } = require('../serverless')
10
11
  const { getAzureTagsFromMetadata, getAzureAppMetadata, getAzureFunctionMetadata } = require('../azure_metadata')
11
12
  const { getEnvironmentVariable } = require('../config/helper')
12
- const { getAgentUrl } = require('../agent/url')
13
13
  const { isACFActive } = require('../../../datadog-core/src/storage')
14
14
 
15
15
  const { AgentExporter } = require('./exporters/agent')
16
16
  const { FileExporter } = require('./exporters/file')
17
- const { ConsoleLogger } = require('./loggers/console')
18
17
  const WallProfiler = require('./profilers/wall')
19
18
  const SpaceProfiler = require('./profilers/space')
20
19
  const EventsProfiler = require('./profilers/events')
@@ -54,8 +53,7 @@ class Config {
54
53
  this.pprofPrefix = options.DD_PROFILING_PPROF_PREFIX
55
54
  this.v8ProfilerBugWorkaroundEnabled = options.DD_PROFILING_V8_PROFILER_BUG_WORKAROUND
56
55
 
57
- this.logger = ensureLogger(options.logger)
58
- this.url = getAgentUrl(options)
56
+ this.url = options.url
59
57
 
60
58
  this.libraryInjected = !!options.DD_INJECTION_ENABLED
61
59
 
@@ -73,7 +71,7 @@ class Config {
73
71
  const heapLimitExtensionSize = options.DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE
74
72
  const maxHeapExtensionCount = options.DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT
75
73
  const exportStrategies = oomMonitoringEnabled
76
- ? ensureOOMExportStrategies(options.DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES, this)
74
+ ? ensureOOMExportStrategies(options.DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES)
77
75
  : []
78
76
  const exportCommand = oomMonitoringEnabled ? buildExportCommand(this) : undefined
79
77
  this.oomMonitoring = {
@@ -102,7 +100,7 @@ class Config {
102
100
  if (level !== undefined) {
103
101
  const maxLevel = { gzip: 9, zstd: 22 }[uploadCompression]
104
102
  if (level > maxLevel) {
105
- this.logger.warn(`Invalid compression level ${level}. Will use ${maxLevel}.`)
103
+ log.warn('Invalid compression level %d. Will use %d.', level, maxLevel)
106
104
  level = maxLevel
107
105
  }
108
106
  }
@@ -119,8 +117,7 @@ class Config {
119
117
 
120
118
  const that = this
121
119
  function turnOffAsyncContextFrame (msg) {
122
- that.logger.warn(
123
- `DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED was set ${msg}, it will have no effect.`)
120
+ log.warn('DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED was set %s, it will have no effect.', msg)
124
121
  that.asyncContextFrameEnabled = false
125
122
  }
126
123
 
@@ -215,18 +212,18 @@ function getProfilers ({
215
212
  return profilersArray
216
213
  }
217
214
 
218
- function getExportStrategy (name, options) {
215
+ function getExportStrategy (name) {
219
216
  const strategy = Object.values(oomExportStrategies).find(value => value === name)
220
217
  if (strategy === undefined) {
221
- options.logger.error(`Unknown oom export strategy "${name}"`)
218
+ log.error('Unknown oom export strategy "%s"', name)
222
219
  }
223
220
  return strategy
224
221
  }
225
222
 
226
- function ensureOOMExportStrategies (strategies, options) {
223
+ function ensureOOMExportStrategies (strategies) {
227
224
  const set = new Set()
228
225
  for (const strategy of strategies) {
229
- set.add(getExportStrategy(strategy, options))
226
+ set.add(getExportStrategy(strategy))
230
227
  }
231
228
 
232
229
  return [...set]
@@ -239,7 +236,7 @@ function getExporter (name, options) {
239
236
  case 'file':
240
237
  return new FileExporter(options)
241
238
  default:
242
- options.logger.error(`Unknown exporter "${name}"`)
239
+ log.error('Unknown exporter "%s"', name)
243
240
  }
244
241
  }
245
242
 
@@ -255,7 +252,7 @@ function getProfiler (name, options) {
255
252
  case 'space':
256
253
  return new SpaceProfiler(options)
257
254
  default:
258
- options.logger.error(`Unknown profiler "${name}"`)
255
+ log.error('Unknown profiler "%s"', name)
259
256
  }
260
257
  }
261
258
 
@@ -278,17 +275,6 @@ function ensureProfilers (profilers, options) {
278
275
  return filteredProfilers
279
276
  }
280
277
 
281
- function ensureLogger (logger) {
282
- if (typeof logger?.debug !== 'function' ||
283
- typeof logger.info !== 'function' ||
284
- typeof logger.warn !== 'function' ||
285
- typeof logger.error !== 'function') {
286
- return new ConsoleLogger()
287
- }
288
-
289
- return logger
290
- }
291
-
292
278
  function buildExportCommand (options) {
293
279
  const tags = [...Object.entries(options.tags),
294
280
  ['snapshot', snapshotKinds.ON_OUT_OF_MEMORY]].map(([key, value]) => `${key}:${value}`).join(',')
@@ -9,6 +9,7 @@ const retry = require('../../../../../vendor/dist/retry')
9
9
  // TODO: avoid using dd-trace internals. Make this a separate module?
10
10
  const docker = require('../../exporters/common/docker')
11
11
  const FormData = require('../../exporters/common/form-data')
12
+ const log = require('../../log')
12
13
  const { storage } = require('../../../../datadog-core')
13
14
  const version = require('../../../../../package.json').version
14
15
  const telemetryMetrics = require('../../telemetry/metrics')
@@ -89,9 +90,8 @@ function computeRetries (uploadTimeout) {
89
90
  class AgentExporter extends EventSerializer {
90
91
  constructor (config = {}) {
91
92
  super(config)
92
- const { url, logger, uploadTimeout } = config
93
+ const { url, uploadTimeout } = config
93
94
  this._url = url
94
- this._logger = logger
95
95
 
96
96
  const [backoffTries, backoffTime] = computeRetries(uploadTimeout)
97
97
 
@@ -109,12 +109,11 @@ class AgentExporter extends EventSerializer {
109
109
  contentType: 'application/json',
110
110
  }])
111
111
 
112
- this._logger.debug(() => {
113
- return `Building agent export report:\n${event}`
114
- })
112
+ log.debug('Building agent export report:\n%s', event)
115
113
 
116
114
  for (const [type, buffer] of Object.entries(profiles)) {
117
- this._logger.debug(() => {
115
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
116
+ log.debug(() => {
118
117
  const bytes = buffer.toString('hex').match(/../g).join(' ')
119
118
  return `Adding ${type} profile to agent export: ` + bytes
120
119
  })
@@ -163,7 +162,8 @@ class AgentExporter extends EventSerializer {
163
162
  options.port = httpOptions.port
164
163
  }
165
164
 
166
- this._logger.debug(() => {
165
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
166
+ log.debug(() => {
167
167
  return `Submitting profiler agent report attempt #${attempt} to: ${JSON.stringify(options)}`
168
168
  })
169
169
 
@@ -171,7 +171,7 @@ class AgentExporter extends EventSerializer {
171
171
  if (err) {
172
172
  const { status } = err
173
173
  if ((typeof status !== 'number' || status >= 500 || status === 429) && operation.retry(err)) {
174
- this._logger.warn(`Error from the agent: ${err.message}`)
174
+ log.warn('Error from the agent: %s', err.message)
175
175
  } else {
176
176
  reject(err)
177
177
  }
@@ -180,9 +180,10 @@ class AgentExporter extends EventSerializer {
180
180
 
181
181
  getBody(response, (err, body) => {
182
182
  if (err) {
183
- this._logger.warn(`Error reading agent response: ${err.message}`)
183
+ log.warn('Error reading agent response: %s', err.message)
184
184
  } else {
185
- this._logger.debug(() => {
185
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
186
+ log.debug(() => {
186
187
  const bytes = (body.toString('hex').match(/../g) || []).join(' ')
187
188
  return `Agent export response: ${bytes}`
188
189
  })