dd-trace 5.99.1 → 5.101.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 (101) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/index.d.ts +14 -0
  3. package/package.json +8 -8
  4. package/packages/datadog-instrumentations/src/cucumber.js +69 -5
  5. package/packages/datadog-instrumentations/src/cypress.js +5 -3
  6. package/packages/datadog-instrumentations/src/express.js +3 -2
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  8. package/packages/datadog-instrumentations/src/hono.js +15 -4
  9. package/packages/datadog-instrumentations/src/http/client.js +20 -3
  10. package/packages/datadog-instrumentations/src/jest.js +146 -90
  11. package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
  12. package/packages/datadog-instrumentations/src/mocha/main.js +43 -26
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -4
  15. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
  16. package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
  17. package/packages/datadog-instrumentations/src/playwright.js +108 -18
  18. package/packages/datadog-instrumentations/src/router.js +53 -33
  19. package/packages/datadog-instrumentations/src/vitest.js +76 -30
  20. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  21. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  22. package/packages/datadog-plugin-bullmq/src/consumer.js +5 -4
  23. package/packages/datadog-plugin-bullmq/src/producer.js +37 -29
  24. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +49 -9
  25. package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
  26. package/packages/datadog-plugin-cypress/src/support.js +22 -21
  27. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  28. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  29. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
  30. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
  31. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
  32. package/packages/datadog-plugin-playwright/src/index.js +6 -0
  33. package/packages/datadog-plugin-router/src/index.js +13 -0
  34. package/packages/dd-trace/index.js +4 -3
  35. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  36. package/packages/dd-trace/src/appsec/reporter.js +4 -1
  37. package/packages/dd-trace/src/baggage.js +10 -0
  38. package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
  39. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
  40. package/packages/dd-trace/src/config/config-types.d.ts +0 -2
  41. package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
  42. package/packages/dd-trace/src/config/index.js +7 -60
  43. package/packages/dd-trace/src/config/normalize-service.js +31 -0
  44. package/packages/dd-trace/src/config/supported-configurations.json +15 -32
  45. package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
  46. package/packages/dd-trace/src/datastreams/encoding.js +39 -28
  47. package/packages/dd-trace/src/datastreams/pathway.js +29 -26
  48. package/packages/dd-trace/src/datastreams/processor.js +17 -15
  49. package/packages/dd-trace/src/datastreams/size.js +6 -2
  50. package/packages/dd-trace/src/debugger/config.js +6 -3
  51. package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
  52. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  53. package/packages/dd-trace/src/dogstatsd.js +10 -7
  54. package/packages/dd-trace/src/encode/0.4.js +3 -3
  55. package/packages/dd-trace/src/encode/0.5.js +2 -2
  56. package/packages/dd-trace/src/encode/agentless-json.js +2 -2
  57. package/packages/dd-trace/src/encode/tags-processors.js +2 -27
  58. package/packages/dd-trace/src/exporters/common/request.js +22 -11
  59. package/packages/dd-trace/src/exporters/common/retry.js +104 -0
  60. package/packages/dd-trace/src/git_metadata.js +66 -0
  61. package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
  62. package/packages/dd-trace/src/heap_snapshots.js +4 -4
  63. package/packages/dd-trace/src/id.js +15 -26
  64. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  65. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
  66. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
  67. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
  68. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
  69. package/packages/dd-trace/src/llmobs/sdk.js +5 -1
  70. package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
  71. package/packages/dd-trace/src/llmobs/tagger.js +42 -0
  72. package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
  73. package/packages/dd-trace/src/llmobs/util.js +80 -5
  74. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
  75. package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
  76. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
  77. package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -10
  78. package/packages/dd-trace/src/opentelemetry/span-helpers.js +308 -0
  79. package/packages/dd-trace/src/opentelemetry/span.js +42 -108
  80. package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
  81. package/packages/dd-trace/src/opentracing/propagation/text_map.js +95 -36
  82. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +98 -32
  83. package/packages/dd-trace/src/opentracing/span.js +58 -49
  84. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  85. package/packages/dd-trace/src/plugins/util/ci.js +119 -32
  86. package/packages/dd-trace/src/plugins/util/test.js +293 -27
  87. package/packages/dd-trace/src/priority_sampler.js +6 -4
  88. package/packages/dd-trace/src/profiling/config.js +5 -4
  89. package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
  90. package/packages/dd-trace/src/propagation-hash/index.js +1 -1
  91. package/packages/dd-trace/src/proxy.js +3 -3
  92. package/packages/dd-trace/src/remote_config/index.js +5 -3
  93. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  94. package/packages/dd-trace/src/span_format.js +52 -5
  95. package/packages/dd-trace/src/span_processor.js +1 -5
  96. package/packages/dd-trace/src/spanleak.js +0 -1
  97. package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
  98. package/packages/dd-trace/src/tracer_metadata.js +1 -1
  99. package/packages/dd-trace/src/util.js +17 -0
  100. package/vendor/dist/path-to-regexp/LICENSE +0 -21
  101. package/vendor/dist/path-to-regexp/index.js +0 -1
@@ -1,14 +1,25 @@
1
1
  'use strict'
2
2
 
3
+ const log = require('../log')
3
4
  const { SPAN_KINDS } = require('./constants/tags')
4
5
 
6
+ // LLM I/O is overwhelmingly ASCII (English prompts and code). Walk once
7
+ // looking for the first non-ASCII char; if there is none, hand the input
8
+ // straight back. Otherwise pick up the slow path from the byte that needed
9
+ // escaping. ~5x faster on typical prompt strings than the per-char `+=`
10
+ // loop the function used to do unconditionally.
5
11
  function encodeUnicode (str = '') {
6
- let result = ''
7
- for (let i = 0; i < str.length; i++) {
8
- const code = str.charCodeAt(i)
9
- result += code > 127 ? String.raw`\u${code.toString(16).padStart(4, '0')}` : str[i]
12
+ for (let index = 0; index < str.length; index++) {
13
+ if (str.charCodeAt(index) > 127) {
14
+ let result = str.slice(0, index)
15
+ for (; index < str.length; index++) {
16
+ const code = str.charCodeAt(index)
17
+ result += code > 127 ? String.raw`\u${code.toString(16).padStart(4, '0')}` : str[index]
18
+ }
19
+ return result
20
+ }
10
21
  }
11
- return result
22
+ return str
12
23
  }
13
24
 
14
25
  function validateKind (kind) {
@@ -22,6 +33,57 @@ function validateKind (kind) {
22
33
  return kind
23
34
  }
24
35
 
36
+ /**
37
+ * Validates cost tag keys and records telemetry for the annotation source.
38
+ * @param {import('../opentracing/span')} span
39
+ * @param {unknown} costTags
40
+ * @param {string} source
41
+ * @param {Record<string, unknown>} spanTags
42
+ * @returns {string[]}
43
+ */
44
+ function validateCostTags (span, costTags, source, spanTags) {
45
+ // Lazy-required to avoid the `index.js -> telemetry -> tagger -> util` module cycle.
46
+ const telemetry = require('./telemetry')
47
+
48
+ telemetry.recordCostTagsAnnotated(span, source)
49
+
50
+ if (!Array.isArray(costTags)) {
51
+ log.warn('costTags must be an array of strings. Ignoring value.')
52
+ telemetry.recordCostTagsSubmitted(span, 1, source, 'error', 'non_list')
53
+ return []
54
+ }
55
+
56
+ const validatedCostTags = new Set()
57
+ let nonStringEntries = 0
58
+ let missingSpanTags = 0
59
+
60
+ for (const costTag of costTags) {
61
+ if (typeof costTag !== 'string') {
62
+ log.warn('costTags entries must be strings. Skipping entry %s.', costTag)
63
+ nonStringEntries++
64
+ continue
65
+ }
66
+ if (!Object.hasOwn(spanTags, costTag)) {
67
+ log.warn('costTags entry "%s" must reference a key present in span tags. Skipping entry.', costTag)
68
+ missingSpanTags++
69
+ continue
70
+ }
71
+ validatedCostTags.add(costTag)
72
+ }
73
+
74
+ if (nonStringEntries) {
75
+ telemetry.recordCostTagsSubmitted(span, nonStringEntries, source, 'error', 'non_string_entry')
76
+ }
77
+ if (missingSpanTags) {
78
+ telemetry.recordCostTagsSubmitted(span, missingSpanTags, source, 'error', 'missing_span_tag')
79
+ }
80
+ if (validatedCostTags.size) {
81
+ telemetry.recordCostTagsSubmitted(span, validatedCostTags.size, source, 'success')
82
+ }
83
+
84
+ return [...validatedCostTags]
85
+ }
86
+
25
87
  // extracts the argument names from a function string
26
88
  function parseArgumentNames (str) {
27
89
  const result = []
@@ -174,9 +236,22 @@ function spanHasError (span) {
174
236
  return !!(tags.error || tags['error.type'])
175
237
  }
176
238
 
239
+ // LLM SDKs stream tool-call argument JSON across SSE chunks; a malformed
240
+ // accumulation would otherwise throw straight into the chunk subscriber.
241
+ function safeJsonParse (value, fallback) {
242
+ if (typeof value !== 'string') return value
243
+ try {
244
+ return JSON.parse(value)
245
+ } catch {
246
+ return fallback === undefined ? value : fallback
247
+ }
248
+ }
249
+
177
250
  module.exports = {
178
251
  encodeUnicode,
252
+ validateCostTags,
179
253
  validateKind,
180
254
  getFunctionArguments,
255
+ safeJsonParse,
181
256
  spanHasError,
182
257
  }
@@ -30,7 +30,7 @@ const COUNTER_UNIT = '{evaluation}'
30
30
  * If counter creation fails (e.g. the OTel API is not yet available), the call
31
31
  * is silently skipped and retried on the next `finally()` invocation.
32
32
  *
33
- * When `config.otelMetricsEnabled` is false, `finally()` is always a no-op.
33
+ * When `config.DD_METRICS_OTEL_ENABLED` is false, `finally()` is always a no-op.
34
34
  */
35
35
  class EvalMetricsHook {
36
36
  #enabled = false
@@ -40,7 +40,7 @@ class EvalMetricsHook {
40
40
  * @param {import('../config')} config - Tracer configuration object
41
41
  */
42
42
  constructor (config) {
43
- this.#enabled = config.otelMetricsEnabled === true
43
+ this.#enabled = config.DD_METRICS_OTEL_ENABLED === true
44
44
  }
45
45
 
46
46
  /**
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ const BridgeSpanBase = require('./bridge-span-base')
4
+ const { setOtelResource } = require('./span-helpers')
5
+
6
+ /**
7
+ * OTel `Span`-compatible proxy around an already-active Datadog span.
8
+ *
9
+ * Makes `trace.getActiveSpan()` forward attribute/link/event/status/exception writes onto
10
+ * the Datadog span. `end()` is intentionally a no-op: the span's lifecycle belongs to
11
+ * whoever created it. Mutation methods all bail out once the underlying Datadog span has
12
+ * finished (gated inside the helpers), matching OTel `Span` semantics.
13
+ */
14
+ class ActiveSpanProxy extends BridgeSpanBase {
15
+ /** @type {import('./span_context')} */
16
+ #otelSpanContext
17
+
18
+ /**
19
+ * @param {import('../opentracing/span')} ddSpan
20
+ * @param {import('./span_context')} otelSpanContext
21
+ */
22
+ constructor (ddSpan, otelSpanContext) {
23
+ super(ddSpan)
24
+ this.#otelSpanContext = otelSpanContext
25
+ }
26
+
27
+ spanContext () {
28
+ return this.#otelSpanContext
29
+ }
30
+
31
+ /**
32
+ * @param {string} name
33
+ */
34
+ updateName (name) {
35
+ setOtelResource(this._ddSpan, name)
36
+ return this
37
+ }
38
+
39
+ end () {}
40
+ }
41
+
42
+ module.exports = ActiveSpanProxy
@@ -0,0 +1,106 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ addOtelEvent,
5
+ addOtelLink,
6
+ addOtelLinks,
7
+ applyOtelStatus,
8
+ recordException,
9
+ setOtelAttribute,
10
+ setOtelAttributes,
11
+ } = require('./span-helpers')
12
+
13
+ /**
14
+ * Shared base for the OTel-bridge span classes (`Span` and `ActiveSpanProxy`). Subclasses
15
+ * pass the underlying Datadog span to `super(ddSpan)` and provide `spanContext()`, `end()`,
16
+ * and `updateName()`. The writable-span gate lives in the helpers in `span-helpers.js`,
17
+ * so neither bridge can drift from it.
18
+ *
19
+ * `_ddSpan` is left as a `_underscore` field rather than `#private` so the bridge does not
20
+ * expand its published API to expose the underlying DD span. External callers that need
21
+ * the reference (`ContextManager` proxy-cache check, OTLP serialization, tests) reach in
22
+ * via `_ddSpan`, matching the existing convention for "internal, may break".
23
+ */
24
+ class BridgeSpanBase {
25
+ // OTel SpanStatusCode: 0 = UNSET, 1 = OK, 2 = ERROR. Tracked for OK-is-final precedence.
26
+ #statusCode = 0
27
+
28
+ /**
29
+ * @param {import('../opentracing/span')} ddSpan
30
+ */
31
+ constructor (ddSpan) {
32
+ this._ddSpan = ddSpan
33
+ }
34
+
35
+ get ended () {
36
+ return this._ddSpan._duration !== undefined
37
+ }
38
+
39
+ isRecording () {
40
+ return !this.ended
41
+ }
42
+
43
+ /**
44
+ * @param {string} key
45
+ * @param {import('@opentelemetry/api').AttributeValue} value
46
+ */
47
+ setAttribute (key, value) {
48
+ setOtelAttribute(this._ddSpan, key, value)
49
+ return this
50
+ }
51
+
52
+ /**
53
+ * @param {import('@opentelemetry/api').Attributes} attributes
54
+ */
55
+ setAttributes (attributes) {
56
+ setOtelAttributes(this._ddSpan, attributes)
57
+ return this
58
+ }
59
+
60
+ /**
61
+ * @param {string} name
62
+ * @param {import('@opentelemetry/api').Attributes | import('@opentelemetry/api').TimeInput} [attributesOrStartTime]
63
+ * @param {import('@opentelemetry/api').TimeInput} [startTime]
64
+ */
65
+ addEvent (name, attributesOrStartTime, startTime) {
66
+ addOtelEvent(this._ddSpan, name, attributesOrStartTime, startTime)
67
+ return this
68
+ }
69
+
70
+ /**
71
+ * Accepts the OTel `Link` shape and the deprecated `(SpanContext, Attributes)` form.
72
+ *
73
+ * @param {import('@opentelemetry/api').Link | import('@opentelemetry/api').SpanContext} link
74
+ * @param {import('@opentelemetry/api').Attributes} [attrs]
75
+ */
76
+ addLink (link, attrs) {
77
+ addOtelLink(this._ddSpan, link, attrs)
78
+ return this
79
+ }
80
+
81
+ /**
82
+ * @param {import('@opentelemetry/api').Link[]} links
83
+ */
84
+ addLinks (links) {
85
+ addOtelLinks(this._ddSpan, links)
86
+ return this
87
+ }
88
+
89
+ /**
90
+ * @param {import('@opentelemetry/api').Exception} exception
91
+ * @param {import('@opentelemetry/api').TimeInput} [timeInput]
92
+ */
93
+ recordException (exception, timeInput) {
94
+ recordException(this._ddSpan, exception, timeInput)
95
+ }
96
+
97
+ /**
98
+ * @param {import('@opentelemetry/api').SpanStatus} status
99
+ */
100
+ setStatus (status) {
101
+ this.#statusCode = applyOtelStatus(this._ddSpan, this.#statusCode, status)
102
+ return this
103
+ }
104
+ }
105
+
106
+ module.exports = BridgeSpanBase
@@ -2,8 +2,9 @@
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
+ const { getAllBaggageItems, setAllBaggageItems, removeAllBaggageItems } = require('../baggage')
6
6
 
7
+ const ActiveSpanProxy = require('./active-span-proxy')
7
8
  const SpanContext = require('./span_context')
8
9
 
9
10
  class ContextManager {
@@ -48,11 +49,19 @@ class ContextManager {
48
49
  ddContext._otelSpanContext = new SpanContext(ddContext)
49
50
  }
50
51
 
51
- if (store && trace.getSpanContext(store) === ddContext._otelSpanContext) {
52
+ // Cache the active-span proxy next to the bridge span context. This lets
53
+ // `trace.getActiveSpan()` forward attribute/status/link/exception writes
54
+ // onto the active Datadog span rather than returning a NonRecordingSpan
55
+ // whose mutation methods are silent no-ops.
56
+ if (!ddContext._otelActiveSpan) {
57
+ ddContext._otelActiveSpan = new ActiveSpanProxy(activeSpan, ddContext._otelSpanContext)
58
+ }
59
+
60
+ if (store && trace.getSpan(store) === ddContext._otelActiveSpan) {
52
61
  return otelBaggages ? propagation.setBaggage(store, otelBaggages) : store
53
62
  }
54
63
 
55
- const wrappedContext = trace.setSpanContext(baseContext, ddContext._otelSpanContext)
64
+ const wrappedContext = trace.setSpan(baseContext, ddContext._otelActiveSpan)
56
65
  return otelBaggages ? propagation.setBaggage(wrappedContext, otelBaggages) : wrappedContext
57
66
  }
58
67
 
@@ -64,13 +73,16 @@ class ContextManager {
64
73
  return this._store.run(context, cb, ...args)
65
74
  }
66
75
  const baggages = propagation.getBaggage(context)
67
- let baggageItems = []
68
- if (baggages) {
69
- baggageItems = baggages.getAllEntries()
70
- }
71
- removeAllBaggageItems()
72
- for (const baggage of baggageItems) {
73
- setBaggageItem(baggage[0], baggage[1].value)
76
+ const baggageItems = baggages ? baggages.getAllEntries() : []
77
+ if (baggageItems.length > 0) {
78
+ /** @type {Record<string, string>} */
79
+ const items = {}
80
+ for (const [key, entry] of baggageItems) {
81
+ items[key] = entry.value
82
+ }
83
+ setAllBaggageItems(items)
84
+ } else {
85
+ removeAllBaggageItems()
74
86
  }
75
87
  if (span && span._ddSpan) {
76
88
  const ddSpan = span._ddSpan
@@ -0,0 +1,308 @@
1
+ 'use strict'
2
+
3
+ const { performance } = require('node:perf_hooks')
4
+
5
+ const { timeInputToHrTime } = require('../../../../vendor/dist/@opentelemetry/core')
6
+
7
+ const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE, IGNORE_OTEL_ERROR } = require('../constants')
8
+ const DatadogSpanContext = require('../opentracing/span_context')
9
+ const TraceState = require('../opentracing/propagation/tracestate')
10
+
11
+ const id = require('../id')
12
+
13
+ const { timeOrigin } = performance
14
+
15
+ /**
16
+ * @typedef {{
17
+ * _ddContext?: import('../opentracing/span_context'),
18
+ * toTraceId?: (get128?: boolean) => string,
19
+ * toSpanId?: (get128?: boolean) => string,
20
+ * traceId?: string,
21
+ * spanId?: string,
22
+ * traceFlags?: number,
23
+ * traceState?: { serialize: () => string }
24
+ * }} LinkContextLike
25
+ * @typedef {{ context: LinkContextLike, attributes?: Record<string, unknown> }} OtelLink
26
+ * @typedef {{
27
+ * name?: string,
28
+ * message?: string,
29
+ * stack?: string,
30
+ * type?: string,
31
+ * escaped?: unknown
32
+ * }} ExceptionLike
33
+ * @typedef {number | Date | [number, number]} TimeInput
34
+ */
35
+
36
+ /**
37
+ * @param {import('../opentracing/span')} ddSpan
38
+ */
39
+ function isWritable (ddSpan) {
40
+ return ddSpan._duration === undefined
41
+ }
42
+
43
+ /**
44
+ * @param {unknown} value
45
+ * @returns {value is TimeInput}
46
+ */
47
+ function isTimeInput (value) {
48
+ return typeof value === 'number' ||
49
+ value instanceof Date ||
50
+ (Array.isArray(value) && value.length === 2 &&
51
+ typeof value[0] === 'number' && typeof value[1] === 'number')
52
+ }
53
+
54
+ /**
55
+ * @param {LinkContextLike} ctx
56
+ * @returns {ctx is import('../opentracing/span_context')}
57
+ */
58
+ function isDatadogSpanContext (ctx) {
59
+ return typeof ctx.toTraceId === 'function' && typeof ctx.toSpanId === 'function'
60
+ }
61
+
62
+ /**
63
+ * @param {unknown} link
64
+ * @returns {link is OtelLink}
65
+ */
66
+ function isOtelLink (link) {
67
+ return typeof link === 'object' && link !== null && 'context' in link
68
+ }
69
+
70
+ /**
71
+ * The OTel-shipped `hrTimeToMilliseconds` rounds, which drops sub-millisecond precision.
72
+ *
73
+ * @param {TimeInput} [timeInput]
74
+ */
75
+ function timeInputToMilliseconds (timeInput) {
76
+ const hrTime = timeInputToHrTime(timeInput || (performance.now() + timeOrigin))
77
+ return hrTime[0] * 1e3 + hrTime[1] / 1e6
78
+ }
79
+
80
+ /**
81
+ * Resolves the OTel `addEvent(name, attributesOrStartTime, startTime)` overloads into a
82
+ * `{ attributes, startTime }` pair where `startTime` is always a millisecond number, so the
83
+ * caller can hand the OTel inputs straight to `DatadogSpan.addEvent` without `Date`/hrTime
84
+ * confusion.
85
+ *
86
+ * @param {Record<string, unknown> | TimeInput | undefined} attributesOrStartTime
87
+ * @param {TimeInput} [startTime]
88
+ */
89
+ function normalizeOtelEvent (attributesOrStartTime, startTime) {
90
+ let attributes
91
+ if (attributesOrStartTime) {
92
+ if (isTimeInput(attributesOrStartTime)) {
93
+ startTime = attributesOrStartTime
94
+ } else if (typeof attributesOrStartTime === 'object') {
95
+ attributes = attributesOrStartTime
96
+ }
97
+ }
98
+ return { attributes, startTime: timeInputToMilliseconds(startTime) }
99
+ }
100
+
101
+ /**
102
+ * Accepts the native `DatadogSpanContext` (`toTraceId`/`toSpanId`), the bridge wrapper
103
+ * (`_ddContext`), or a standard OTel `SpanContext` (`traceId`/`spanId` strings); returns
104
+ * a `DatadogSpanContext` or `undefined` when nothing usable is present.
105
+ *
106
+ * @param {LinkContextLike | undefined | null} context
107
+ */
108
+ function normalizeLinkContext (context) {
109
+ if (!context) return
110
+
111
+ if (context._ddContext) return context._ddContext
112
+
113
+ if (isDatadogSpanContext(context)) return context
114
+
115
+ if (typeof context.traceId !== 'string' || typeof context.spanId !== 'string') return
116
+
117
+ let sampling
118
+ if (typeof context.traceFlags === 'number') {
119
+ sampling = { priority: context.traceFlags & 1 }
120
+ }
121
+
122
+ let tracestate
123
+ if (context.traceState?.serialize) {
124
+ tracestate = TraceState.fromString(context.traceState.serialize())
125
+ }
126
+
127
+ return new DatadogSpanContext({
128
+ traceId: id(context.traceId, 16),
129
+ spanId: id(context.spanId, 16),
130
+ sampling,
131
+ tracestate,
132
+ })
133
+ }
134
+
135
+ /**
136
+ * Mirrors `http.response.status_code` onto `http.status_code` (DD's special tag used by APM
137
+ * trace metrics and client-side stats); both names end up on the span.
138
+ *
139
+ * @param {import('../opentracing/span')} ddSpan
140
+ * @param {string} key
141
+ * @param {unknown} value
142
+ */
143
+ function setOtelAttribute (ddSpan, key, value) {
144
+ if (!isWritable(ddSpan)) return
145
+
146
+ if (key === 'http.response.status_code') {
147
+ ddSpan.setTag('http.status_code', String(value))
148
+ }
149
+
150
+ ddSpan.setTag(key, value)
151
+ }
152
+
153
+ /**
154
+ * Same `http.status_code` mirror as `setOtelAttribute`; does not mutate the caller's
155
+ * `attributes` object.
156
+ *
157
+ * @param {import('../opentracing/span')} ddSpan
158
+ * @param {Record<string, unknown>} attributes
159
+ */
160
+ function setOtelAttributes (ddSpan, attributes) {
161
+ if (!isWritable(ddSpan)) return
162
+
163
+ ddSpan.addTags(attributes)
164
+ if ('http.response.status_code' in attributes) {
165
+ ddSpan.setTag('http.status_code', String(attributes['http.response.status_code']))
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Accepts both `{ context, attributes }` and the deprecated `(context, attrs)` form.
171
+ *
172
+ * @param {import('../opentracing/span')} ddSpan
173
+ * @param {LinkContextLike | OtelLink} link
174
+ * @param {Record<string, unknown>} [attrs]
175
+ */
176
+ function addOtelLink (ddSpan, link, attrs) {
177
+ if (!isWritable(ddSpan) || !link) return
178
+
179
+ // TODO: Drop the (context, attrs) form in v6.0.0.
180
+ const { context, attributes } = isOtelLink(link)
181
+ ? link
182
+ : { context: link, attributes: attrs ?? {} }
183
+
184
+ const ddSpanContext = normalizeLinkContext(context)
185
+ if (!ddSpanContext) return
186
+
187
+ ddSpan.addLink({ context: ddSpanContext, attributes })
188
+ }
189
+
190
+ /**
191
+ * Forwards the array-form `addLinks` overload; non-array inputs are silently ignored to
192
+ * match the OTel API's lenient handling.
193
+ *
194
+ * @param {import('../opentracing/span')} ddSpan
195
+ * @param {Array<LinkContextLike | OtelLink>} links
196
+ */
197
+ function addOtelLinks (ddSpan, links) {
198
+ if (!isWritable(ddSpan) || !Array.isArray(links)) return
199
+
200
+ for (const link of links) {
201
+ addOtelLink(ddSpan, link)
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Owns the OTel `addEvent(name, attributesOrStartTime, startTime)` overload normalization so
207
+ * the bridge classes can delegate without touching `Date`/hrTime conversion.
208
+ *
209
+ * @param {import('../opentracing/span')} ddSpan
210
+ * @param {string} name
211
+ * @param {Record<string, unknown> | TimeInput | undefined} [attributesOrStartTime]
212
+ * @param {TimeInput} [startTime]
213
+ */
214
+ function addOtelEvent (ddSpan, name, attributesOrStartTime, startTime) {
215
+ if (!isWritable(ddSpan)) return
216
+
217
+ const event = normalizeOtelEvent(attributesOrStartTime, startTime)
218
+ ddSpan.addEvent(name, event.attributes, event.startTime)
219
+ }
220
+
221
+ /**
222
+ * @param {import('../opentracing/span')} ddSpan
223
+ * @param {ExceptionLike} exception
224
+ * @param {TimeInput} [timeInput]
225
+ */
226
+ function recordException (ddSpan, exception, timeInput) {
227
+ if (!isWritable(ddSpan)) return
228
+
229
+ ddSpan.addTags({
230
+ [ERROR_TYPE]: exception.name,
231
+ [ERROR_MESSAGE]: exception.message,
232
+ [ERROR_STACK]: exception.stack,
233
+ [IGNORE_OTEL_ERROR]: ddSpan.context()._tags[IGNORE_OTEL_ERROR] ?? true,
234
+ })
235
+
236
+ const attributes = {}
237
+ if (exception.message) attributes['exception.message'] = exception.message
238
+ if (exception.type) attributes['exception.type'] = exception.type
239
+ if (exception.escaped) attributes['exception.escaped'] = exception.escaped
240
+ if (exception.stack) attributes['exception.stacktrace'] = exception.stack
241
+
242
+ ddSpan.addEvent(exception.name ?? 'Error', attributes, timeInputToMilliseconds(timeInput))
243
+ }
244
+
245
+ /**
246
+ * Applies OTel `setStatus({ code, message })` per spec: UNSET / missing is a no-op, OK is
247
+ * final, ERROR is replaceable. Only ERROR writes tags; the returned code is the one the
248
+ * caller must store for the next call.
249
+ *
250
+ * @param {import('../opentracing/span')} ddSpan
251
+ * @param {number} currentCode 0 = UNSET, 1 = OK, 2 = ERROR.
252
+ * @param {{ code?: number, message?: string }} [status]
253
+ * @returns {number} The new status code to track on the caller.
254
+ */
255
+ function applyOtelStatus (ddSpan, currentCode, status) {
256
+ if (!isWritable(ddSpan)) return currentCode
257
+
258
+ const code = status?.code
259
+ if (!code || currentCode === 1) return currentCode
260
+
261
+ if (code === 2) {
262
+ ddSpan.addTags({
263
+ [ERROR_MESSAGE]: status.message,
264
+ [IGNORE_OTEL_ERROR]: false,
265
+ })
266
+ }
267
+
268
+ return code
269
+ }
270
+
271
+ /**
272
+ * OTel `updateName` for OTel-created bridge spans: writes the DD operation name, matching
273
+ * the OTel SDK semantic that `updateName` updates the canonical span identifier.
274
+ *
275
+ * @param {import('../opentracing/span')} ddSpan
276
+ * @param {string} name
277
+ */
278
+ function setOtelOperationName (ddSpan, name) {
279
+ if (!isWritable(ddSpan)) return
280
+
281
+ ddSpan.setOperationName(name)
282
+ }
283
+
284
+ /**
285
+ * OTel `updateName` for DD-native spans the bridge did not create: writes `resource.name`
286
+ * so the operation name (and the backend metric aggregation it drives) stays stable.
287
+ *
288
+ * @param {import('../opentracing/span')} ddSpan
289
+ * @param {string} name
290
+ */
291
+ function setOtelResource (ddSpan, name) {
292
+ if (!isWritable(ddSpan)) return
293
+
294
+ ddSpan.setTag('resource.name', name)
295
+ }
296
+
297
+ module.exports = {
298
+ addOtelEvent,
299
+ addOtelLink,
300
+ addOtelLinks,
301
+ applyOtelStatus,
302
+ normalizeLinkContext,
303
+ recordException,
304
+ setOtelAttribute,
305
+ setOtelAttributes,
306
+ setOtelOperationName,
307
+ setOtelResource,
308
+ }