dd-trace 6.14.0 → 6.16.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/README.md +15 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +4 -2
- package/ci/test-optimization-validation/package-check.js +4 -1
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +1 -1
- package/index.d.ts +12 -0
- package/next.d.ts +27 -0
- package/next.js +3 -0
- package/package.json +17 -13
- package/packages/datadog-esbuild/index.js +14 -1
- package/packages/datadog-esbuild/src/resolver.js +284 -0
- package/packages/datadog-esbuild/src/utils.js +163 -119
- package/packages/datadog-instrumentations/src/ai.js +39 -97
- package/packages/datadog-instrumentations/src/anthropic.js +76 -134
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +1 -1
- package/packages/datadog-instrumentations/src/azure-cosmos.js +1 -1
- package/packages/datadog-instrumentations/src/azure-durable-functions.js +23 -9
- package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
- package/packages/datadog-instrumentations/src/bullmq.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/dd-trace-api.js +2 -2
- package/packages/datadog-instrumentations/src/graphql.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +39 -14
- package/packages/datadog-instrumentations/src/helpers/instrument.js +25 -8
- package/packages/datadog-instrumentations/src/helpers/instrumentation-utils.js +72 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -64
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +46 -14
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-durable-functions.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +1 -5
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +6 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +99 -70
- package/packages/datadog-instrumentations/src/jest.js +141 -25
- package/packages/datadog-instrumentations/src/langchain.js +1 -1
- package/packages/datadog-instrumentations/src/langgraph.js +1 -1
- package/packages/datadog-instrumentations/src/mercurius.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +41 -5
- package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/openai-agents.js +1 -1
- package/packages/datadog-instrumentations/src/openai.js +54 -76
- package/packages/datadog-instrumentations/src/playwright.js +4 -0
- package/packages/datadog-instrumentations/src/vitest-worker.js +7 -10
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +7 -1
- package/packages/datadog-plugin-azure-cosmos/src/index.js +1 -1
- package/packages/datadog-plugin-azure-durable-functions/src/index.js +96 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -24
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -36
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-request-context.js +31 -0
- package/packages/datadog-plugin-graphql/src/resolve-error.js +33 -11
- package/packages/datadog-turbopack/index.js +212 -0
- package/packages/datadog-turbopack/src/constants.js +5 -0
- package/packages/datadog-turbopack/src/loader.js +500 -0
- package/packages/dd-trace/src/aiguard/index.js +2 -1
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +53 -25
- package/packages/dd-trace/src/aiguard/integrations/evaluate.js +49 -25
- package/packages/dd-trace/src/aiguard/integrations/index.js +3 -2
- package/packages/dd-trace/src/aiguard/integrations/openai.js +59 -30
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +116 -42
- package/packages/dd-trace/src/aiguard/messages/utils.js +29 -1
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +25 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -70
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +2 -2
- package/packages/dd-trace/src/appsec/telemetry/api_security.js +1 -3
- package/packages/dd-trace/src/appsec/telemetry/common.js +4 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +23 -5
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +1 -20
- package/packages/dd-trace/src/appsec/telemetry/user.js +1 -3
- package/packages/dd-trace/src/appsec/telemetry/waf.js +1 -20
- package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
- package/packages/dd-trace/src/ci-visibility/requests/video-request.js +4 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/helper.js +10 -2
- package/packages/dd-trace/src/config/index.js +20 -5
- package/packages/dd-trace/src/config/supported-configurations.json +10 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +14 -1
- package/packages/dd-trace/src/debugger/devtools_client/request-options.js +1 -6
- package/packages/dd-trace/src/evp_proxy/direct.js +2 -28
- package/packages/dd-trace/src/evp_proxy/discovery.js +3 -4
- package/packages/dd-trace/src/evp_proxy/path.js +12 -3
- package/packages/dd-trace/src/exporters/agentless/writer.js +3 -1
- package/packages/dd-trace/src/exporters/common/docker.js +4 -1
- package/packages/dd-trace/src/exporters/common/proxy.js +55 -0
- package/packages/dd-trace/src/exporters/common/request.js +11 -3
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +5 -4
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +106 -25
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +12 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +8 -2
- package/packages/dd-trace/src/plugin_manager.js +9 -3
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/test.js +4 -1
- package/packages/dd-trace/src/priority_sampler.js +4 -0
- package/packages/dd-trace/src/sampling_rule.js +3 -1
- package/packages/dd-trace/src/span_processor.js +18 -3
- package/packages/dd-trace/src/telemetry/send-data.js +5 -4
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +0 -10
|
@@ -6,90 +6,30 @@ const { addHook } = require('./helpers/instrument')
|
|
|
6
6
|
|
|
7
7
|
const anthropicTracingChannel = tracingChannel('apm:anthropic:request')
|
|
8
8
|
const onStreamedChunkCh = channel('apm:anthropic:request:chunk')
|
|
9
|
-
const messagesBeforeChannel = channel('dd-trace:anthropic:messages:before')
|
|
10
|
-
const messagesAfterChannel = channel('dd-trace:anthropic:messages:after')
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* @param {object} payload
|
|
20
|
-
* @returns {Promise<void>}
|
|
21
|
-
*/
|
|
22
|
-
function publishLifecycle (channel, payload) {
|
|
23
|
-
const abortController = new AbortController()
|
|
24
|
-
const ctx = { ...payload, abortController, pending: [] }
|
|
25
|
-
|
|
26
|
-
channel.publish(ctx)
|
|
27
|
-
|
|
28
|
-
return Promise.all(ctx.pending).then(() => {
|
|
29
|
-
if (abortController.signal.aborted) {
|
|
30
|
-
throw abortController.signal.reason
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
}
|
|
10
|
+
// `prepare` is published before the SDK is invoked, so subscribers may replace `arguments[0]`.
|
|
11
|
+
// `intercept` follows with the native call data; a subscriber may set either callback and this
|
|
12
|
+
// instrumentation applies it, so the wrapping stays here and only the policy lives outside:
|
|
13
|
+
// beforeResult () => Promise<void>|undefined holds the result back until it settles
|
|
14
|
+
// onResult (body) => unknown|Promise<unknown> inspects or replaces the delivered body
|
|
15
|
+
const messagesPrepareChannel = channel('dd-trace:anthropic:messages:prepare')
|
|
16
|
+
const messagesInterceptChannel = channel('dd-trace:anthropic:messages:intercept')
|
|
34
17
|
|
|
35
18
|
/**
|
|
19
|
+
* Returns `promise`, but not before `settled` settles, so a rejection from an interceptor
|
|
20
|
+
* reaches the caller instead of the SDK's value.
|
|
21
|
+
*
|
|
36
22
|
* @template T
|
|
37
23
|
* @param {Promise<T>} promise
|
|
38
|
-
* @param {Promise<void>|undefined}
|
|
24
|
+
* @param {Promise<void>|undefined} settled
|
|
39
25
|
* @returns {Promise<T>}
|
|
40
26
|
*/
|
|
41
|
-
function
|
|
42
|
-
if (!
|
|
27
|
+
function heldUntil (promise, settled) {
|
|
28
|
+
if (!settled) return promise
|
|
43
29
|
|
|
44
|
-
// The
|
|
30
|
+
// The interceptor's rejection wins over an earlier SDK rejection, so keep that one handled.
|
|
45
31
|
promise.catch(() => {})
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* @param {Array<unknown>} args
|
|
51
|
-
* @returns {Array<unknown>|undefined}
|
|
52
|
-
*/
|
|
53
|
-
function snapshotLifecycleArgs (args) {
|
|
54
|
-
const options = args[0]
|
|
55
|
-
if (!options || typeof options !== 'object') return
|
|
56
|
-
|
|
57
|
-
const input = { messages: options.messages }
|
|
58
|
-
if (options.system !== undefined) input.system = options.system
|
|
59
|
-
|
|
60
|
-
// Snapshot via JSON so AI Guard evaluates exactly what the SDK serializes and sends, immune to
|
|
61
|
-
// later caller mutation. If it can't serialize (e.g. circular), the SDK's own serialization would
|
|
62
|
-
// fail too, so skipping evaluation is safe — nothing reaches the model.
|
|
63
|
-
try {
|
|
64
|
-
const snapshot = [...args]
|
|
65
|
-
// eslint-disable-next-line unicorn/prefer-structured-clone
|
|
66
|
-
snapshot[0] = { ...options, ...JSON.parse(JSON.stringify(input)) }
|
|
67
|
-
return snapshot
|
|
68
|
-
} catch {
|
|
69
|
-
// Unserializable input — leave AI Guard inactive for this call (the SDK send would fail too).
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Finishes after output evaluation so a block propagates to the caller and span.
|
|
75
|
-
*
|
|
76
|
-
* @param {object} ctx
|
|
77
|
-
* @param {object} result
|
|
78
|
-
* @param {(body: object) => Promise<void>|undefined} getVerdict
|
|
79
|
-
* @param {object|string} [returnedResult]
|
|
80
|
-
* @returns {object|string|Promise<object|string>}
|
|
81
|
-
*/
|
|
82
|
-
function finishResult (ctx, result, getVerdict, returnedResult = result) {
|
|
83
|
-
const verdict = getVerdict(result)
|
|
84
|
-
if (!verdict) {
|
|
85
|
-
finish(ctx, result)
|
|
86
|
-
return returnedResult
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return verdict.then(() => {
|
|
90
|
-
finish(ctx, result)
|
|
91
|
-
return returnedResult
|
|
92
|
-
})
|
|
32
|
+
return settled.then(() => promise)
|
|
93
33
|
}
|
|
94
34
|
|
|
95
35
|
/**
|
|
@@ -106,22 +46,36 @@ function finishAndThrow (ctx, error) {
|
|
|
106
46
|
* @param {object} response
|
|
107
47
|
* @param {'json'|'text'} method
|
|
108
48
|
* @param {object} ctx
|
|
109
|
-
* @param {
|
|
49
|
+
* @param {object} [interceptCtx]
|
|
110
50
|
*/
|
|
111
|
-
function wrapResponseReader (response, method, ctx,
|
|
51
|
+
function wrapResponseReader (response, method, ctx, interceptCtx) {
|
|
112
52
|
if (typeof response[method] !== 'function') return
|
|
113
53
|
|
|
114
54
|
shimmer.wrap(response, method, original => function (...args) {
|
|
115
55
|
return original.apply(this, args)
|
|
116
56
|
.then(body => {
|
|
117
|
-
|
|
57
|
+
// `text()` callers keep their string; only the callback and the span see the parsed body.
|
|
58
|
+
let parsed = body
|
|
59
|
+
if (method === 'text') {
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(body)
|
|
62
|
+
} catch {
|
|
63
|
+
finish(ctx)
|
|
64
|
+
return body
|
|
65
|
+
}
|
|
66
|
+
}
|
|
118
67
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
} catch {
|
|
122
|
-
finish(ctx)
|
|
68
|
+
if (!interceptCtx?.onResult) {
|
|
69
|
+
finish(ctx, parsed)
|
|
123
70
|
return body
|
|
124
71
|
}
|
|
72
|
+
|
|
73
|
+
return Promise.resolve(interceptCtx.onResult(parsed)).then(deliveredBody => {
|
|
74
|
+
finish(ctx, parsed)
|
|
75
|
+
// `json()` callers get any replacement; `text()` callers keep their original string,
|
|
76
|
+
// because the callback was handed the parsed object rather than the text.
|
|
77
|
+
return method === 'json' ? deliveredBody : body
|
|
78
|
+
})
|
|
125
79
|
})
|
|
126
80
|
.catch(error => finishAndThrow(ctx, error))
|
|
127
81
|
})
|
|
@@ -130,17 +84,17 @@ function wrapResponseReader (response, method, ctx, getVerdict) {
|
|
|
130
84
|
/**
|
|
131
85
|
* @param {object} response
|
|
132
86
|
* @param {object} ctx
|
|
133
|
-
* @param {
|
|
87
|
+
* @param {object} [interceptCtx]
|
|
134
88
|
*/
|
|
135
|
-
function wrapRawResponse (response, ctx,
|
|
136
|
-
wrapResponseReader(response, 'json', ctx,
|
|
137
|
-
wrapResponseReader(response, 'text', ctx,
|
|
89
|
+
function wrapRawResponse (response, ctx, interceptCtx) {
|
|
90
|
+
wrapResponseReader(response, 'json', ctx, interceptCtx)
|
|
91
|
+
wrapResponseReader(response, 'text', ctx, interceptCtx)
|
|
138
92
|
|
|
139
93
|
if (typeof response.clone !== 'function') return
|
|
140
94
|
|
|
141
95
|
shimmer.wrap(response, 'clone', clone => function (...args) {
|
|
142
96
|
const clonedResponse = clone.apply(this, args)
|
|
143
|
-
wrapRawResponse(clonedResponse, ctx,
|
|
97
|
+
wrapRawResponse(clonedResponse, ctx, interceptCtx)
|
|
144
98
|
return clonedResponse
|
|
145
99
|
})
|
|
146
100
|
}
|
|
@@ -172,68 +126,60 @@ function wrapStreamIterator (iterator, ctx) {
|
|
|
172
126
|
|
|
173
127
|
function wrapCreate (create) {
|
|
174
128
|
return function (...args) {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
// Streaming is out of scope for lifecycle evaluation.
|
|
179
|
-
const lifecycleArgs = !stream &&
|
|
180
|
-
(messagesBeforeChannel.hasSubscribers || messagesAfterChannel.hasSubscribers)
|
|
181
|
-
? snapshotLifecycleArgs(args)
|
|
182
|
-
: undefined
|
|
129
|
+
const stream = args[0]?.stream
|
|
130
|
+
const preparing = !stream && messagesPrepareChannel.hasSubscribers
|
|
131
|
+
const intercepting = !stream && messagesInterceptChannel.hasSubscribers
|
|
183
132
|
|
|
184
|
-
if (!anthropicTracingChannel.start.hasSubscribers && !
|
|
133
|
+
if (!anthropicTracingChannel.start.hasSubscribers && !preparing && !intercepting) {
|
|
185
134
|
return create.apply(this, args)
|
|
186
135
|
}
|
|
187
136
|
|
|
188
|
-
|
|
137
|
+
// `args` is this wrapper's own rest array, so replacing `arguments[0]` changes what reaches
|
|
138
|
+
// the SDK without touching anything the caller still holds.
|
|
139
|
+
if (preparing) {
|
|
140
|
+
messagesPrepareChannel.publish({ arguments: args })
|
|
141
|
+
}
|
|
189
142
|
|
|
190
|
-
|
|
191
|
-
const parentSpan = lifecycleArgs ? ctx.currentStore?.span : undefined
|
|
143
|
+
const ctx = { options: args[0], resource: 'create', baseUrl: this._client?.baseURL }
|
|
192
144
|
|
|
145
|
+
return anthropicTracingChannel.start.runStores(ctx, () => {
|
|
193
146
|
let apiPromise
|
|
194
147
|
try {
|
|
195
|
-
// Anthropic starts the request eagerly
|
|
196
|
-
apiPromise = create.apply(this,
|
|
148
|
+
// Anthropic starts the request eagerly, so subscribers can only hold back delivery.
|
|
149
|
+
apiPromise = create.apply(this, args)
|
|
197
150
|
} catch (error) {
|
|
198
151
|
finish(ctx, null, error)
|
|
199
152
|
throw error
|
|
200
153
|
}
|
|
201
154
|
|
|
202
|
-
let
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
function getBeforeVerdict () {
|
|
208
|
-
if (!lifecycleArgs || beforeVerdict) return beforeVerdict
|
|
209
|
-
if (!messagesBeforeChannel.hasSubscribers) return
|
|
210
|
-
|
|
211
|
-
beforeVerdict = publishLifecycle(messagesBeforeChannel, { args: lifecycleArgs, parentSpan })
|
|
212
|
-
return beforeVerdict
|
|
155
|
+
let interceptCtx
|
|
156
|
+
if (intercepting) {
|
|
157
|
+
interceptCtx = { arguments: args, tracingContext: ctx }
|
|
158
|
+
messagesInterceptChannel.publish(interceptCtx)
|
|
213
159
|
}
|
|
214
160
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
*/
|
|
218
|
-
function getAfterVerdict (body) {
|
|
219
|
-
if (!lifecycleArgs || afterVerdict) return afterVerdict
|
|
220
|
-
if (!messagesAfterChannel.hasSubscribers) return
|
|
221
|
-
|
|
222
|
-
afterVerdict = publishLifecycle(messagesAfterChannel, { args: lifecycleArgs, body, parentSpan })
|
|
223
|
-
return afterVerdict
|
|
224
|
-
}
|
|
161
|
+
let parseResult
|
|
162
|
+
let wrappedResponse
|
|
225
163
|
|
|
226
164
|
shimmer.wrap(apiPromise, 'parse', parse => function (...parseArgs) {
|
|
227
165
|
if (parseResult) return parseResult
|
|
228
166
|
|
|
229
|
-
|
|
230
|
-
parseResult = waitForVerdict(parsed, getBeforeVerdict())
|
|
167
|
+
parseResult = heldUntil(parse.apply(this, parseArgs), interceptCtx?.beforeResult?.())
|
|
231
168
|
.then(response => {
|
|
232
169
|
if (stream) {
|
|
233
170
|
shimmer.wrap(response, Symbol.asyncIterator, iterator => wrapStreamIterator(iterator, ctx))
|
|
234
171
|
return response
|
|
235
172
|
}
|
|
236
|
-
|
|
173
|
+
|
|
174
|
+
if (!interceptCtx?.onResult) {
|
|
175
|
+
finish(ctx, response)
|
|
176
|
+
return response
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return Promise.resolve(interceptCtx.onResult(response)).then(deliveredResponse => {
|
|
180
|
+
finish(ctx, response)
|
|
181
|
+
return deliveredResponse
|
|
182
|
+
})
|
|
237
183
|
})
|
|
238
184
|
.catch(error => finishAndThrow(ctx, error))
|
|
239
185
|
|
|
@@ -242,18 +188,14 @@ function wrapCreate (create) {
|
|
|
242
188
|
|
|
243
189
|
if (typeof apiPromise.asResponse === 'function') {
|
|
244
190
|
shimmer.wrap(apiPromise, 'asResponse', origAsResponse => function (...asResponseArgs) {
|
|
245
|
-
return
|
|
191
|
+
return heldUntil(origAsResponse.apply(this, asResponseArgs), interceptCtx?.beforeResult?.())
|
|
246
192
|
.then(response => {
|
|
247
|
-
// Wrap
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
afterVerdict || messagesAfterChannel.hasSubscribers
|
|
251
|
-
if (!stream && outputObserved && wrappedResponse !== response) {
|
|
193
|
+
// Wrap json()/text()/clone() so the span still closes on the raw-response path,
|
|
194
|
+
// and not twice for the same response.
|
|
195
|
+
if (!stream && wrappedResponse !== response) {
|
|
252
196
|
wrappedResponse = response
|
|
253
|
-
wrapRawResponse(response, ctx,
|
|
197
|
+
wrapRawResponse(response, ctx, interceptCtx)
|
|
254
198
|
}
|
|
255
|
-
|
|
256
|
-
if (afterVerdict) return afterVerdict.then(() => response)
|
|
257
199
|
return response
|
|
258
200
|
})
|
|
259
201
|
.catch(error => finishAndThrow(ctx, error))
|
|
@@ -105,7 +105,7 @@ function observeDurablePromise (dp, onSettle) {
|
|
|
105
105
|
dp[ON_SETTLE] = onSettle
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
for (const hook of getHooks('@aws/durable-execution-sdk-js')) {
|
|
108
|
+
for (const hook of getHooks('@aws/durable-execution-sdk-js').values()) {
|
|
109
109
|
hook.file = null
|
|
110
110
|
addHook(hook, exports => exports)
|
|
111
111
|
}
|
|
@@ -5,6 +5,7 @@ const shimmer = require('../../datadog-shimmer')
|
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
7
|
addHook,
|
|
8
|
+
getHooks,
|
|
8
9
|
} = require('./helpers/instrument')
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -12,6 +13,10 @@ const {
|
|
|
12
13
|
*/
|
|
13
14
|
const azureDurableFunctionsChannel = dc.tracingChannel('datadog:azure:durable-functions:invoke')
|
|
14
15
|
|
|
16
|
+
for (const hook of getHooks('durable-functions').values()) {
|
|
17
|
+
addHook(hook, exports => exports)
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
addHook({ name: 'durable-functions', versions: ['>=3'], patchDefault: false }, (df) => {
|
|
16
21
|
const { app } = df
|
|
17
22
|
|
|
@@ -42,9 +47,16 @@ function entityHandler (handler, entityName) {
|
|
|
42
47
|
if (!azureDurableFunctionsChannel.hasSubscribers) return handler.apply(this, args)
|
|
43
48
|
|
|
44
49
|
const entityContext = args[0]
|
|
50
|
+
const traceContext = entityContext?.traceContext
|
|
45
51
|
return azureDurableFunctionsChannel.traceSync(
|
|
46
52
|
handler,
|
|
47
|
-
{
|
|
53
|
+
{
|
|
54
|
+
trigger: 'Entity',
|
|
55
|
+
functionName: entityName,
|
|
56
|
+
operationName: entityContext?.df?.operationName,
|
|
57
|
+
traceparent: traceContext?.traceParent,
|
|
58
|
+
tracestate: traceContext?.traceState,
|
|
59
|
+
},
|
|
48
60
|
this, ...args)
|
|
49
61
|
}
|
|
50
62
|
}
|
|
@@ -58,16 +70,18 @@ function activityHandler (method) {
|
|
|
58
70
|
return function (...args) {
|
|
59
71
|
if (!azureDurableFunctionsChannel.hasSubscribers) return handler.apply(this, args)
|
|
60
72
|
|
|
73
|
+
const traceContext = args[1]?.traceContext
|
|
74
|
+
const channelCtx = {
|
|
75
|
+
trigger: 'Activity',
|
|
76
|
+
functionName: activityName,
|
|
77
|
+
traceparent: traceContext?.traceParent,
|
|
78
|
+
tracestate: traceContext?.traceState,
|
|
79
|
+
}
|
|
80
|
+
|
|
61
81
|
// use tracePromise if this is an async handler. otherwise, use traceSync
|
|
62
82
|
return isAsync
|
|
63
|
-
? azureDurableFunctionsChannel.tracePromise(
|
|
64
|
-
|
|
65
|
-
{ trigger: 'Activity', functionName: activityName },
|
|
66
|
-
this, ...args)
|
|
67
|
-
: azureDurableFunctionsChannel.traceSync(
|
|
68
|
-
handler,
|
|
69
|
-
{ trigger: 'Activity', functionName: activityName },
|
|
70
|
-
this, ...args)
|
|
83
|
+
? azureDurableFunctionsChannel.tracePromise(handler, channelCtx, this, ...args)
|
|
84
|
+
: azureDurableFunctionsChannel.traceSync(handler, channelCtx, this, ...args)
|
|
71
85
|
}
|
|
72
86
|
})
|
|
73
87
|
return method.apply(this, arguments)
|
|
@@ -8,6 +8,12 @@ const {
|
|
|
8
8
|
|
|
9
9
|
const azureFunctionsChannel = dc.tracingChannel('datadog:azure:functions:invoke')
|
|
10
10
|
|
|
11
|
+
// Orchestrators register via app.generic; activities/entities use the durable-functions hook.
|
|
12
|
+
const azureDurableFunctionsChannel = dc.tracingChannel('datadog:azure:durable-functions:invoke')
|
|
13
|
+
|
|
14
|
+
const ORCHESTRATION_TRIGGER_TYPE = 'orchestrationTrigger'
|
|
15
|
+
const ORCHESTRATOR_COMPLETED_EVENT_TYPE = 13
|
|
16
|
+
|
|
11
17
|
addHook({ name: '@azure/functions', versions: ['>=4'], patchDefault: false }, (azureFunction) => {
|
|
12
18
|
const { app } = azureFunction
|
|
13
19
|
|
|
@@ -29,9 +35,51 @@ addHook({ name: '@azure/functions', versions: ['>=4'], patchDefault: false }, (a
|
|
|
29
35
|
// CosmosDB triggers
|
|
30
36
|
shimmer.wrap(app, 'cosmosDB', wrapHandler)
|
|
31
37
|
|
|
38
|
+
// Durable Functions orchestration triggers
|
|
39
|
+
shimmer.wrap(app, 'generic', wrapGeneric)
|
|
40
|
+
|
|
32
41
|
return azureFunction
|
|
33
42
|
})
|
|
34
43
|
|
|
44
|
+
function wrapGeneric (method) {
|
|
45
|
+
return function (name, options) {
|
|
46
|
+
if (options?.trigger?.type === ORCHESTRATION_TRIGGER_TYPE && typeof options.handler === 'function') {
|
|
47
|
+
options.handler = traceOrchestrationHandler(options.handler, name)
|
|
48
|
+
}
|
|
49
|
+
return method.apply(this, arguments)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function traceOrchestrationHandler (handler, functionName) {
|
|
54
|
+
return function (...args) {
|
|
55
|
+
if (!azureDurableFunctionsChannel.hasSubscribers) return handler.apply(this, args)
|
|
56
|
+
|
|
57
|
+
const orchestrationBinding = args[0]
|
|
58
|
+
// we do not want to trace if the orchestrator has already completed and is being reactivated
|
|
59
|
+
const history = orchestrationBinding?.history
|
|
60
|
+
const hasPreviousActivation = history?.some(
|
|
61
|
+
event => event.EventType === ORCHESTRATOR_COMPLETED_EVENT_TYPE
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if (hasPreviousActivation) {
|
|
65
|
+
return handler.apply(this, args)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const traceContext = args[1]?.traceContext
|
|
69
|
+
const channelCtx = {
|
|
70
|
+
trigger: 'Orchestration',
|
|
71
|
+
functionName,
|
|
72
|
+
traceparent: traceContext?.traceParent,
|
|
73
|
+
tracestate: traceContext?.traceState,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return azureDurableFunctionsChannel.tracePromise(
|
|
77
|
+
handler,
|
|
78
|
+
channelCtx,
|
|
79
|
+
this, ...args)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
35
83
|
// The http methods are overloaded so we need to check which type of argument was passed in order to wrap the handler
|
|
36
84
|
// The arguments are either an object with a handler property or the handler function itself
|
|
37
85
|
function wrapHandler (method) {
|
|
@@ -638,7 +638,7 @@ function wrapQueryAsyncIterator (asyncIterator, ctx) {
|
|
|
638
638
|
|
|
639
639
|
let querySubscribed = false
|
|
640
640
|
|
|
641
|
-
for (const hook of getHooks('@anthropic-ai/claude-agent-sdk')) {
|
|
641
|
+
for (const hook of getHooks('@anthropic-ai/claude-agent-sdk').values()) {
|
|
642
642
|
hook.file = null
|
|
643
643
|
|
|
644
644
|
addHook(hook, exports => {
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
const { addHook } = require('./helpers/instrument')
|
|
4
4
|
|
|
5
5
|
// Empty hook just to make the plugin load.
|
|
6
|
-
//
|
|
7
|
-
addHook({ name: 'dd-trace-api' }, api => api)
|
|
6
|
+
// Version 1.0.1 re-publishes tracer initialization after the plugin subscribes.
|
|
7
|
+
addHook({ name: 'dd-trace-api', versions: ['>=1.0.1'] }, api => api)
|
|
@@ -12,7 +12,7 @@ const { addHook, getHooks } = require('./helpers/instrument')
|
|
|
12
12
|
*/
|
|
13
13
|
function addRewriterHooks (name) {
|
|
14
14
|
const files = new Set()
|
|
15
|
-
for (const hook of getHooks(name)) {
|
|
15
|
+
for (const hook of getHooks(name).values()) {
|
|
16
16
|
if (files.has(hook.file)) continue
|
|
17
17
|
files.add(hook.file)
|
|
18
18
|
addHook(hook, exports => exports)
|
|
@@ -4,13 +4,16 @@ const Module = require('module')
|
|
|
4
4
|
const dc = require('dc-polyfill')
|
|
5
5
|
|
|
6
6
|
const log = require('../../../dd-trace/src/log')
|
|
7
|
+
const { loadChannel } = require('./register.js')
|
|
7
8
|
const {
|
|
8
9
|
filename,
|
|
9
|
-
|
|
10
|
+
getDisabledInstrumentations,
|
|
10
11
|
matchVersion,
|
|
11
|
-
|
|
12
|
+
matchesInstrumentation,
|
|
13
|
+
} = require('./instrumentation-utils')
|
|
12
14
|
const hooks = require('./hooks')
|
|
13
15
|
const instrumentations = require('./instrumentations')
|
|
16
|
+
const disabledInstrumentations = getDisabledInstrumentations()
|
|
14
17
|
|
|
15
18
|
// register.js has now set up ritm (require-in-the-middle). In bundled
|
|
16
19
|
// environments (webpack, esbuild), Node.js built-in modules required by
|
|
@@ -69,21 +72,29 @@ function doHook (name) {
|
|
|
69
72
|
|
|
70
73
|
try {
|
|
71
74
|
hookFn()
|
|
72
|
-
} catch {
|
|
73
|
-
log.error('esbuild-wrapped %s hook failed', name)
|
|
75
|
+
} catch (error) {
|
|
76
|
+
log.error('esbuild-wrapped %s hook failed: %s', name, String(error?.message ?? error), error)
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @typedef {object} Payload
|
|
82
|
+
* @property {boolean} [activate]
|
|
83
|
+
* @property {string} package
|
|
84
|
+
* @property {unknown} [module]
|
|
85
|
+
* @property {string} [version]
|
|
86
|
+
* @property {string} [path]
|
|
87
|
+
*/
|
|
88
|
+
|
|
77
89
|
/** @type {Set<string>} */
|
|
78
90
|
const instrumentedNodeModules = new Set()
|
|
79
91
|
|
|
80
|
-
/** @typedef {{ package: string, module: unknown, version: string, path: string }} Payload */
|
|
81
92
|
dc.subscribe(CHANNEL, (message) => {
|
|
82
93
|
const payload = /** @type {Payload} */ (message)
|
|
83
94
|
const name = payload.package
|
|
95
|
+
if (disabledInstrumentations.has(name)) return
|
|
84
96
|
|
|
85
97
|
const isPrefixedWithNode = name.startsWith('node:')
|
|
86
|
-
|
|
87
98
|
const isNodeModule = isPrefixedWithNode || !hooks[name]
|
|
88
99
|
|
|
89
100
|
if (isNodeModule) {
|
|
@@ -100,20 +111,34 @@ dc.subscribe(CHANNEL, (message) => {
|
|
|
100
111
|
const instrumentation = instrumentations[name] ?? instrumentations[`node:${name}`]
|
|
101
112
|
|
|
102
113
|
if (!instrumentation) {
|
|
103
|
-
log.error('esbuild-wrapped %s missing in list of instrumentations', name)
|
|
114
|
+
if (!payload.activate) log.error('esbuild-wrapped %s missing in list of instrumentations', name)
|
|
104
115
|
return
|
|
105
116
|
}
|
|
106
117
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
if (payload.activate) {
|
|
119
|
+
for (const { sourceRewrite, versions, hook } of instrumentation) {
|
|
120
|
+
if (!sourceRewrite || payload.path !== filename(name, sourceRewrite) ||
|
|
121
|
+
!matchVersion(payload.version, versions)) continue
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
loadChannel.publish({ name })
|
|
125
|
+
hook(undefined, payload.version)
|
|
126
|
+
} catch (error) {
|
|
127
|
+
log.error('Error executing bundler hook: %s', String(error?.message ?? error), error)
|
|
128
|
+
}
|
|
129
|
+
return
|
|
110
130
|
}
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const entry of instrumentation) {
|
|
135
|
+
if (!matchesInstrumentation(name, payload.version, payload.path, entry)) continue
|
|
111
136
|
|
|
112
137
|
try {
|
|
113
|
-
loadChannel.publish({ name
|
|
114
|
-
payload.module = hook(payload.module, payload.version) ?? payload.module
|
|
115
|
-
} catch (
|
|
116
|
-
log.error('Error executing bundler hook',
|
|
138
|
+
loadChannel.publish({ name })
|
|
139
|
+
payload.module = entry.hook(payload.module, payload.version) ?? payload.module
|
|
140
|
+
} catch (error) {
|
|
141
|
+
log.error('Error executing bundler hook: %s', String(error?.message ?? error), error)
|
|
117
142
|
}
|
|
118
143
|
}
|
|
119
144
|
})
|
|
@@ -5,6 +5,8 @@ const dc = /** @type {typeof import('node:diagnostics_channel')} */ (require('dc
|
|
|
5
5
|
const instrumentations = require('./instrumentations')
|
|
6
6
|
const rewriterInstrumentations = require('./rewriter/instrumentations')
|
|
7
7
|
|
|
8
|
+
const sourceRewritePaths = new WeakMap()
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* @typedef {import('node:diagnostics_channel').Channel} Channel
|
|
10
12
|
* @typedef {import('node:diagnostics_channel').TracingChannel} TracingChannel
|
|
@@ -74,13 +76,26 @@ exports.createErrorPublisher = function createErrorPublisher (errorChannel) {
|
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
// The rewriter instrumentation list holds one entry per *transform*, so a
|
|
80
|
+
// module with several transforms repeats its module definition several times
|
|
81
|
+
// (mercurius defines 3, graphql 26). A hook only cares about the module, not
|
|
82
|
+
// the transform, so duplicates would push the same (versionRange, filePath)
|
|
83
|
+
// registration through `addHook` once per transform and have shimmer patch the
|
|
84
|
+
// same file several times.
|
|
77
85
|
exports.getHooks = function getHooks (names) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
const requested = new Set([names].flat())
|
|
87
|
+
const hooks = new Map()
|
|
88
|
+
for (const { module } of rewriterInstrumentations) {
|
|
89
|
+
if (!requested.has(module.name)) continue
|
|
90
|
+
// Fresh objects, including the versions array: callers may adjust a hook
|
|
91
|
+
// for their own registration (the ai, claude-agent-sdk and
|
|
92
|
+
// aws-durable-execution-sdk-js plugins set `hook.file = null`), which must
|
|
93
|
+
// not leak into any other call.
|
|
94
|
+
const hook = { name: module.name, versions: [module.versionRange], file: module.filePath }
|
|
95
|
+
sourceRewritePaths.set(hook, module.filePath)
|
|
96
|
+
hooks.set(`${module.name}|${module.versionRange}|${module.filePath}`, hook)
|
|
97
|
+
}
|
|
98
|
+
return hooks
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
/**
|
|
@@ -93,12 +108,14 @@ exports.getHooks = function getHooks (names) {
|
|
|
93
108
|
* @param {(moduleExports: unknown, version: string, isIitm?: boolean, hookMeta?: object) => unknown} [hook]
|
|
94
109
|
* Patches module exports
|
|
95
110
|
*/
|
|
96
|
-
exports.addHook = function addHook (
|
|
111
|
+
exports.addHook = function addHook (args, hook) {
|
|
112
|
+
const { name, versions, file, filePattern, patchDefault } = args
|
|
97
113
|
if (!instrumentations[name]) {
|
|
98
114
|
instrumentations[name] = []
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
|
|
117
|
+
const sourceRewrite = sourceRewritePaths.get(args)
|
|
118
|
+
instrumentations[name].push({ versions, file, filePattern, hook, patchDefault, sourceRewrite })
|
|
102
119
|
}
|
|
103
120
|
|
|
104
121
|
exports.AsyncResource = AsyncResource
|