dd-trace 5.86.0 → 5.88.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 (105) hide show
  1. package/LICENSE-3rdparty.csv +60 -32
  2. package/ext/exporters.d.ts +1 -0
  3. package/ext/exporters.js +1 -0
  4. package/index.d.ts +243 -7
  5. package/package.json +9 -6
  6. package/packages/datadog-instrumentations/src/ai.js +54 -90
  7. package/packages/datadog-instrumentations/src/cucumber.js +14 -0
  8. package/packages/datadog-instrumentations/src/helpers/hook.js +17 -11
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +55 -14
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +15 -13
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +103 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.js +108 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -1
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/transformer.js +21 -0
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +138 -12
  17. package/packages/datadog-instrumentations/src/http/client.js +119 -1
  18. package/packages/datadog-instrumentations/src/jest.js +179 -15
  19. package/packages/datadog-instrumentations/src/kafkajs.js +20 -17
  20. package/packages/datadog-instrumentations/src/mocha/utils.js +6 -0
  21. package/packages/datadog-instrumentations/src/mysql2.js +131 -64
  22. package/packages/datadog-instrumentations/src/playwright.js +9 -1
  23. package/packages/datadog-instrumentations/src/stripe.js +92 -0
  24. package/packages/datadog-instrumentations/src/vitest.js +11 -0
  25. package/packages/datadog-plugin-amqplib/src/consumer.js +14 -10
  26. package/packages/datadog-plugin-amqplib/src/producer.js +23 -19
  27. package/packages/datadog-plugin-azure-functions/src/index.js +53 -37
  28. package/packages/datadog-plugin-bullmq/src/consumer.js +33 -11
  29. package/packages/datadog-plugin-bullmq/src/producer.js +60 -31
  30. package/packages/datadog-plugin-cucumber/src/index.js +9 -6
  31. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +33 -0
  32. package/packages/datadog-plugin-cypress/src/support.js +48 -8
  33. package/packages/datadog-plugin-jest/src/index.js +12 -2
  34. package/packages/datadog-plugin-jest/src/util.js +2 -1
  35. package/packages/datadog-plugin-kafkajs/src/consumer.js +22 -12
  36. package/packages/datadog-plugin-kafkajs/src/producer.js +33 -22
  37. package/packages/datadog-plugin-mocha/src/index.js +9 -6
  38. package/packages/datadog-plugin-playwright/src/index.js +10 -6
  39. package/packages/datadog-plugin-vitest/src/index.js +13 -8
  40. package/packages/dd-trace/src/appsec/addresses.js +11 -0
  41. package/packages/dd-trace/src/appsec/channels.js +5 -1
  42. package/packages/dd-trace/src/appsec/downstream_requests.js +302 -0
  43. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +1 -1
  44. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +1 -1
  45. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +1 -1
  46. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +4 -5
  47. package/packages/dd-trace/src/appsec/iast/path-line.js +36 -25
  48. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +1 -1
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +3 -2
  51. package/packages/dd-trace/src/appsec/index.js +103 -0
  52. package/packages/dd-trace/src/appsec/rasp/ssrf.js +66 -4
  53. package/packages/dd-trace/src/azure_metadata.js +0 -2
  54. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +14 -1
  55. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  56. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -0
  57. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -1
  58. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
  59. package/packages/dd-trace/src/ci-visibility/requests/request.js +236 -0
  60. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  61. package/packages/dd-trace/src/config/defaults.js +148 -195
  62. package/packages/dd-trace/src/config/helper.js +43 -1
  63. package/packages/dd-trace/src/config/index.js +42 -14
  64. package/packages/dd-trace/src/config/supported-configurations.json +4115 -510
  65. package/packages/dd-trace/src/constants.js +0 -2
  66. package/packages/dd-trace/src/crashtracking/crashtracker.js +10 -3
  67. package/packages/dd-trace/src/datastreams/pathway.js +22 -3
  68. package/packages/dd-trace/src/datastreams/processor.js +14 -1
  69. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -2
  70. package/packages/dd-trace/src/debugger/devtools_client/index.js +75 -23
  71. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +23 -1
  72. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +3 -3
  73. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +168 -36
  74. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +18 -0
  75. package/packages/dd-trace/src/encode/agentless-json.js +141 -0
  76. package/packages/dd-trace/src/exporter.js +2 -0
  77. package/packages/dd-trace/src/exporters/agent/writer.js +22 -8
  78. package/packages/dd-trace/src/exporters/agentless/index.js +89 -0
  79. package/packages/dd-trace/src/exporters/agentless/writer.js +184 -0
  80. package/packages/dd-trace/src/exporters/common/agents.js +1 -1
  81. package/packages/dd-trace/src/exporters/common/request.js +4 -4
  82. package/packages/dd-trace/src/llmobs/constants/writers.js +1 -1
  83. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -3
  84. package/packages/dd-trace/src/llmobs/sdk.js +34 -5
  85. package/packages/dd-trace/src/opentelemetry/context_manager.js +19 -46
  86. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +3 -4
  87. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -5
  88. package/packages/dd-trace/src/opentracing/span.js +6 -4
  89. package/packages/dd-trace/src/plugins/ci_plugin.js +57 -5
  90. package/packages/dd-trace/src/plugins/database.js +57 -45
  91. package/packages/dd-trace/src/plugins/outbound.js +27 -2
  92. package/packages/dd-trace/src/plugins/tracing.js +39 -4
  93. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +7 -0
  94. package/packages/dd-trace/src/plugins/util/test.js +48 -0
  95. package/packages/dd-trace/src/plugins/util/web.js +8 -7
  96. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -0
  97. package/packages/dd-trace/src/propagation-hash/index.js +145 -0
  98. package/packages/dd-trace/src/proxy.js +4 -0
  99. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  100. package/packages/dd-trace/src/startup-log.js +3 -3
  101. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.json +0 -106
  102. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +0 -741
  103. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +0 -11
  104. package/packages/dd-trace/src/plugins/util/serverless.js +0 -8
  105. package/packages/dd-trace/src/scope/noop/scope.js +0 -21
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { trace, ROOT_CONTEXT, propagation } = require('@opentelemetry/api')
4
4
  const { storage } = require('../../../datadog-core')
5
+ const { getAllBaggageItems, setBaggageItem, removeAllBaggageItems } = require('../baggage')
5
6
 
6
7
  const tracer = require('../../')
7
8
  const SpanContext = require('./span_context')
@@ -19,31 +20,26 @@ class ContextManager {
19
20
 
20
21
  const storedSpan = store ? trace.getSpan(store) : null
21
22
 
23
+ // Convert DD baggage to OTel format
24
+ const baggages = getAllBaggageItems()
25
+ const hasBaggage = Object.keys(baggages).length > 0
26
+ let otelBaggages
27
+ if (hasBaggage) {
28
+ const entries = {}
29
+ for (const [key, value] of Object.entries(baggages)) {
30
+ entries[key] = { value }
31
+ }
32
+ otelBaggages = propagation.createBaggage(entries)
33
+ }
34
+
22
35
  // If stored span wraps the active DD span, prefer the stored context
23
36
  if (storedSpan && storedSpan._ddSpan === activeSpan) {
24
- const baggages = JSON.parse(activeSpan.getAllBaggageItems())
25
- if (Object.keys(baggages).length > 0) {
26
- const entries = {}
27
- for (const [key, value] of Object.entries(baggages)) {
28
- entries[key] = { value }
29
- }
30
- const otelBaggages = propagation.createBaggage(entries)
31
- return propagation.setBaggage(store, otelBaggages)
32
- }
37
+ if (otelBaggages) return propagation.setBaggage(store, otelBaggages)
33
38
  return store
34
39
  }
35
40
 
36
41
  if (!activeSpan) {
37
- const storedBaggageItems = storedSpan?._spanContext?._ddContext?._baggageItems
38
- if (storedBaggageItems) {
39
- const baggages = storedBaggageItems
40
- const entries = {}
41
- for (const [key, value] of Object.entries(baggages)) {
42
- entries[key] = { value }
43
- }
44
- const otelBaggages = propagation.createBaggage(entries)
45
- return propagation.setBaggage(baseContext, otelBaggages)
46
- }
42
+ if (otelBaggages) return propagation.setBaggage(baseContext, otelBaggages)
47
43
  return baseContext
48
44
  }
49
45
 
@@ -53,18 +49,6 @@ class ContextManager {
53
49
  ddContext._otelSpanContext = new SpanContext(ddContext)
54
50
  }
55
51
 
56
- // Convert DD baggage to OTel format
57
- const baggages = JSON.parse(activeSpan.getAllBaggageItems())
58
- const hasBaggage = Object.keys(baggages).length > 0
59
- let otelBaggages
60
- if (hasBaggage) {
61
- const entries = {}
62
- for (const [key, value] of Object.entries(baggages)) {
63
- entries[key] = { value }
64
- }
65
- otelBaggages = propagation.createBaggage(entries)
66
- }
67
-
68
52
  if (store && trace.getSpanContext(store) === ddContext._otelSpanContext) {
69
53
  return otelBaggages ? propagation.setBaggage(store, otelBaggages) : store
70
54
  }
@@ -86,22 +70,11 @@ class ContextManager {
86
70
  if (baggages) {
87
71
  baggageItems = baggages.getAllEntries()
88
72
  }
89
- if (span && span._ddSpan) {
90
- // does otel always override datadog?
91
- span._ddSpan.removeAllBaggageItems()
92
- for (const baggage of baggageItems) {
93
- span._ddSpan.setBaggageItem(baggage[0], baggage[1].value)
94
- }
95
- return ddScope.activate(span._ddSpan, run)
96
- }
97
- // span instanceof NonRecordingSpan
98
- const ddContext = span?._spanContext?._ddContext
99
- if (ddContext && ddContext._baggageItems) {
100
- ddContext._baggageItems = {}
101
- for (const baggage of baggageItems) {
102
- ddContext._baggageItems[baggage[0]] = baggage[1].value
103
- }
73
+ removeAllBaggageItems()
74
+ for (const baggage of baggageItems) {
75
+ setBaggageItem(baggage[0], baggage[1].value)
104
76
  }
77
+ if (span && span._ddSpan) return ddScope.activate(span._ddSpan, run)
105
78
  return run()
106
79
  }
107
80
 
@@ -20,7 +20,7 @@ class OtlpHttpExporterBase {
20
20
  * Creates a new OtlpHttpExporterBase instance.
21
21
  *
22
22
  * @param {string} url - OTLP endpoint URL
23
- * @param {string} headers - Additional HTTP headers as comma-separated key=value string
23
+ * @param {string|undefined} headers - Additional HTTP headers as comma-separated key=value string
24
24
  * @param {number} timeout - Request timeout in milliseconds
25
25
  * @param {string} protocol - OTLP protocol (http/protobuf or http/json)
26
26
  * @param {string} defaultPath - Default path to use if URL has no path
@@ -117,11 +117,10 @@ class OtlpHttpExporterBase {
117
117
 
118
118
  /**
119
119
  * Parses additional HTTP headers from a comma-separated string.
120
- * @param {string} headersString - Comma-separated key=value pairs
120
+ * @param {string} [headersString=''] - Comma-separated key=value pairs
121
121
  * @returns {Record<string, string>} Parsed headers object
122
- * @private
123
122
  */
124
- #parseAdditionalHeaders (headersString) {
123
+ #parseAdditionalHeaders (headersString = '') {
125
124
  const headers = {}
126
125
  let key = ''
127
126
  let value = ''
@@ -670,10 +670,8 @@ class TextMapPropagator {
670
670
  if (!this._hasPropagationStyle('extract', 'baggage')) return
671
671
  if (!carrier?.baggage) return
672
672
  const baggages = carrier.baggage.split(',')
673
- const tagAllKeys = this._config.baggageTagKeys === '*'
674
- const keysToSpanTag = tagAllKeys
675
- ? undefined
676
- : new Set(this._config.baggageTagKeys.split(','))
673
+ const baggageTagKeys = new Set(this._config.baggageTagKeys)
674
+ const tagAllKeys = baggageTagKeys.has('*')
677
675
  for (const keyValue of baggages) {
678
676
  if (!keyValue) continue
679
677
 
@@ -707,7 +705,7 @@ class TextMapPropagator {
707
705
  return
708
706
  }
709
707
 
710
- if (spanContext && (tagAllKeys || keysToSpanTag?.has(key))) {
708
+ if (spanContext && (tagAllKeys || baggageTagKeys.has(key))) {
711
709
  spanContext._trace.tags['baggage.' + key] = value
712
710
  }
713
711
  setBaggageItem(key, value)
@@ -3,7 +3,6 @@
3
3
  // TODO (new internal tracer): use DC events for lifecycle metrics and test them
4
4
  const { performance } = require('perf_hooks')
5
5
  const now = performance.now.bind(performance)
6
- const dateNow = Date.now
7
6
  const util = require('util')
8
7
  const { channel } = require('dc-polyfill')
9
8
  const id = require('../id')
@@ -13,12 +12,15 @@ const log = require('../log')
13
12
  const { storage } = require('../../../datadog-core')
14
13
  const telemetryMetrics = require('../telemetry/metrics')
15
14
  const { getValueFromEnvSources } = require('../config/helper')
15
+ const { isTrue } = require('../util')
16
16
  const SpanContext = require('./span_context')
17
17
 
18
+ const dateNow = Date.now
19
+
18
20
  const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
19
21
 
20
- const DD_TRACE_EXPERIMENTAL_STATE_TRACKING = getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_STATE_TRACKING')
21
- const DD_TRACE_EXPERIMENTAL_SPAN_COUNTS = getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_SPAN_COUNTS')
22
+ const DD_TRACE_EXPERIMENTAL_STATE_TRACKING = isTrue(getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_STATE_TRACKING'))
23
+ const DD_TRACE_EXPERIMENTAL_SPAN_COUNTS = isTrue(getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_SPAN_COUNTS'))
22
24
 
23
25
  const unfinishedRegistry = createRegistry('unfinished')
24
26
  const finishedRegistry = createRegistry('finished')
@@ -251,7 +253,7 @@ class DatadogSpan {
251
253
  return
252
254
  }
253
255
 
254
- if (DD_TRACE_EXPERIMENTAL_STATE_TRACKING === 'true' && !this._spanContext._tags['service.name']) {
256
+ if (DD_TRACE_EXPERIMENTAL_STATE_TRACKING && !this._spanContext._tags['service.name']) {
255
257
  log.error('Finishing invalid span: %s', this)
256
258
  }
257
259
 
@@ -63,6 +63,8 @@ const {
63
63
  getPullRequestDiff,
64
64
  getModifiedFilesFromDiff,
65
65
  getPullRequestBaseBranch,
66
+ getSessionRequestErrorTags,
67
+ DD_CI_LIBRARY_CONFIGURATION_ERROR,
66
68
  TEST_IS_TEST_FRAMEWORK_WORKER,
67
69
  TEST_IS_NEW,
68
70
  TEST_IS_RUM_ACTIVE,
@@ -120,6 +122,7 @@ module.exports = class CiPlugin extends Plugin {
120
122
  this.fileLineToProbeId = new Map()
121
123
  this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
122
124
  this._testSuiteSpansByTestSuite = new Map()
125
+ this._pendingRequestErrorTags = []
123
126
 
124
127
  this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => {
125
128
  const { onDone, isParallel, frameworkVersion } = ctx
@@ -131,10 +134,15 @@ module.exports = class CiPlugin extends Plugin {
131
134
  this.tracer._exporter.getLibraryConfiguration(this.testConfiguration, (err, libraryConfig) => {
132
135
  if (err) {
133
136
  log.error('Library configuration could not be fetched. %s', err.message)
137
+ this._addRequestErrorTag(DD_CI_LIBRARY_CONFIGURATION_ERROR, err)
134
138
  } else {
135
139
  this.libraryConfig = libraryConfig
136
140
  }
137
141
 
142
+ const requestErrorTags = this.testSessionSpan
143
+ ? getSessionRequestErrorTags(this.testSessionSpan)
144
+ : Object.fromEntries(this._pendingRequestErrorTags.map(({ tag, value }) => [tag, value]))
145
+
138
146
  const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, isParallel, frameworkVersion)
139
147
  const metadataTags = {
140
148
  test: {
@@ -142,7 +150,7 @@ module.exports = class CiPlugin extends Plugin {
142
150
  },
143
151
  }
144
152
  this.tracer._exporter.addMetadataTags(metadataTags)
145
- onDone({ err, libraryConfig })
153
+ onDone({ err, libraryConfig, requestErrorTags })
146
154
  })
147
155
  })
148
156
 
@@ -200,6 +208,10 @@ module.exports = class CiPlugin extends Plugin {
200
208
  },
201
209
  integrationName: this.constructor.id,
202
210
  })
211
+ for (const { tag, value } of this._pendingRequestErrorTags) {
212
+ this.testSessionSpan.setTag(tag, value)
213
+ }
214
+ this._pendingRequestErrorTags = []
203
215
  // TODO: add telemetry tag when we can add `is_agentless_log_submission_enabled` for agentless log submission
204
216
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'session')
205
217
 
@@ -209,6 +221,7 @@ module.exports = class CiPlugin extends Plugin {
209
221
  [COMPONENT]: this.constructor.id,
210
222
  ...this.testEnvironmentMetadata,
211
223
  ...testModuleSpanMetadata,
224
+ ...getSessionRequestErrorTags(this.testSessionSpan),
212
225
  },
213
226
  integrationName: this.constructor.id,
214
227
  })
@@ -230,7 +243,10 @@ module.exports = class CiPlugin extends Plugin {
230
243
  this.addSub(`ci:${this.constructor.id}:itr:skipped-suites`, ({ skippedSuites, frameworkVersion }) => {
231
244
  const testCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
232
245
  for (const testSuite of skippedSuites) {
233
- const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, this.constructor.id)
246
+ const testSuiteMetadata = {
247
+ ...getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, this.constructor.id),
248
+ ...getSessionRequestErrorTags(this.testSessionSpan),
249
+ }
234
250
  if (this.itrCorrelationId) {
235
251
  testSuiteMetadata[ITR_CORRELATION_ID] = this.itrCorrelationId
236
252
  }
@@ -261,8 +277,10 @@ module.exports = class CiPlugin extends Plugin {
261
277
  this.tracer._exporter.getKnownTests(this.testConfiguration, (err, knownTests) => {
262
278
  if (err) {
263
279
  log.error('Known tests could not be fetched. %s', err.message)
264
- this.libraryConfig.isEarlyFlakeDetectionEnabled = false
265
- this.libraryConfig.isKnownTestsEnabled = false
280
+ if (this.libraryConfig) {
281
+ this.libraryConfig.isEarlyFlakeDetectionEnabled = false
282
+ this.libraryConfig.isKnownTestsEnabled = false
283
+ }
266
284
  }
267
285
  onDone({ err, knownTests })
268
286
  })
@@ -279,7 +297,9 @@ module.exports = class CiPlugin extends Plugin {
279
297
  this.tracer._exporter.getTestManagementTests(this.testConfiguration, (err, testManagementTests) => {
280
298
  if (err) {
281
299
  log.error('Test management tests could not be fetched. %s', err.message)
282
- this.libraryConfig.isTestManagementEnabled = false
300
+ if (this.libraryConfig) {
301
+ this.libraryConfig.isTestManagementEnabled = false
302
+ }
283
303
  }
284
304
  onDone({ err, testManagementTests })
285
305
  })
@@ -346,8 +366,15 @@ module.exports = class CiPlugin extends Plugin {
346
366
  span.meta = {
347
367
  ...span.meta,
348
368
  ...testSuiteTags,
369
+ ...getSessionRequestErrorTags(this.testSessionSpan),
349
370
  }
350
371
  }
372
+
373
+ // Jest and Vitest worker test spans are serialized in the worker and may not include
374
+ // request error tags; add them from the session span in the main process.
375
+ if ((span.name === 'jest.test' || span.name === 'vitest.test') && this.testSessionSpan) {
376
+ Object.assign(span.meta, getSessionRequestErrorTags(this.testSessionSpan))
377
+ }
351
378
  }
352
379
  this.tracer._exporter.export(trace)
353
380
  }
@@ -418,6 +445,30 @@ module.exports = class CiPlugin extends Plugin {
418
445
  }
419
446
  }
420
447
 
448
+ /**
449
+ * Adds a hidden _dd tag to the test session span when a test-optimization request fails.
450
+ * If the session span does not exist yet (e.g. library-configuration failed before session:start),
451
+ * the tag is queued and applied when the span is created.
452
+ * @param {string} tag - Tag name (e.g. DD_CI_LIBRARY_CONFIGURATION_ERROR)
453
+ * @param {Error} err - Request error
454
+ */
455
+ _addRequestErrorTag (tag, err) {
456
+ const value = 'true'
457
+ if (this.testSessionSpan) {
458
+ this.testSessionSpan.setTag(tag, value)
459
+ } else {
460
+ this._pendingRequestErrorTags.push({ tag, value })
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Returns request error tags from the test session span for propagation to module, suite and test spans.
466
+ * @returns {Record<string, string>}
467
+ */
468
+ getSessionRequestErrorTags () {
469
+ return getSessionRequestErrorTags(this.testSessionSpan)
470
+ }
471
+
421
472
  configure (config, shouldGetEnvironmentData = true) {
422
473
  super.configure(config)
423
474
 
@@ -529,6 +580,7 @@ module.exports = class CiPlugin extends Plugin {
529
580
  [TEST_SESSION_ID]: testSuiteSpan.context().toTraceId(),
530
581
  [TEST_COMMAND]: testSuiteSpan.context()._tags[TEST_COMMAND],
531
582
  [TEST_MODULE]: this.constructor.id,
583
+ ...getSessionRequestErrorTags(this.testSessionSpan),
532
584
  }
533
585
  if (testSuiteSpan.context()._parentId) {
534
586
  suiteTags[TEST_MODULE_ID] = testSuiteSpan.context()._parentId.toString(10)
@@ -1,88 +1,94 @@
1
1
  'use strict'
2
2
 
3
3
  const { PEER_SERVICE_KEY, PEER_SERVICE_SOURCE_KEY } = require('../constants')
4
+ const propagationHash = require('../propagation-hash')
4
5
  const StoragePlugin = require('./storage')
5
6
 
6
7
  class DatabasePlugin extends StoragePlugin {
7
8
  static operation = 'query'
8
9
  static peerServicePrecursors = ['db.name']
9
10
 
10
- constructor (...args) {
11
- super(...args)
12
- this.serviceTags = {
13
- dddbs: '',
14
- encodedDddbs: '',
15
- dde: '',
16
- encodedDde: '',
17
- ddps: '',
18
- encodedDdps: '',
19
- ddpv: '',
20
- encodedDdpv: '',
21
- }
22
- }
23
-
24
- encodingServiceTags (serviceTag, encodeATag, spanConfig) {
25
- if (serviceTag !== spanConfig) {
26
- this.serviceTags[serviceTag] = spanConfig
27
- this.serviceTags[encodeATag] = encodeURIComponent(spanConfig)
28
- }
29
- }
30
-
31
- createDBMPropagationCommentService (serviceName, span) {
32
- this.encodingServiceTags('dddbs', 'encodedDddbs', serviceName)
33
- this.encodingServiceTags('dde', 'encodedDde', this.tracer._env)
34
- this.encodingServiceTags('ddps', 'encodedDdps', this.tracer._service)
35
- this.encodingServiceTags('ddpv', 'encodedDdpv', this.tracer._version)
36
- if (span.context()._tags['out.host']) {
37
- this.encodingServiceTags('ddh', 'encodedDdh', span._spanContext._tags['out.host'])
38
- }
39
- if (span.context()._tags['db.name']) {
40
- this.encodingServiceTags('dddb', 'encodedDddb', span._spanContext._tags['db.name'])
41
- }
42
-
43
- const { encodedDddb, encodedDddbs, encodedDde, encodedDdh, encodedDdps, encodedDdpv } = this.serviceTags
11
+ /**
12
+ * @param {string} serviceName
13
+ * @param {import('../../../..').Span} span
14
+ * @param {object} peerData
15
+ * @returns {string}
16
+ */
17
+ #createDBMPropagationCommentService (serviceName, span, peerData) {
18
+ const spanTags = span.context()._tags
19
+ const encodedDddb = encode(spanTags['db.name'])
20
+ const encodedDddbs = encode(serviceName)
21
+ const encodedDde = encode(this.tracer._env)
22
+ const encodedDdh = encode(spanTags['out.host'])
23
+ const encodedDdps = this.tracer._service ?? ''
24
+ const encodedDdpv = this.tracer._version
44
25
 
45
26
  let dbmComment = `dddb='${encodedDddb}',dddbs='${encodedDddbs}',dde='${encodedDde}',ddh='${encodedDdh}',` +
46
27
  `ddps='${encodedDdps}',ddpv='${encodedDdpv}'`
47
28
 
48
- const peerData = this.getPeerService(span.context()._tags)
49
29
  if (peerData !== undefined && peerData[PEER_SERVICE_SOURCE_KEY] === PEER_SERVICE_KEY) {
50
- this.encodingServiceTags('ddprs', 'encodedDdprs', peerData[PEER_SERVICE_KEY])
51
-
52
- const { encodedDdprs } = this.serviceTags
53
- dbmComment += `,ddprs='${encodedDdprs}'`
30
+ dbmComment += `,ddprs='${encode(peerData[PEER_SERVICE_KEY])}'`
54
31
  }
55
32
  return dbmComment
56
33
  }
57
34
 
58
- getDbmServiceName (span, tracerService) {
35
+ /**
36
+ * @param {string} tracerService
37
+ * @param {object} peerData
38
+ * @returns {string}
39
+ */
40
+ #getDbmServiceName (tracerService, peerData) {
59
41
  if (this._tracerConfig.spanComputePeerService) {
60
- const peerData = this.getPeerService(span.context()._tags)
61
42
  return this.getPeerServiceRemap(peerData)[PEER_SERVICE_KEY] || tracerService
62
43
  }
63
44
  return tracerService
64
45
  }
65
46
 
47
+ /**
48
+ * @param {import('../../../..').Span} span
49
+ * @param {string} serviceName
50
+ * @param {boolean} disableFullMode
51
+ */
66
52
  createDbmComment (span, serviceName, disableFullMode = false) {
67
53
  const mode = this.config.dbmPropagationMode
68
- const dbmService = this.getDbmServiceName(span, serviceName)
69
54
 
70
55
  if (mode === 'disabled') {
71
56
  return null
72
57
  }
73
58
 
74
- const servicePropagation = this.createDBMPropagationCommentService(dbmService, span)
59
+ const peerData = this.getPeerService(span.context()._tags)
60
+ const dbmService = this.#getDbmServiceName(serviceName, peerData)
61
+ const servicePropagation = this.#createDBMPropagationCommentService(dbmService, span, peerData)
62
+
63
+ let dbmComment = servicePropagation
64
+
65
+ // Add propagation hash if both process tags and SQL base hash injection are enabled
66
+ if (propagationHash.isEnabled() && this.config['dbm.injectSqlBaseHash']) {
67
+ const hashBase64 = propagationHash.getHashBase64()
68
+ if (hashBase64) {
69
+ dbmComment += `,ddsh='${hashBase64}'`
70
+ // Add hash to span meta as a tag
71
+ span.setTag('_dd.dbm.propagation_hash', hashBase64)
72
+ }
73
+ }
75
74
 
76
75
  if (disableFullMode || mode === 'service') {
77
- return servicePropagation
76
+ return dbmComment
78
77
  } else if (mode === 'full') {
79
78
  span.setTag('_dd.dbm_trace_injected', 'true')
80
79
  span._processor.sample(span)
81
80
  const traceparent = span._spanContext.toTraceparent()
82
- return `${servicePropagation},traceparent='${traceparent}'`
81
+ return `${dbmComment},traceparent='${traceparent}'`
83
82
  }
84
83
  }
85
84
 
85
+ /**
86
+ * @param {import('../../../..').Span} span
87
+ * @param {string} query
88
+ * @param {string} serviceName
89
+ * @param {boolean} disableFullMode
90
+ * @returns {string}
91
+ */
86
92
  injectDbmQuery (span, query, serviceName, disableFullMode = false) {
87
93
  const dbmTraceComment = this.createDbmComment(span, serviceName, disableFullMode)
88
94
 
@@ -95,6 +101,10 @@ class DatabasePlugin extends StoragePlugin {
95
101
  : `/*${dbmTraceComment}*/ ${query}`
96
102
  }
97
103
 
104
+ /**
105
+ * @param {string} query
106
+ * @returns {string}
107
+ */
98
108
  maybeTruncate (query) {
99
109
  const maxLength = typeof this.config.truncate === 'number'
100
110
  ? this.config.truncate
@@ -108,4 +118,6 @@ class DatabasePlugin extends StoragePlugin {
108
118
  }
109
119
  }
110
120
 
121
+ const encode = value => value ? encodeURIComponent(value) : ''
122
+
111
123
  module.exports = DatabasePlugin
@@ -18,6 +18,10 @@ const COMMON_PEER_SVC_SOURCE_TAGS = [
18
18
 
19
19
  // TODO: Exit span on finish when AsyncResource instances are removed.
20
20
  class OutboundPlugin extends TracingPlugin {
21
+ /**
22
+ *
23
+ * @type {string[]}
24
+ */
21
25
  static peerServicePrecursors = []
22
26
 
23
27
  constructor (...args) {
@@ -28,12 +32,15 @@ class OutboundPlugin extends TracingPlugin {
28
32
  })
29
33
  }
30
34
 
35
+ /**
36
+ * @param {{ parentStore?: { span: import('../../../..').Span } }} ctx
37
+ */
31
38
  bindFinish (ctx) {
32
39
  return ctx.parentStore
33
40
  }
34
41
 
35
- startSpan (...args) {
36
- const span = super.startSpan(...args)
42
+ startSpan (name, options, enterOrCtx) {
43
+ const span = super.startSpan(name, options, enterOrCtx)
37
44
  if (
38
45
  this._tracerConfig.codeOriginForSpans.enabled &&
39
46
  this._tracerConfig.codeOriginForSpans.experimental.exit_spans.enabled
@@ -43,6 +50,9 @@ class OutboundPlugin extends TracingPlugin {
43
50
  return span
44
51
  }
45
52
 
53
+ /**
54
+ * @param {Record<string, string>} tags
55
+ */
46
56
  getPeerService (tags) {
47
57
  /**
48
58
  * Compute `peer.service` and associated metadata from available tags, based
@@ -75,6 +85,9 @@ class OutboundPlugin extends TracingPlugin {
75
85
  }
76
86
  }
77
87
 
88
+ /**
89
+ * @param {Record<string, string>} peerData
90
+ */
78
91
  getPeerServiceRemap (peerData) {
79
92
  /**
80
93
  * If DD_TRACE_PEER_SERVICE_MAPPING is matched, we need to override the existing
@@ -92,6 +105,9 @@ class OutboundPlugin extends TracingPlugin {
92
105
  return peerData
93
106
  }
94
107
 
108
+ /**
109
+ * @param {{ currentStore?: { span: import('../../../..').Span } }} ctx
110
+ */
95
111
  finish (ctx) {
96
112
  const span = ctx?.currentStore?.span || this.activeSpan
97
113
  this.tagPeerService(span)
@@ -104,6 +120,9 @@ class OutboundPlugin extends TracingPlugin {
104
120
  super.finish(...arguments)
105
121
  }
106
122
 
123
+ /**
124
+ * @param {import('../../../..').Span} span
125
+ */
107
126
  tagPeerService (span) {
108
127
  if (this._tracerConfig.spanComputePeerService) {
109
128
  const peerData = this.getPeerService(span.context()._tags)
@@ -113,10 +132,16 @@ class OutboundPlugin extends TracingPlugin {
113
132
  }
114
133
  }
115
134
 
135
+ /**
136
+ * @param {object} ctx
137
+ */
116
138
  connect (ctx) {
117
139
  this.addHost(ctx)
118
140
  }
119
141
 
142
+ /**
143
+ * @param {{ hostname: string, port: number, currentStore?: { span: import('../../../..').Span } }} ctx
144
+ */
120
145
  addHost (ctx) {
121
146
  const { hostname, port } = ctx
122
147
 
@@ -16,11 +16,18 @@ class TracingPlugin extends Plugin {
16
16
  }
17
17
 
18
18
  get activeSpan () {
19
- const store = storage('legacy').getStore()
19
+ const store = /** @type {{ span?: import('../../../..').Span }} */ (storage('legacy').getStore())
20
20
 
21
- return store && store.span
21
+ return store?.span
22
22
  }
23
23
 
24
+ /**
25
+ * @param {object} opts
26
+ * @param {string} [opts.type]
27
+ * @param {string} [opts.id]
28
+ * @param {string} [opts.kind]
29
+ * @returns {string}
30
+ */
24
31
  serviceName (opts = {}) {
25
32
  const {
26
33
  type = this.constructor.type,
@@ -31,6 +38,13 @@ class TracingPlugin extends Plugin {
31
38
  return this._tracer._nomenclature.serviceName(type, kind, id, opts)
32
39
  }
33
40
 
41
+ /**
42
+ * @param {object} opts
43
+ * @param {string} [opts.type]
44
+ * @param {string} [opts.id]
45
+ * @param {string} [opts.kind]
46
+ * @returns {string}
47
+ */
34
48
  operationName (opts = {}) {
35
49
  const {
36
50
  type = this.constructor.type,
@@ -41,6 +55,10 @@ class TracingPlugin extends Plugin {
41
55
  return this._tracer._nomenclature.opName(type, kind, id, opts)
42
56
  }
43
57
 
58
+ /**
59
+ * @param {object} config
60
+ * @returns {object}
61
+ */
44
62
  configure (config) {
45
63
  return super.configure({
46
64
  ...config,
@@ -53,11 +71,17 @@ class TracingPlugin extends Plugin {
53
71
 
54
72
  start () {} // implemented by individual plugins
55
73
 
74
+ /**
75
+ * @param {{ currentStore?: { span: import('../../../..').Span } }} ctx
76
+ */
56
77
  finish (ctx) {
57
78
  const span = ctx?.currentStore?.span || this.activeSpan
58
79
  span?.finish()
59
80
  }
60
81
 
82
+ /**
83
+ * @param {{ currentStore?: { span: import('../../../..').Span }, error?: unknown }} ctxOrError
84
+ */
61
85
  error (ctxOrError) {
62
86
  if (ctxOrError?.currentStore) {
63
87
  ctxOrError.currentStore?.span.setTag('error', ctxOrError?.error)
@@ -84,21 +108,32 @@ class TracingPlugin extends Plugin {
84
108
  }
85
109
  }
86
110
 
111
+ /**
112
+ * @param {string} eventName
113
+ * @param {Function} handler
114
+ */
87
115
  addTraceSub (eventName, handler) {
88
116
  const prefix = this.constructor.prefix || `apm:${this.component}:${this.operation}`
89
117
  this.addSub(`${prefix}:${eventName}`, handler)
90
118
  }
91
119
 
120
+ /**
121
+ * @param {string} eventName
122
+ * @param {Function} transform
123
+ */
92
124
  addTraceBind (eventName, transform) {
93
125
  const prefix = this.constructor.prefix || `apm:${this.component}:${this.operation}`
94
126
  this.addBind(`${prefix}:${eventName}`, transform)
95
127
  }
96
128
 
129
+ /**
130
+ * @param {unknown} error
131
+ * @param {import('../../../..').Span} [span]
132
+ */
97
133
  addError (error, span = this.activeSpan) {
98
134
  if (span && !span._spanContext._tags.error) {
99
135
  // Errors may be wrapped in a context.
100
- error = (error && error.error) || error
101
- span.setTag('error', error || 1)
136
+ span.setTag('error', error?.error || error || 1)
102
137
  }
103
138
  }
104
139