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.
- package/LICENSE-3rdparty.csv +0 -1
- package/index.d.ts +14 -0
- package/package.json +8 -8
- package/packages/datadog-instrumentations/src/cucumber.js +69 -5
- package/packages/datadog-instrumentations/src/cypress.js +5 -3
- package/packages/datadog-instrumentations/src/express.js +3 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/hono.js +15 -4
- package/packages/datadog-instrumentations/src/http/client.js +20 -3
- package/packages/datadog-instrumentations/src/jest.js +146 -90
- package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +43 -26
- package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -4
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
- package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
- package/packages/datadog-instrumentations/src/playwright.js +108 -18
- package/packages/datadog-instrumentations/src/router.js +53 -33
- package/packages/datadog-instrumentations/src/vitest.js +76 -30
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
- package/packages/datadog-plugin-bullmq/src/consumer.js +5 -4
- package/packages/datadog-plugin-bullmq/src/producer.js +37 -29
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +49 -9
- package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
- package/packages/datadog-plugin-cypress/src/support.js +22 -21
- package/packages/datadog-plugin-grpc/src/client.js +1 -1
- package/packages/datadog-plugin-grpc/src/server.js +1 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
- package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
- package/packages/datadog-plugin-playwright/src/index.js +6 -0
- package/packages/datadog-plugin-router/src/index.js +13 -0
- package/packages/dd-trace/index.js +4 -3
- package/packages/dd-trace/src/aiguard/sdk.js +2 -2
- package/packages/dd-trace/src/appsec/reporter.js +4 -1
- package/packages/dd-trace/src/baggage.js +10 -0
- package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
- package/packages/dd-trace/src/config/config-types.d.ts +0 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
- package/packages/dd-trace/src/config/index.js +7 -60
- package/packages/dd-trace/src/config/normalize-service.js +31 -0
- package/packages/dd-trace/src/config/supported-configurations.json +15 -32
- package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
- package/packages/dd-trace/src/datastreams/encoding.js +39 -28
- package/packages/dd-trace/src/datastreams/pathway.js +29 -26
- package/packages/dd-trace/src/datastreams/processor.js +17 -15
- package/packages/dd-trace/src/datastreams/size.js +6 -2
- package/packages/dd-trace/src/debugger/config.js +6 -3
- package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
- package/packages/dd-trace/src/dogstatsd.js +10 -7
- package/packages/dd-trace/src/encode/0.4.js +3 -3
- package/packages/dd-trace/src/encode/0.5.js +2 -2
- package/packages/dd-trace/src/encode/agentless-json.js +2 -2
- package/packages/dd-trace/src/encode/tags-processors.js +2 -27
- package/packages/dd-trace/src/exporters/common/request.js +22 -11
- package/packages/dd-trace/src/exporters/common/retry.js +104 -0
- package/packages/dd-trace/src/git_metadata.js +66 -0
- package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
- package/packages/dd-trace/src/heap_snapshots.js +4 -4
- package/packages/dd-trace/src/id.js +15 -26
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
- package/packages/dd-trace/src/llmobs/sdk.js +5 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
- package/packages/dd-trace/src/llmobs/tagger.js +42 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
- package/packages/dd-trace/src/llmobs/util.js +80 -5
- package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
- package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -10
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +308 -0
- package/packages/dd-trace/src/opentelemetry/span.js +42 -108
- package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +95 -36
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +98 -32
- package/packages/dd-trace/src/opentracing/span.js +58 -49
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +119 -32
- package/packages/dd-trace/src/plugins/util/test.js +293 -27
- package/packages/dd-trace/src/priority_sampler.js +6 -4
- package/packages/dd-trace/src/profiling/config.js +5 -4
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
- package/packages/dd-trace/src/propagation-hash/index.js +1 -1
- package/packages/dd-trace/src/proxy.js +3 -3
- package/packages/dd-trace/src/remote_config/index.js +5 -3
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
- package/packages/dd-trace/src/span_format.js +52 -5
- package/packages/dd-trace/src/span_processor.js +1 -5
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
- package/packages/dd-trace/src/tracer_metadata.js +1 -1
- package/packages/dd-trace/src/util.js +17 -0
- package/vendor/dist/path-to-regexp/LICENSE +0 -21
- 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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
+
}
|