dd-trace 5.109.0 → 5.110.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/ci/init.js +4 -3
- package/index.d.ts +66 -24
- package/loader-hook.mjs +88 -2
- package/package.json +18 -14
- package/packages/datadog-esbuild/index.js +26 -0
- package/packages/datadog-esbuild/src/utils.js +46 -3
- package/packages/datadog-instrumentations/src/connect.js +4 -3
- package/packages/datadog-instrumentations/src/fastify.js +4 -12
- package/packages/datadog-instrumentations/src/graphql.js +17 -6
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
- package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
- package/packages/datadog-instrumentations/src/koa.js +3 -2
- package/packages/datadog-instrumentations/src/mariadb.js +4 -2
- package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
- package/packages/datadog-instrumentations/src/next.js +10 -10
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-instrumentations/src/restify.js +4 -3
- package/packages/datadog-instrumentations/src/router.js +18 -5
- package/packages/datadog-instrumentations/src/selenium.js +1 -2
- package/packages/datadog-instrumentations/src/tedious.js +28 -0
- package/packages/datadog-instrumentations/src/vitest.js +264 -7
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
- package/packages/datadog-plugin-http/src/client.js +8 -3
- package/packages/datadog-plugin-http2/src/client.js +5 -2
- package/packages/datadog-plugin-mysql/src/index.js +7 -2
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
- package/packages/datadog-plugin-undici/src/index.js +5 -2
- package/packages/datadog-plugin-vitest/src/index.js +7 -1
- package/packages/datadog-webpack/index.js +17 -1
- package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
- package/packages/dd-trace/src/aiguard/sdk.js +13 -1
- package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
- package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
- package/packages/dd-trace/src/appsec/index.js +10 -6
- package/packages/dd-trace/src/appsec/reporter.js +1 -0
- package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
- package/packages/dd-trace/src/config/defaults.js +22 -3
- package/packages/dd-trace/src/config/generated-config-types.d.ts +535 -1
- package/packages/dd-trace/src/config/helper.js +133 -33
- package/packages/dd-trace/src/config/index.js +4 -1
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/supported-configurations.json +21 -3
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
- package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
- package/packages/dd-trace/src/debugger/index.js +5 -0
- package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
- package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
- package/packages/dd-trace/src/exporters/common/request.js +1 -20
- package/packages/dd-trace/src/exporters/common/url.js +34 -0
- package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
- package/packages/dd-trace/src/guardrails/index.js +8 -5
- package/packages/dd-trace/src/index.js +6 -4
- package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
- package/packages/dd-trace/src/llmobs/index.js +16 -5
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
- package/packages/dd-trace/src/llmobs/sdk.js +5 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
- package/packages/dd-trace/src/llmobs/tagger.js +55 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
- package/packages/dd-trace/src/log/index.js +18 -20
- package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
- package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
- package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
- package/packages/dd-trace/src/opentelemetry/span.js +14 -3
- package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
- package/packages/dd-trace/src/opentracing/span_context.js +17 -0
- package/packages/dd-trace/src/plugin_manager.js +7 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +9 -1
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
- package/packages/dd-trace/src/plugins/util/test.js +80 -0
- package/packages/dd-trace/src/plugins/util/url.js +68 -1
- package/packages/dd-trace/src/plugins/util/web.js +19 -36
- package/packages/dd-trace/src/priority_sampler.js +1 -13
- package/packages/dd-trace/src/profiling/config.js +129 -198
- package/packages/dd-trace/src/profiling/constants.js +4 -7
- package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
- package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
- package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
- package/packages/dd-trace/src/profiling/oom.js +71 -0
- package/packages/dd-trace/src/profiling/profiler.js +35 -23
- package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
- package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
- package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +1 -2
- package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
- package/packages/dd-trace/src/span_format.js +2 -1
- package/packages/dd-trace/src/span_processor.js +6 -0
- package/packages/dd-trace/src/telemetry/send-data.js +12 -10
- package/packages/dd-trace/src/tracer.js +15 -1
- package/packages/dd-trace/src/util.js +14 -0
- package/register.js +58 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
|
@@ -72,44 +72,53 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
72
72
|
})
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Start the consumer (`aws.response`) span for a receive. The first message carrying trace
|
|
77
|
+
* context becomes the parent; every additional one fans in as a span link.
|
|
78
|
+
*
|
|
79
|
+
* @param {{ request: object, response: object, needsFinish?: boolean, currentStore?: object }} ctx
|
|
80
|
+
* @returns {object | undefined} The store to activate for the consumer span, else the parent store.
|
|
81
|
+
*/
|
|
75
82
|
#startResponseSpan (ctx) {
|
|
76
83
|
const { request, response } = ctx
|
|
77
|
-
const
|
|
84
|
+
const carriers = this.responseExtract(request.params, request.operation, response)
|
|
78
85
|
|
|
79
86
|
let store = this._parentMap.get(request)
|
|
80
87
|
let span
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
88
|
+
|
|
89
|
+
if (carriers !== undefined) {
|
|
90
|
+
// request:start records requestTags only after the isEnabled gate, so an absent entry
|
|
91
|
+
// means this consumer is disabled — gate on it instead of paying isEnabled again here.
|
|
92
|
+
const requestTags = this.requestTags.get(request)
|
|
93
|
+
if (requestTags !== undefined) {
|
|
94
|
+
// A receive can return messages from many producers; fanning the extra ones in as span
|
|
95
|
+
// links is the shape dd-trace-java and dd-trace-py use for batch SQS receives.
|
|
96
|
+
for (const carrier of carriers) {
|
|
97
|
+
if (carrier === undefined) continue
|
|
98
|
+
const datadogContext = this.tracer.extract('text_map', carrier)
|
|
99
|
+
// A DSM-only carrier (a non-first sendMessageBatch entry when batchPropagationEnabled
|
|
100
|
+
// is off) extracts to null; span.addLink dereferences the context and would throw on it.
|
|
101
|
+
if (datadogContext === null) continue
|
|
102
|
+
if (span === undefined) {
|
|
103
|
+
ctx.needsFinish = true
|
|
104
|
+
span = this.startSpan('aws.response', {
|
|
105
|
+
childOf: datadogContext,
|
|
106
|
+
meta: {
|
|
107
|
+
...requestTags,
|
|
108
|
+
'span.kind': 'server',
|
|
109
|
+
},
|
|
110
|
+
integrationName: 'aws-sdk',
|
|
111
|
+
}, ctx)
|
|
112
|
+
store = ctx.currentStore
|
|
113
|
+
} else {
|
|
114
|
+
span.addLink({ context: datadogContext })
|
|
101
115
|
}
|
|
102
|
-
span = this.startSpan('aws.response', options, ctx)
|
|
103
|
-
store = ctx.currentStore
|
|
104
116
|
}
|
|
105
117
|
}
|
|
106
118
|
}
|
|
107
119
|
|
|
108
120
|
// Extract DSM context after, as we might not have a parent-child but may have a DSM context.
|
|
109
|
-
this.responseExtractDSMContext(
|
|
110
|
-
request.operation, request.params, response, span ?? null,
|
|
111
|
-
{ parsedAttributes: parsedMessageAttributes, parsedFirstBody, firstBodyChecked }
|
|
112
|
-
)
|
|
121
|
+
this.responseExtractDSMContext(request.operation, request.params, response, span ?? null, carriers)
|
|
113
122
|
|
|
114
123
|
return store
|
|
115
124
|
}
|
|
@@ -183,19 +192,39 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
183
192
|
return tags
|
|
184
193
|
}
|
|
185
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Parse the trace-context carrier of every received message, in message order.
|
|
197
|
+
* Entries are `undefined` for messages that carry no `_datadog` context.
|
|
198
|
+
*
|
|
199
|
+
* @param {{ MaxNumberOfMessages?: number }} params
|
|
200
|
+
* @param {string} operation
|
|
201
|
+
* @param {{ Messages?: object[] }} response
|
|
202
|
+
* @returns {Array<Record<string, string> | undefined> | undefined}
|
|
203
|
+
*/
|
|
186
204
|
responseExtract (params, operation, response) {
|
|
187
205
|
if (operation !== 'receiveMessage') return
|
|
188
|
-
if (
|
|
189
|
-
if (!response || !response.Messages || !response.Messages[0]) return
|
|
206
|
+
if (!response?.Messages?.length) return
|
|
190
207
|
|
|
191
|
-
|
|
192
|
-
|
|
208
|
+
return response.Messages.map(message => this.parseMessageCarrier(message))
|
|
209
|
+
}
|
|
193
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Resolve the trace-context carrier for a single received message. The
|
|
213
|
+
* `MessageAttributes._datadog` text map (direct SQS or SNS to SQS) wins;
|
|
214
|
+
* otherwise the EventBridge envelope, optionally wrapped in an SNS
|
|
215
|
+
* `Notification` (see getEventBridgeContext). Checking MessageAttributes first
|
|
216
|
+
* avoids parsing a large SNS `Message` just to rule out an EventBridge envelope.
|
|
217
|
+
*
|
|
218
|
+
* @param {object} message A single `response.Messages` entry.
|
|
219
|
+
* @returns {Record<string, string> | undefined}
|
|
220
|
+
*/
|
|
221
|
+
parseMessageCarrier (message) {
|
|
222
|
+
let parsedBody
|
|
194
223
|
if (message.Body) {
|
|
195
224
|
try {
|
|
196
225
|
parsedBody = JSON.parse(message.Body)
|
|
197
226
|
} catch {
|
|
198
|
-
// SQS to SQS
|
|
227
|
+
// Opaque, non-JSON body (SQS to SQS).
|
|
199
228
|
}
|
|
200
229
|
// SNS to SQS
|
|
201
230
|
if (parsedBody?.Type === 'Notification') {
|
|
@@ -203,34 +232,9 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
203
232
|
}
|
|
204
233
|
}
|
|
205
234
|
|
|
206
|
-
// Check MessageAttributes first (common direct-SQS/SNS path): avoids parsing
|
|
207
|
-
// the body (e.g. a large SNS `Message`) just to rule out an EventBridge
|
|
208
|
-
// envelope. Precedence matches responseExtractDSMContext.
|
|
209
235
|
const datadogAttribute = message.MessageAttributes?._datadog
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (parsedAttributes) {
|
|
213
|
-
return {
|
|
214
|
-
datadogContext: this.tracer.extract('text_map', parsedAttributes),
|
|
215
|
-
parsedAttributes,
|
|
216
|
-
parsedBody,
|
|
217
|
-
bodyChecked: true,
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Then the EventBridge envelope (optionally via SNS); see getEventBridgeContext.
|
|
223
|
-
const eventBridgeContext = getEventBridgeContext(parsedBody)
|
|
224
|
-
if (eventBridgeContext) {
|
|
225
|
-
return {
|
|
226
|
-
datadogContext: this.tracer.extract('text_map', eventBridgeContext),
|
|
227
|
-
parsedAttributes: eventBridgeContext,
|
|
228
|
-
parsedBody,
|
|
229
|
-
bodyChecked: true,
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return { parsedBody, bodyChecked: true }
|
|
236
|
+
const carrier = datadogAttribute ? this.parseDatadogAttributes(datadogAttribute) : undefined
|
|
237
|
+
return carrier ?? getEventBridgeContext(parsedBody)
|
|
234
238
|
}
|
|
235
239
|
|
|
236
240
|
parseDatadogAttributes (attributes) {
|
|
@@ -247,51 +251,39 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
247
251
|
}
|
|
248
252
|
}
|
|
249
253
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
/**
|
|
255
|
+
* @param {string} operation
|
|
256
|
+
* @param {{ QueueUrl: string }} params
|
|
257
|
+
* @param {{ Messages?: object[] }} response
|
|
258
|
+
* @param {import('../../../dd-trace/src/opentracing/span') | null} span
|
|
259
|
+
* @param {Array<Record<string, string> | undefined>} [carriers] Per-message carriers already
|
|
260
|
+
* parsed by `responseExtract`; reused so each message body is parsed once. When omitted, the
|
|
261
|
+
* carriers are parsed here.
|
|
262
|
+
*/
|
|
263
|
+
responseExtractDSMContext (operation, params, response, span, carriers) {
|
|
253
264
|
if (!this.config.dsmEnabled) return
|
|
254
265
|
if (operation !== 'receiveMessage') return
|
|
255
|
-
if (!response
|
|
266
|
+
if (!response?.Messages?.length) return
|
|
256
267
|
|
|
268
|
+
const messages = response.Messages
|
|
257
269
|
// Only attribute payloadSize to the span when there is a single message.
|
|
258
|
-
span =
|
|
270
|
+
span = messages.length > 1 ? null : span
|
|
259
271
|
|
|
260
272
|
// QueueUrl is the same for the whole receive batch.
|
|
261
273
|
const queue = params.QueueUrl.slice(params.QueueUrl.lastIndexOf('/') + 1)
|
|
262
274
|
|
|
263
|
-
for (let i = 0; i <
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
} else if (message.Body) {
|
|
271
|
-
try {
|
|
272
|
-
body = JSON.parse(message.Body)
|
|
273
|
-
} catch {
|
|
274
|
-
// SQS to SQS
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
// SNS to SQS
|
|
278
|
-
if (body?.Type === 'Notification') {
|
|
279
|
-
message = body
|
|
280
|
-
}
|
|
281
|
-
// MessageAttributes for direct SQS/SNS; else the EventBridge envelope.
|
|
282
|
-
parsedAttributes = message.MessageAttributes?._datadog
|
|
283
|
-
? this.parseDatadogAttributes(message.MessageAttributes._datadog)
|
|
284
|
-
: getEventBridgeContext(body)
|
|
275
|
+
for (let i = 0; i < messages.length; i++) {
|
|
276
|
+
const message = messages[i]
|
|
277
|
+
const carrier = carriers === undefined ? this.parseMessageCarrier(message) : carriers[i]
|
|
278
|
+
if (carrier) {
|
|
279
|
+
// Inert for EventBridge until its producer emits a pathway (separate
|
|
280
|
+
// change) — no `dd-pathway-ctx-base64` to decode yet; SQS/SNS decode now.
|
|
281
|
+
this.tracer.decodeDataStreamsContext(carrier)
|
|
285
282
|
}
|
|
286
283
|
const payloadSize = getHeadersSize({
|
|
287
284
|
Body: message.Body,
|
|
288
285
|
MessageAttributes: message.MessageAttributes,
|
|
289
286
|
})
|
|
290
|
-
if (parsedAttributes) {
|
|
291
|
-
// Inert for EventBridge until its producer emits a pathway (separate
|
|
292
|
-
// change) — no `dd-pathway-ctx-base64` to decode yet; SQS/SNS decode now.
|
|
293
|
-
this.tracer.decodeDataStreamsContext(parsedAttributes)
|
|
294
|
-
}
|
|
295
287
|
this.tracer
|
|
296
288
|
.setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize)
|
|
297
289
|
}
|
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
TEST_IS_RUM_ACTIVE,
|
|
12
12
|
TEST_CODE_OWNERS,
|
|
13
13
|
getTestEnvironmentMetadata,
|
|
14
|
+
getTestLevelsMetadataTags,
|
|
14
15
|
CI_APP_ORIGIN,
|
|
15
16
|
getTestParentSpan,
|
|
16
17
|
getCodeOwnersFileEntries,
|
|
@@ -23,6 +24,7 @@ const {
|
|
|
23
24
|
TEST_MODULE_ID,
|
|
24
25
|
TEST_SESSION_ID,
|
|
25
26
|
TEST_COMMAND,
|
|
27
|
+
TEST_LEVELS_METADATA,
|
|
26
28
|
TEST_MODULE,
|
|
27
29
|
TEST_SOURCE_START,
|
|
28
30
|
finishAllTraceSpans,
|
|
@@ -1043,7 +1045,13 @@ class CypressPlugin {
|
|
|
1043
1045
|
)
|
|
1044
1046
|
|
|
1045
1047
|
if (this.tracer._tracer._exporter?.addMetadataTags) {
|
|
1046
|
-
const metadataTags = {
|
|
1048
|
+
const metadataTags = {
|
|
1049
|
+
[TEST_LEVELS_METADATA]: {
|
|
1050
|
+
[TEST_COMMAND]: this.command,
|
|
1051
|
+
[TEST_SESSION_NAME]: testSessionName,
|
|
1052
|
+
...getTestLevelsMetadataTags(this.testEnvironmentMetadata),
|
|
1053
|
+
},
|
|
1054
|
+
}
|
|
1047
1055
|
const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, this.frameworkVersion)
|
|
1048
1056
|
metadataTags.test = {
|
|
1049
1057
|
...libraryCapabilitiesTags,
|
|
@@ -8,6 +8,7 @@ const tags = require('../../../ext/tags')
|
|
|
8
8
|
const formats = require('../../../ext/formats')
|
|
9
9
|
const HTTP_HEADERS = formats.HTTP_HEADERS
|
|
10
10
|
const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
|
|
11
|
+
const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
|
|
11
12
|
const log = require('../../dd-trace/src/log')
|
|
12
13
|
const { CLIENT_PORT_KEY, COMPONENT, ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants')
|
|
13
14
|
|
|
@@ -27,13 +28,17 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
27
28
|
const protocol = options.protocol || agent.protocol || 'http:'
|
|
28
29
|
const hostname = options.hostname || options.host || 'localhost'
|
|
29
30
|
const host = options.port ? `${hostname}:${options.port}` : hostname
|
|
30
|
-
const
|
|
31
|
+
const base = `${protocol}//${host}`
|
|
32
|
+
// A URL object (e.g. from the fetch integration) carries the query in
|
|
33
|
+
// `options.search`, not `options.path`; keep it so url.full retains the query.
|
|
34
|
+
const pathname = options.path || `${options.pathname || ''}${options.search || ''}`
|
|
31
35
|
const path = pathname ? pathname.split(/[?#]/)[0] : '/'
|
|
32
|
-
const uri = `${
|
|
36
|
+
const uri = `${base}${path}`
|
|
33
37
|
|
|
34
38
|
const allowed = this.config.filter(uri)
|
|
35
39
|
|
|
36
40
|
const method = (options.method || 'GET').toUpperCase()
|
|
41
|
+
const otelSemantics = this.config.DD_TRACE_OTEL_SEMANTICS_ENABLED
|
|
37
42
|
const childOf = store && allowed ? store.span : null
|
|
38
43
|
// TODO delegate to super.startspan
|
|
39
44
|
const span = this.startSpan(this.operationName(), {
|
|
@@ -46,7 +51,7 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
46
51
|
'resource.name': method,
|
|
47
52
|
'span.type': 'http',
|
|
48
53
|
'http.method': method,
|
|
49
|
-
'http.url': uri,
|
|
54
|
+
'http.url': otelSemantics ? buildClientHttpUrl(this.config, base, pathname, uri) : uri,
|
|
50
55
|
'out.host': hostname,
|
|
51
56
|
},
|
|
52
57
|
metrics: {
|
|
@@ -10,6 +10,7 @@ const kinds = require('../../../ext/kinds')
|
|
|
10
10
|
const formats = require('../../../ext/formats')
|
|
11
11
|
const { COMPONENT, CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
|
|
12
12
|
const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
|
|
13
|
+
const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
|
|
13
14
|
|
|
14
15
|
const HTTP_HEADERS = formats.HTTP_HEADERS
|
|
15
16
|
const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE
|
|
@@ -33,8 +34,10 @@ class Http2ClientPlugin extends ClientPlugin {
|
|
|
33
34
|
const path = headers[HTTP2_HEADER_PATH] || '/'
|
|
34
35
|
const pathname = path.split(/[?#]/)[0]
|
|
35
36
|
const method = headers[HTTP2_HEADER_METHOD] || HTTP2_METHOD_GET
|
|
36
|
-
const
|
|
37
|
+
const base = `${sessionDetails.protocol}//${sessionDetails.host}:${sessionDetails.port}`
|
|
38
|
+
const uri = `${base}${pathname}`
|
|
37
39
|
const allowed = this.config.filter(uri)
|
|
40
|
+
const otelSemantics = this.config.DD_TRACE_OTEL_SEMANTICS_ENABLED
|
|
38
41
|
|
|
39
42
|
const store = storage('legacy').getStore()
|
|
40
43
|
const childOf = store && allowed ? store.span : null
|
|
@@ -48,7 +51,7 @@ class Http2ClientPlugin extends ClientPlugin {
|
|
|
48
51
|
'resource.name': method,
|
|
49
52
|
'span.type': 'http',
|
|
50
53
|
'http.method': method,
|
|
51
|
-
'http.url': uri,
|
|
54
|
+
'http.url': otelSemantics ? buildClientHttpUrl(this.config, base, path, uri) : uri,
|
|
52
55
|
'out.host': sessionDetails.host,
|
|
53
56
|
},
|
|
54
57
|
metrics: {
|
|
@@ -11,11 +11,16 @@ class MySQLPlugin extends DatabasePlugin {
|
|
|
11
11
|
constructor () {
|
|
12
12
|
super(...arguments)
|
|
13
13
|
|
|
14
|
+
// Capture into `currentStore` (not `parentStore`) so connection:finish can
|
|
15
|
+
// restore the caller context even when the connection resolves inside an
|
|
16
|
+
// instrumentation skip (a noop store), as the mariadb pool does: the store
|
|
17
|
+
// binding only honors an explicit `currentStore` through a noop store.
|
|
18
|
+
// Without a skip (mysql/mysql2) this is unchanged.
|
|
14
19
|
this.addSub(`apm:${this.component}:connection:start`, ctx => {
|
|
15
|
-
ctx.
|
|
20
|
+
ctx.currentStore = storage('legacy').getStore()
|
|
16
21
|
})
|
|
17
22
|
|
|
18
|
-
this.addBind(`apm:${this.component}:connection:finish`, ctx => ctx.
|
|
23
|
+
this.addBind(`apm:${this.component}:connection:finish`, ctx => ctx.currentStore)
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
bindStart (ctx) {
|
|
@@ -47,10 +47,10 @@ class SchemaExtractor {
|
|
|
47
47
|
|
|
48
48
|
static extractProperty (field, schemaName, fieldName, builder, depth) {
|
|
49
49
|
let array = false
|
|
50
|
-
let description
|
|
51
50
|
let ref
|
|
52
51
|
let enumValues
|
|
53
52
|
|
|
53
|
+
const description = field.comment
|
|
54
54
|
const resolvedType = field.resolvedType ? field.resolvedType.constructor.name : field.type
|
|
55
55
|
|
|
56
56
|
const isRepeatedField = field.rule === 'repeated'
|
|
@@ -124,7 +124,7 @@ class SchemaExtractor {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
iterateOverSchema (builder) {
|
|
127
|
-
|
|
127
|
+
SchemaExtractor.extractSchema(this.schema, builder, 0)
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
static attachSchemaOnSpan (args, span, operation, tracer) {
|
|
@@ -6,6 +6,7 @@ const tags = require('../../../ext/tags')
|
|
|
6
6
|
const formats = require('../../../ext/formats')
|
|
7
7
|
const HTTP_HEADERS = formats.HTTP_HEADERS
|
|
8
8
|
const log = require('../../dd-trace/src/log')
|
|
9
|
+
const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
|
|
9
10
|
const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
|
|
10
11
|
|
|
11
12
|
const {
|
|
@@ -60,10 +61,12 @@ class UndiciPlugin extends HttpClientPlugin {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
const host = port ? `${hostname}:${port}` : hostname
|
|
64
|
+
const base = `${protocol}//${host}`
|
|
63
65
|
const pathname = path.split(/[?#]/)[0]
|
|
64
|
-
const uri = `${
|
|
66
|
+
const uri = `${base}${pathname}`
|
|
65
67
|
|
|
66
68
|
const allowed = this.config.filter(uri)
|
|
69
|
+
const otelSemantics = this.config.DD_TRACE_OTEL_SEMANTICS_ENABLED
|
|
67
70
|
const childOf = store && allowed ? store.span : null
|
|
68
71
|
|
|
69
72
|
const span = this.startSpan(this.operationName(), {
|
|
@@ -71,7 +74,7 @@ class UndiciPlugin extends HttpClientPlugin {
|
|
|
71
74
|
meta: {
|
|
72
75
|
'span.kind': 'client',
|
|
73
76
|
'http.method': method,
|
|
74
|
-
'http.url': uri,
|
|
77
|
+
'http.url': otelSemantics ? buildClientHttpUrl(this.config, base, path, uri) : uri,
|
|
75
78
|
'out.host': hostname,
|
|
76
79
|
},
|
|
77
80
|
metrics: {
|
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
finishAllTraceSpans,
|
|
10
10
|
getTestSuitePath,
|
|
11
11
|
getTestSuiteCommonTags,
|
|
12
|
+
getTestLevelsMetadataTags,
|
|
12
13
|
getTestSessionName,
|
|
13
14
|
getIsFaultyEarlyFlakeDetection,
|
|
14
15
|
TEST_SOURCE_FILE,
|
|
@@ -16,6 +17,7 @@ const {
|
|
|
16
17
|
TEST_CODE_COVERAGE_LINES_PCT,
|
|
17
18
|
TEST_CODE_OWNERS,
|
|
18
19
|
TEST_COMMAND,
|
|
20
|
+
TEST_LEVELS_METADATA,
|
|
19
21
|
TEST_SESSION_NAME,
|
|
20
22
|
TEST_SOURCE_START,
|
|
21
23
|
TEST_IS_NEW,
|
|
@@ -328,7 +330,11 @@ class VitestPlugin extends CiPlugin {
|
|
|
328
330
|
const testSessionName = getTestSessionName(this.config, trimmedCommand, this.testEnvironmentMetadata)
|
|
329
331
|
if (this.tracer._exporter.addMetadataTags) {
|
|
330
332
|
this.tracer._exporter.addMetadataTags({
|
|
331
|
-
|
|
333
|
+
[TEST_LEVELS_METADATA]: {
|
|
334
|
+
[TEST_COMMAND]: testCommand,
|
|
335
|
+
[TEST_SESSION_NAME]: testSessionName,
|
|
336
|
+
...getTestLevelsMetadataTags(this.testEnvironmentMetadata),
|
|
337
|
+
},
|
|
332
338
|
test: getLibraryCapabilitiesTags(this.constructor.id),
|
|
333
339
|
})
|
|
334
340
|
}
|
|
@@ -6,6 +6,7 @@ const fs = require('node:fs')
|
|
|
6
6
|
const instrumentations = require('../datadog-instrumentations/src/helpers/instrumentations')
|
|
7
7
|
const extractPackageAndModulePath = require('../datadog-instrumentations/src/helpers/extract-package-and-module-path')
|
|
8
8
|
const hooks = require('../datadog-instrumentations/src/helpers/hooks')
|
|
9
|
+
const { matchesOptionalPeerFile } = require('../datadog-instrumentations/src/helpers/optional-peer-bundler')
|
|
9
10
|
const { isESMFile } = require('../datadog-esbuild/src/utils')
|
|
10
11
|
const log = require('./src/log')
|
|
11
12
|
|
|
@@ -130,11 +131,26 @@ class DatadogWebpackPlugin {
|
|
|
130
131
|
nmf.hooks.afterResolve.tap(PLUGIN_NAME, (resolveData) => {
|
|
131
132
|
const { createData } = resolveData
|
|
132
133
|
const resource = createData?.resource
|
|
133
|
-
if (!resource
|
|
134
|
+
if (!resource) {
|
|
134
135
|
return
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
const normalizedResource = resource.replaceAll('\\', '/')
|
|
139
|
+
|
|
140
|
+
// Rewrite optional-peer loads so installed peers get bundled and survive relocation
|
|
141
|
+
// (#8980); absent peers stay opaque, so a build that does not opt into the feature does
|
|
142
|
+
// not follow their dependency chain (#8635).
|
|
143
|
+
if (matchesOptionalPeerFile(normalizedResource)) {
|
|
144
|
+
createData.loaders = createData.loaders || []
|
|
145
|
+
createData.loaders.push({ loader: require.resolve('./src/optional-peer-loader') })
|
|
146
|
+
log.debug('INLINE: optional-peer loader applied to %s', normalizedResource)
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!resource.includes('node_modules')) {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
138
154
|
const { pkg, path: modulePath, pkgJson } = extractPackageAndModulePath(normalizedResource)
|
|
139
155
|
if (!pkg) {
|
|
140
156
|
return
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { rewriteOptionalPeerLoads } = require('../../datadog-instrumentations/src/helpers/optional-peer-bundler')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Webpack loader applied to the optional-peer loader files. Rewrites each
|
|
7
|
+
* `requireOptionalPeer('name')` whose peer is installed at build time into a literal
|
|
8
|
+
* `require('name')` so webpack bundles the peer (#8980). Peers that are absent stay opaque, so
|
|
9
|
+
* builds that do not opt into the feature keep the #8635 guarantee.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} source
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
module.exports = function optionalPeerLoader (source) {
|
|
15
|
+
this.cacheable(false)
|
|
16
|
+
return rewriteOptionalPeerLoads(source, this.context)
|
|
17
|
+
}
|
|
@@ -63,6 +63,18 @@ class AIGuardClientError extends Error {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Resolves the AI Guard host for a given Datadog site. Sites with a single subdomain level
|
|
68
|
+
* (e.g. `datadoghq.com`, `ddog-gov.com`) are served from the `app.` subdomain, while regional
|
|
69
|
+
* sites (e.g. `us3.datadoghq.com`, `ap1.datadoghq.com`) are used as-is.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} site - Datadog site (e.g. `datadoghq.com`, `us3.datadoghq.com`)
|
|
72
|
+
* @returns {string} The host to use for the AI Guard endpoint
|
|
73
|
+
*/
|
|
74
|
+
function aiGuardHost (site) {
|
|
75
|
+
return site.split('.').length === 2 ? `app.${site}` : site
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
class AIGuard extends NoopAIGuard {
|
|
67
79
|
#initialized
|
|
68
80
|
#tracer
|
|
@@ -94,7 +106,7 @@ class AIGuard extends NoopAIGuard {
|
|
|
94
106
|
'DD-AI-GUARD-SOURCE': 'SDK',
|
|
95
107
|
'DD-AI-GUARD-LANGUAGE': 'nodejs',
|
|
96
108
|
}
|
|
97
|
-
const endpoint = config.experimental.aiguard.endpoint || `https
|
|
109
|
+
const endpoint = config.experimental.aiguard.endpoint || `https://${aiGuardHost(config.site)}/api/v2/ai-guard`
|
|
98
110
|
this.#evaluateUrl = `${endpoint}/evaluate`
|
|
99
111
|
this.#timeout = config.experimental.aiguard.timeout
|
|
100
112
|
this.#maxMessagesLength = config.experimental.aiguard.maxMessagesLength
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const web = require('../../plugins/util/web')
|
|
4
|
+
const { isSchemaAttribute } = require('../reporter')
|
|
5
|
+
const appsecTelemetry = require('../telemetry')
|
|
6
|
+
const sampler = require('./sampler')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Map a sampling decision into the corresponding API Security telemetry metric.
|
|
10
|
+
*
|
|
11
|
+
* The decision is done by the sampler, here the outcome is translated to emitted metrics:
|
|
12
|
+
* - SAMPLE: request.schema / request.no_schema depending on WAF schema attributes
|
|
13
|
+
* - MISSING_ROUTE: missing_route
|
|
14
|
+
* - SKIP: no metric emitted
|
|
15
|
+
*
|
|
16
|
+
* @param {import('http').IncomingMessage} req
|
|
17
|
+
* @param {'sample' | 'missing_route' | 'skip'} samplingDecision Sampler decision
|
|
18
|
+
* @param {{ attributes?: Record<string, unknown> } | undefined} wafResult WAF run result
|
|
19
|
+
*/
|
|
20
|
+
function reportRequest (req, samplingDecision, wafResult) {
|
|
21
|
+
switch (samplingDecision) {
|
|
22
|
+
case sampler.SamplingDecision.SAMPLE: {
|
|
23
|
+
const framework = getFramework(req)
|
|
24
|
+
if (hasSchemaAttributes(wafResult?.attributes)) {
|
|
25
|
+
appsecTelemetry.incrementApiSecRequestSchemaMetric(framework)
|
|
26
|
+
} else {
|
|
27
|
+
appsecTelemetry.incrementApiSecRequestNoSchemaMetric(framework)
|
|
28
|
+
}
|
|
29
|
+
break
|
|
30
|
+
}
|
|
31
|
+
case sampler.SamplingDecision.MISSING_ROUTE:
|
|
32
|
+
appsecTelemetry.incrementApiSecMissingRouteMetric(getFramework(req))
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getFramework (req) {
|
|
38
|
+
return web.root(req)?.context()?.getTag?.('component')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hasSchemaAttributes (attributes) {
|
|
42
|
+
if (!attributes) return false
|
|
43
|
+
for (const key of Object.keys(attributes)) {
|
|
44
|
+
if (isSchemaAttribute(key)) return true
|
|
45
|
+
}
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
configure: sampler.configure,
|
|
51
|
+
disable: sampler.disable,
|
|
52
|
+
sampleRequest: sampler.sampleRequest,
|
|
53
|
+
reportRequest,
|
|
54
|
+
SamplingDecision: sampler.SamplingDecision,
|
|
55
|
+
}
|