dd-trace 5.105.0 → 5.107.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/index.d.ts +20 -1
- package/package.json +5 -7
- package/packages/datadog-core/src/storage.js +47 -48
- package/packages/datadog-esbuild/index.js +6 -1
- package/packages/datadog-instrumentations/src/ai.js +12 -3
- package/packages/datadog-instrumentations/src/aws-sdk.js +3 -2
- package/packages/datadog-instrumentations/src/body-parser.js +5 -2
- package/packages/datadog-instrumentations/src/connect.js +3 -2
- package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +19 -0
- package/packages/datadog-instrumentations/src/cucumber.js +319 -152
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
- package/packages/datadog-instrumentations/src/express-session.js +12 -11
- package/packages/datadog-instrumentations/src/express.js +24 -20
- package/packages/datadog-instrumentations/src/fastify.js +18 -6
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
- package/packages/datadog-instrumentations/src/http/client.js +9 -12
- package/packages/datadog-instrumentations/src/http/server.js +30 -16
- package/packages/datadog-instrumentations/src/http2/client.js +15 -12
- package/packages/datadog-instrumentations/src/http2/server.js +15 -8
- package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
- package/packages/datadog-instrumentations/src/jest.js +143 -73
- package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
- package/packages/datadog-instrumentations/src/multer.js +3 -2
- package/packages/datadog-instrumentations/src/mysql2.js +34 -0
- package/packages/datadog-instrumentations/src/net.js +8 -6
- package/packages/datadog-instrumentations/src/openai.js +19 -7
- package/packages/datadog-instrumentations/src/pg.js +19 -0
- package/packages/datadog-instrumentations/src/router.js +12 -10
- package/packages/datadog-instrumentations/src/vitest.js +29 -4
- package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
- package/packages/datadog-plugin-cucumber/src/index.js +2 -0
- package/packages/datadog-plugin-cypress/src/support.js +31 -1
- package/packages/datadog-plugin-http/src/client.js +0 -3
- package/packages/datadog-plugin-http/src/server.js +11 -1
- package/packages/datadog-plugin-mocha/src/index.js +2 -0
- package/packages/datadog-plugin-pg/src/index.js +10 -0
- package/packages/dd-trace/src/aiguard/index.js +34 -15
- package/packages/dd-trace/src/aiguard/sdk.js +34 -3
- package/packages/dd-trace/src/aiguard/tags.js +6 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +14 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +5 -9
- package/packages/dd-trace/src/config/parsers.js +8 -0
- package/packages/dd-trace/src/config/supported-configurations.json +13 -6
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/datastreams/writer.js +1 -2
- package/packages/dd-trace/src/debugger/config.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
- package/packages/dd-trace/src/debugger/index.js +1 -2
- package/packages/dd-trace/src/dogstatsd.js +2 -3
- package/packages/dd-trace/src/encode/0.4.js +49 -41
- package/packages/dd-trace/src/encode/agentless-json.js +5 -1
- package/packages/dd-trace/src/encode/tags-processors.js +14 -0
- package/packages/dd-trace/src/exporters/agent/index.js +1 -2
- package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
- package/packages/dd-trace/src/exporters/common/request.js +26 -0
- package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
- package/packages/dd-trace/src/id.js +15 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +91 -5
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
- package/packages/dd-trace/src/llmobs/sdk.js +4 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
- package/packages/dd-trace/src/llmobs/tagger.js +5 -3
- package/packages/dd-trace/src/llmobs/util.js +54 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
- package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +25 -5
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -10
- package/packages/dd-trace/src/opentracing/span.js +23 -18
- package/packages/dd-trace/src/opentracing/span_context.js +1 -3
- package/packages/dd-trace/src/opentracing/tracer.js +16 -12
- package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
- package/packages/dd-trace/src/priority_sampler.js +6 -5
- package/packages/dd-trace/src/profiling/config.js +11 -25
- package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
- package/packages/dd-trace/src/profiling/profiler.js +19 -9
- package/packages/dd-trace/src/profiling/profilers/wall.js +2 -3
- package/packages/dd-trace/src/proxy.js +13 -10
- package/packages/dd-trace/src/remote_config/index.js +1 -2
- package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
- package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
- package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
- package/packages/dd-trace/src/span_format.js +33 -25
- package/packages/dd-trace/src/span_stats.js +1 -1
- package/packages/dd-trace/src/startup-log.js +1 -2
- package/packages/dd-trace/src/telemetry/send-data.js +1 -1
- package/packages/dd-trace/src/tracer.js +1 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/dd-trace/src/agent/url.js +0 -28
- package/scripts/preinstall.js +0 -34
|
@@ -5,6 +5,43 @@ const BaseAwsSdkPlugin = require('../base')
|
|
|
5
5
|
const { DsmPathwayCodec, getHeadersSize } = require('../../../dd-trace/src/datastreams')
|
|
6
6
|
const { extractQueueMetadata, isEmpty } = require('../util')
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* 'detail-type'?: string,
|
|
11
|
+
* detail?: { _datadog?: Record<string, string> },
|
|
12
|
+
* Type?: string,
|
|
13
|
+
* Message?: string
|
|
14
|
+
* }} ParsedSqsBody
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the EventBridge `_datadog` text map from a parsed SQS body — for both
|
|
19
|
+
* EventBridge -> SQS (`body.detail._datadog`) and EventBridge -> SNS -> SQS (the
|
|
20
|
+
* envelope is the SNS `Notification`'s stringified `Message`). Keyed off
|
|
21
|
+
* `detail-type`, the marker AWS sets on every PutEvents delivery. Relies on the
|
|
22
|
+
* default SQS-target shape; a target InputTransformer can drop `detail`.
|
|
23
|
+
*
|
|
24
|
+
* @param {ParsedSqsBody} [parsedBody]
|
|
25
|
+
* @returns {Record<string, string> | undefined}
|
|
26
|
+
*/
|
|
27
|
+
function getEventBridgeContext (parsedBody) {
|
|
28
|
+
let envelope
|
|
29
|
+
if (parsedBody?.['detail-type'] !== undefined) {
|
|
30
|
+
envelope = parsedBody // EventBridge -> SQS
|
|
31
|
+
} else if (parsedBody?.Type === 'Notification' && typeof parsedBody.Message === 'string') {
|
|
32
|
+
// EventBridge -> SNS -> SQS
|
|
33
|
+
try {
|
|
34
|
+
const innerEnvelope = JSON.parse(parsedBody.Message)
|
|
35
|
+
if (innerEnvelope?.['detail-type'] !== undefined) {
|
|
36
|
+
envelope = innerEnvelope
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// SNS `Message` not JSON
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return envelope?.detail?._datadog
|
|
43
|
+
}
|
|
44
|
+
|
|
8
45
|
class Sqs extends BaseAwsSdkPlugin {
|
|
9
46
|
static id = 'sqs'
|
|
10
47
|
static peerServicePrecursors = ['queuename']
|
|
@@ -149,17 +186,28 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
149
186
|
}
|
|
150
187
|
}
|
|
151
188
|
|
|
152
|
-
|
|
153
|
-
|
|
189
|
+
// Check MessageAttributes first (common direct-SQS/SNS path): avoids parsing
|
|
190
|
+
// the body (e.g. a large SNS `Message`) just to rule out an EventBridge
|
|
191
|
+
// envelope. Precedence matches responseExtractDSMContext.
|
|
192
|
+
const datadogAttribute = message.MessageAttributes?._datadog
|
|
193
|
+
if (datadogAttribute) {
|
|
194
|
+
const parsedAttributes = this.parseDatadogAttributes(datadogAttribute)
|
|
195
|
+
if (parsedAttributes) {
|
|
196
|
+
return {
|
|
197
|
+
datadogContext: this.tracer.extract('text_map', parsedAttributes),
|
|
198
|
+
parsedAttributes,
|
|
199
|
+
parsedBody,
|
|
200
|
+
bodyChecked: true,
|
|
201
|
+
}
|
|
202
|
+
}
|
|
154
203
|
}
|
|
155
204
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (parsedAttributes) {
|
|
205
|
+
// Then the EventBridge envelope (optionally via SNS); see getEventBridgeContext.
|
|
206
|
+
const eventBridgeContext = getEventBridgeContext(parsedBody)
|
|
207
|
+
if (eventBridgeContext) {
|
|
160
208
|
return {
|
|
161
|
-
datadogContext: this.tracer.extract('text_map',
|
|
162
|
-
parsedAttributes,
|
|
209
|
+
datadogContext: this.tracer.extract('text_map', eventBridgeContext),
|
|
210
|
+
parsedAttributes: eventBridgeContext,
|
|
163
211
|
parsedBody,
|
|
164
212
|
bodyChecked: true,
|
|
165
213
|
}
|
|
@@ -213,15 +261,18 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
213
261
|
if (body?.Type === 'Notification') {
|
|
214
262
|
message = body
|
|
215
263
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
264
|
+
// MessageAttributes for direct SQS/SNS; else the EventBridge envelope.
|
|
265
|
+
parsedAttributes = message.MessageAttributes?._datadog
|
|
266
|
+
? this.parseDatadogAttributes(message.MessageAttributes._datadog)
|
|
267
|
+
: getEventBridgeContext(body)
|
|
219
268
|
}
|
|
220
269
|
const payloadSize = getHeadersSize({
|
|
221
270
|
Body: message.Body,
|
|
222
271
|
MessageAttributes: message.MessageAttributes,
|
|
223
272
|
})
|
|
224
273
|
if (parsedAttributes) {
|
|
274
|
+
// Inert for EventBridge until its producer emits a pathway (separate
|
|
275
|
+
// change) — no `dd-pathway-ctx-base64` to decode yet; SQS/SNS decode now.
|
|
225
276
|
this.tracer.decodeDataStreamsContext(parsedAttributes)
|
|
226
277
|
}
|
|
227
278
|
this.tracer
|
|
@@ -80,6 +80,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
80
80
|
isTestManagementTestsEnabled,
|
|
81
81
|
isParallel,
|
|
82
82
|
}) => {
|
|
83
|
+
this._exportPendingWorkerTraces()
|
|
83
84
|
const {
|
|
84
85
|
isSuitesSkippingEnabled,
|
|
85
86
|
isCodeCoverageEnabled,
|
|
@@ -186,6 +187,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
186
187
|
integrationName: this.constructor.id,
|
|
187
188
|
})
|
|
188
189
|
this._testSuiteSpansByTestSuite.set(testSuitePath, testSuiteSpan)
|
|
190
|
+
this._exportPendingWorkerTracesForTestSuite(testSuitePath)
|
|
189
191
|
|
|
190
192
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
191
193
|
if (this.libraryConfig?.isCodeCoverageEnabled) {
|
|
@@ -136,6 +136,7 @@ function getRetriedTests (test, numRetries, tags) {
|
|
|
136
136
|
// TODO: signal in framework logs that this is a retry.
|
|
137
137
|
// TODO: Change it so these tests are allowed to fail.
|
|
138
138
|
const clonedTest = test.clone()
|
|
139
|
+
disableFrameworkRetries(clonedTest)
|
|
139
140
|
if (tags.includes('_ddIsEfdRetry')) {
|
|
140
141
|
clonedTest._ddEfdRetryIndex = retryIndex + 1
|
|
141
142
|
}
|
|
@@ -149,6 +150,19 @@ function getRetriedTests (test, numRetries, tags) {
|
|
|
149
150
|
return retriedTests
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
function disableFrameworkRetries (test) {
|
|
154
|
+
test._retries = 0
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function shouldDisableFrameworkRetries (test) {
|
|
158
|
+
return test && (
|
|
159
|
+
test._ddIsAttemptToFix ||
|
|
160
|
+
(isTestIsolationEnabled &&
|
|
161
|
+
isEarlyFlakeDetectionEnabled &&
|
|
162
|
+
(test._ddIsNew || test._ddIsModified))
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
152
166
|
const oldRunTests = Cypress.mocha.getRunner().runTests
|
|
153
167
|
Cypress.mocha.getRunner().runTests = function (suite, fn) {
|
|
154
168
|
if (!isKnownTestsEnabled && !isTestManagementEnabled && !isImpactedTestsEnabled) {
|
|
@@ -188,9 +202,11 @@ Cypress.mocha.getRunner().runTests = function (suite, fn) {
|
|
|
188
202
|
let retryMessage = ''
|
|
189
203
|
if (isAtemptToFix) {
|
|
190
204
|
test._ddIsAttemptToFix = true
|
|
205
|
+
disableFrameworkRetries(test)
|
|
191
206
|
retryMessage = 'because it is an attempt to fix'
|
|
192
207
|
retriedTests = getRetriedTests(test, testManagementAttemptToFixRetries, ['_ddIsAttemptToFix'])
|
|
193
208
|
} else if (isModified && isEarlyFlakeDetectionEnabled) {
|
|
209
|
+
disableFrameworkRetries(test)
|
|
194
210
|
retryMessage = 'to detect flakes because it is modified'
|
|
195
211
|
retriedTests = getRetriedTests(test, earlyFlakeDetectionNumRetries, [
|
|
196
212
|
'_ddIsModified',
|
|
@@ -198,6 +214,7 @@ Cypress.mocha.getRunner().runTests = function (suite, fn) {
|
|
|
198
214
|
isKnownTestsEnabled && isNewTest(test) && '_ddIsNew',
|
|
199
215
|
])
|
|
200
216
|
} else if (isNew && isEarlyFlakeDetectionEnabled) {
|
|
217
|
+
disableFrameworkRetries(test)
|
|
201
218
|
retryMessage = 'to detect flakes because it is new'
|
|
202
219
|
retriedTests = getRetriedTests(test, earlyFlakeDetectionNumRetries, ['_ddIsNew', '_ddIsEfdRetry'])
|
|
203
220
|
}
|
|
@@ -214,8 +231,21 @@ Cypress.mocha.getRunner().runTests = function (suite, fn) {
|
|
|
214
231
|
return oldRunTests.apply(this, [suite, fn])
|
|
215
232
|
}
|
|
216
233
|
|
|
234
|
+
Cypress.on('test:before:run', (attributes, test) => {
|
|
235
|
+
if (shouldDisableFrameworkRetries(test)) {
|
|
236
|
+
disableFrameworkRetries(test)
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
Cypress.on('test:before:run:async', (attributes, test) => {
|
|
241
|
+
if (shouldDisableFrameworkRetries(test)) {
|
|
242
|
+
disableFrameworkRetries(test)
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
|
|
217
246
|
beforeEach(function () {
|
|
218
|
-
const
|
|
247
|
+
const currentTest = Cypress.mocha.getRunner().suite.ctx.currentTest
|
|
248
|
+
const testName = currentTest.fullTitle()
|
|
219
249
|
|
|
220
250
|
const retryMessage = retryReasonsByTestName.get(testName)
|
|
221
251
|
if (retryMessage) {
|
|
@@ -5,7 +5,6 @@ const { URL } = require('url')
|
|
|
5
5
|
const ClientPlugin = require('../../dd-trace/src/plugins/client')
|
|
6
6
|
const { storage } = require('../../datadog-core')
|
|
7
7
|
const tags = require('../../../ext/tags')
|
|
8
|
-
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
9
8
|
const formats = require('../../../ext/formats')
|
|
10
9
|
const HTTP_HEADERS = formats.HTTP_HEADERS
|
|
11
10
|
const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
|
|
@@ -69,8 +68,6 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
69
68
|
this.tracer.inject(span, HTTP_HEADERS, options.headers)
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
analyticsSampler.sample(span, this.config.measured)
|
|
73
|
-
|
|
74
71
|
message.span = span
|
|
75
72
|
message.parentStore = store
|
|
76
73
|
message.currentStore = { ...store, span }
|
|
@@ -34,6 +34,11 @@ class HttpServerPlugin extends ServerPlugin {
|
|
|
34
34
|
if (this.#startConfig === undefined) {
|
|
35
35
|
this.#refreshStartCache()
|
|
36
36
|
}
|
|
37
|
+
// A fresh request has no span yet; web.startSpan creates and enters one.
|
|
38
|
+
// A reused context (HTTP/2 stream, serverless re-entry) returns the
|
|
39
|
+
// existing span without entering, so the explicit enter below is the only
|
|
40
|
+
// one in that case.
|
|
41
|
+
const spanCreated = !web.getContext(req)?.span
|
|
37
42
|
const span = web.startSpan(
|
|
38
43
|
this.tracer,
|
|
39
44
|
this.#startConfig,
|
|
@@ -57,7 +62,12 @@ class HttpServerPlugin extends ServerPlugin {
|
|
|
57
62
|
store = withRequest(store, req)
|
|
58
63
|
}
|
|
59
64
|
|
|
60
|
-
|
|
65
|
+
// Skip the re-enter when web.startSpan already entered { ...store, span }
|
|
66
|
+
// and AppSec did not rebuild the store with req: the bind would be
|
|
67
|
+
// identical to the one startSpan just made.
|
|
68
|
+
if (!spanCreated || appsecActive) {
|
|
69
|
+
this.enter(span, store)
|
|
70
|
+
}
|
|
61
71
|
|
|
62
72
|
if (!context.instrumented) {
|
|
63
73
|
context.res.writeHead = web.wrapWriteHead(context)
|
|
@@ -150,6 +150,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
150
150
|
ctx.parentStore = store
|
|
151
151
|
ctx.currentStore = { ...store, testSuiteSpan }
|
|
152
152
|
this._testSuiteSpansByTestSuite.set(testSuite, testSuiteSpan)
|
|
153
|
+
this._exportPendingWorkerTracesForTestSuite(testSuite)
|
|
153
154
|
})
|
|
154
155
|
|
|
155
156
|
this.addSub('ci:mocha:test-suite:finish', ({ testSuiteSpan, status }) => {
|
|
@@ -365,6 +366,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
365
366
|
isTestManagementEnabled,
|
|
366
367
|
isParallel,
|
|
367
368
|
}) => {
|
|
369
|
+
this._exportPendingWorkerTraces()
|
|
368
370
|
if (this.testSessionSpan) {
|
|
369
371
|
const {
|
|
370
372
|
isSuitesSkippingEnabled,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { storage } = require('../../datadog-core')
|
|
3
4
|
const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
|
|
4
5
|
const DatabasePlugin = require('../../dd-trace/src/plugins/database')
|
|
5
6
|
|
|
@@ -8,6 +9,15 @@ class PGPlugin extends DatabasePlugin {
|
|
|
8
9
|
static operation = 'query'
|
|
9
10
|
static system = 'postgres'
|
|
10
11
|
|
|
12
|
+
constructor () {
|
|
13
|
+
super(...arguments)
|
|
14
|
+
|
|
15
|
+
this.addSub('apm:pg:pool:connect:start', ctx => {
|
|
16
|
+
ctx.parentStore = storage('legacy').getStore()
|
|
17
|
+
})
|
|
18
|
+
this.addBind('apm:pg:pool:connect:finish', ctx => ctx.parentStore)
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
bindStart (ctx) {
|
|
12
22
|
const { params = {}, query, originalText, processId, stream } = ctx
|
|
13
23
|
const service = this.serviceName({ pluginConfig: this.config, params })
|
|
@@ -44,27 +44,46 @@ function disable () {
|
|
|
44
44
|
/**
|
|
45
45
|
* Handles channel messages with pre-converted messages.
|
|
46
46
|
*
|
|
47
|
-
* @param {
|
|
47
|
+
* @param {object} ctx
|
|
48
|
+
* @param {Array<object>} ctx.messages
|
|
49
|
+
* @param {string} [ctx.integration]
|
|
50
|
+
* @param {object} [ctx.parentSpan] - LLM span to parent the `ai_guard` span under.
|
|
51
|
+
* @param {AbortController} ctx.abortController
|
|
52
|
+
* @param {Array<Promise<void>>} ctx.pending - Subscribers push only when they evaluate.
|
|
48
53
|
*/
|
|
49
54
|
function onEvaluate (ctx) {
|
|
55
|
+
// Decline to evaluate empty payloads by not pushing to pending.
|
|
50
56
|
if (!ctx.messages?.length) {
|
|
51
|
-
ctx.resolve()
|
|
52
57
|
return
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
const opts = {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
const opts = {
|
|
61
|
+
block,
|
|
62
|
+
source: SOURCE_AUTO,
|
|
63
|
+
integration: ctx.integration || INTEGRATION_NONE,
|
|
64
|
+
childOf: ctx.parentSpan,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
ctx.pending.push(aiguard.evaluate(ctx.messages, opts).catch(handleEvaluationError.bind(null, ctx)))
|
|
69
|
+
} catch (err) {
|
|
70
|
+
ctx.pending.push(Promise.resolve().then(() => handleEvaluationError(ctx, err)))
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Handles an AI Guard evaluation failure.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} ctx
|
|
78
|
+
* @param {AbortController} ctx.abortController
|
|
79
|
+
* @param {Error} err
|
|
80
|
+
*/
|
|
81
|
+
function handleEvaluationError (ctx, err) {
|
|
82
|
+
if (err.name === 'AIGuardAbortError') {
|
|
83
|
+
ctx.abortController.abort(err)
|
|
84
|
+
} else {
|
|
85
|
+
log.error('AIGuard: unexpected error during evaluation: %s', err.message)
|
|
86
|
+
}
|
|
68
87
|
}
|
|
69
88
|
|
|
70
89
|
module.exports = { enable, disable }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
|
|
4
|
-
const { HTTP_CLIENT_IP, NETWORK_CLIENT_IP } = require('../../../../ext/tags')
|
|
4
|
+
const { HTTP_CLIENT_IP, NETWORK_CLIENT_IP, HTTP_USERAGENT } = require('../../../../ext/tags')
|
|
5
|
+
const { USER_ID, USER_SESSION_ID } = require('../appsec/addresses')
|
|
5
6
|
const { getActiveRequest } = require('../appsec/store')
|
|
6
7
|
const log = require('../log')
|
|
7
8
|
const { extractIp } = require('../plugins/util/ip_extractor')
|
|
@@ -17,6 +18,16 @@ const aiguardMetrics = telemetryMetrics.manager.namespace('ai_guard')
|
|
|
17
18
|
|
|
18
19
|
const ALLOW = 'ALLOW'
|
|
19
20
|
|
|
21
|
+
// Tags from the service entry span that must be mirrored onto every AI Guard span
|
|
22
|
+
// so anomaly detection pipelines can process each span independently.
|
|
23
|
+
const SERVICE_ENTRY_TAG_MAPPINGS = [
|
|
24
|
+
[HTTP_USERAGENT, TAGS.HTTP_USERAGENT_TAG_KEY],
|
|
25
|
+
[HTTP_CLIENT_IP, TAGS.HTTP_CLIENT_IP_TAG_KEY],
|
|
26
|
+
[NETWORK_CLIENT_IP, TAGS.NETWORK_CLIENT_IP_TAG_KEY],
|
|
27
|
+
[USER_ID, TAGS.USR_ID_TAG_KEY],
|
|
28
|
+
[USER_SESSION_ID, TAGS.USR_SESSION_ID_TAG_KEY],
|
|
29
|
+
]
|
|
30
|
+
|
|
20
31
|
/**
|
|
21
32
|
* Reports a telemetry error
|
|
22
33
|
*
|
|
@@ -181,13 +192,32 @@ class AIGuard extends NoopAIGuard {
|
|
|
181
192
|
}
|
|
182
193
|
}
|
|
183
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Copies service entry span tags needed by anomaly detection to the AI Guard span.
|
|
197
|
+
*
|
|
198
|
+
* @param {import('../opentracing/span')} guardSpan
|
|
199
|
+
* @param {import('../opentracing/span')} rootSpan
|
|
200
|
+
*/
|
|
201
|
+
#copyServiceEntryTagsToGuardSpan (guardSpan, rootSpan) {
|
|
202
|
+
const rootTags = rootSpan.context().getTags()
|
|
203
|
+
for (const [sourceTag, destTag] of SERVICE_ENTRY_TAG_MAPPINGS) {
|
|
204
|
+
const value = rootTags[sourceTag]
|
|
205
|
+
if (value !== undefined && value !== null) {
|
|
206
|
+
guardSpan.setTag(destTag, value)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
184
211
|
evaluate (messages, opts) {
|
|
185
212
|
if (!this.#initialized) {
|
|
186
213
|
return super.evaluate(messages, opts)
|
|
187
214
|
}
|
|
188
|
-
const { block = true, source = TAGS.SOURCE_SDK, integration = TAGS.INTEGRATION_NONE } = opts ?? {}
|
|
215
|
+
const { block = true, source = TAGS.SOURCE_SDK, integration = TAGS.INTEGRATION_NONE, childOf } = opts ?? {}
|
|
189
216
|
const telemetryTags = { source, integration }
|
|
190
|
-
|
|
217
|
+
// Only pass `childOf` when truthy so `tracer.trace`'s default (`scope().active()`)
|
|
218
|
+
// still applies for SDK callers that don't supply an explicit parent.
|
|
219
|
+
const traceOpts = childOf ? { childOf } : {}
|
|
220
|
+
return this.#tracer.trace(TAGS.RESOURCE, traceOpts, async (span) => {
|
|
191
221
|
const last = messages[messages.length - 1]
|
|
192
222
|
const target = this.#isToolCall(last) ? 'tool' : 'prompt'
|
|
193
223
|
span.setTag(TAGS.TARGET_TAG_KEY, target)
|
|
@@ -206,6 +236,7 @@ class AIGuard extends NoopAIGuard {
|
|
|
206
236
|
const rootSpan = span.context()?._trace?.started?.[0]
|
|
207
237
|
if (rootSpan) {
|
|
208
238
|
this.#setRootSpanClientIpTags(rootSpan)
|
|
239
|
+
this.#copyServiceEntryTagsToGuardSpan(span, rootSpan)
|
|
209
240
|
// keepTrace must be called before executeRequest so the sampling decision
|
|
210
241
|
// is propagated correctly to outgoing HTTP client calls.
|
|
211
242
|
keepTrace(rootSpan, AI_GUARD)
|
|
@@ -10,6 +10,12 @@ module.exports = {
|
|
|
10
10
|
EVENT_TAG_KEY: 'ai_guard.event',
|
|
11
11
|
META_STRUCT_KEY: 'ai_guard',
|
|
12
12
|
|
|
13
|
+
HTTP_USERAGENT_TAG_KEY: 'ai_guard.http.useragent',
|
|
14
|
+
HTTP_CLIENT_IP_TAG_KEY: 'ai_guard.http.client_ip',
|
|
15
|
+
NETWORK_CLIENT_IP_TAG_KEY: 'ai_guard.network.client.ip',
|
|
16
|
+
USR_ID_TAG_KEY: 'ai_guard.usr.id',
|
|
17
|
+
USR_SESSION_ID_TAG_KEY: 'ai_guard.usr.session_id',
|
|
18
|
+
|
|
13
19
|
TELEMETRY_REQUESTS: 'requests',
|
|
14
20
|
TELEMETRY_TRUNCATED: 'truncated',
|
|
15
21
|
TELEMETRY_ERROR: 'error',
|
|
@@ -9,7 +9,7 @@ const CoverageWriter = require('./coverage-writer')
|
|
|
9
9
|
class AgentlessCiVisibilityExporter extends CiVisibilityExporter {
|
|
10
10
|
constructor (config) {
|
|
11
11
|
super(config)
|
|
12
|
-
const { tags, site, url, isTestDynamicInstrumentationEnabled } = config
|
|
12
|
+
const { tags, site, DD_CIVISIBILITY_AGENTLESS_URL: url, isTestDynamicInstrumentationEnabled } = config
|
|
13
13
|
// we don't need to request /info because we are using agentless by configuration
|
|
14
14
|
this._isInitialized = true
|
|
15
15
|
this._resolveCanUseCiVisProtocol(true)
|
|
@@ -72,6 +72,12 @@ for (const [name, value] of Object.entries(defaults)) {
|
|
|
72
72
|
function generateTelemetry (value = null, origin, optionName) {
|
|
73
73
|
const tableEntry = configurationsTable[optionName]
|
|
74
74
|
const { type, canonicalName = optionName } = tableEntry ?? { type: typeof value }
|
|
75
|
+
// Sensitive configurations are excluded from configuration telemetry: their
|
|
76
|
+
// entry is never added to `configWithOrigin`, the single source for every
|
|
77
|
+
// telemetry path (app-started and app-client-configuration-change).
|
|
78
|
+
if (sensitiveConfigurations.has(canonicalName)) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
75
81
|
// TODO: Should we not send defaults to telemetry to reduce size?
|
|
76
82
|
// TODO: How to handle aliases/actual names in the future? Optional fields? Normalize the name at intake?
|
|
77
83
|
// TODO: Validate that space separated tags are parsed by the backend. Optimizations would be possible with that.
|
|
@@ -196,6 +202,11 @@ const fallbackConfigurations = new Map()
|
|
|
196
202
|
|
|
197
203
|
const regExps = {}
|
|
198
204
|
|
|
205
|
+
// Canonical names of configurations whose value is excluded from configuration
|
|
206
|
+
// telemetry. Driven by the `sensitive: true` attribute in
|
|
207
|
+
// `supported-configurations.json` so new entries opt in without code changes.
|
|
208
|
+
const sensitiveConfigurations = new Set()
|
|
209
|
+
|
|
199
210
|
for (const [canonicalName, entries] of Object.entries(supportedConfigurations)) {
|
|
200
211
|
if (entries.length !== 1) {
|
|
201
212
|
// TODO: Determine if we really want to support multiple entries for a canonical name.
|
|
@@ -207,6 +218,9 @@ for (const [canonicalName, entries] of Object.entries(supportedConfigurations))
|
|
|
207
218
|
)
|
|
208
219
|
}
|
|
209
220
|
for (const entry of entries) {
|
|
221
|
+
if (entry.sensitive) {
|
|
222
|
+
sensitiveConfigurations.add(canonicalName)
|
|
223
|
+
}
|
|
210
224
|
const configurationNames = entry.internalPropertyName ? [entry.internalPropertyName] : entry.configurationNames
|
|
211
225
|
const fullPropertyName = configurationNames?.[0] ?? canonicalName
|
|
212
226
|
const type = entry.type.toUpperCase()
|
|
@@ -73,7 +73,7 @@ export interface GeneratedConfig {
|
|
|
73
73
|
DD_APP_KEY: string | undefined;
|
|
74
74
|
DD_AZURE_RESOURCE_GROUP: string | undefined;
|
|
75
75
|
DD_CIVISIBILITY_AGENTLESS_ENABLED: boolean;
|
|
76
|
-
DD_CIVISIBILITY_AGENTLESS_URL:
|
|
76
|
+
DD_CIVISIBILITY_AGENTLESS_URL: URL | undefined;
|
|
77
77
|
DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER: string | undefined;
|
|
78
78
|
DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE: boolean;
|
|
79
79
|
DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING: boolean;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs')
|
|
4
4
|
const os = require('node:os')
|
|
5
|
-
const { URL } = require('node:url')
|
|
5
|
+
const { URL, format } = require('node:url')
|
|
6
6
|
|
|
7
7
|
const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
|
|
8
8
|
const uuid = require('../../../../vendor/dist/crypto-randomuuid') // we need to keep the old uuid dep because of cypress
|
|
@@ -322,18 +322,14 @@ class Config extends ConfigBase {
|
|
|
322
322
|
#applyCalculated () {
|
|
323
323
|
undo(this, 'calculated')
|
|
324
324
|
|
|
325
|
-
if (this.
|
|
326
|
-
this.url ||
|
|
325
|
+
if (this.url ||
|
|
327
326
|
os.type() !== 'Windows_NT' &&
|
|
328
327
|
!trackedConfigOrigins.has('hostname') &&
|
|
329
328
|
!trackedConfigOrigins.has('port') &&
|
|
330
|
-
!this.DD_CIVISIBILITY_AGENTLESS_ENABLED &&
|
|
331
329
|
fs.existsSync('/var/run/datadog/apm.socket')) {
|
|
332
|
-
setAndTrack(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
new URL(this.DD_CIVISIBILITY_AGENTLESS_URL || this.url || 'unix:///var/run/datadog/apm.socket')
|
|
336
|
-
)
|
|
330
|
+
setAndTrack(this, 'url', new URL(this.url || 'unix:///var/run/datadog/apm.socket'))
|
|
331
|
+
} else {
|
|
332
|
+
setAndTrack(this, 'url', new URL(format({ protocol: 'http:', hostname: this.hostname, port: this.port })))
|
|
337
333
|
}
|
|
338
334
|
|
|
339
335
|
if (this.isCiVisibility) {
|
|
@@ -145,6 +145,14 @@ const transformers = {
|
|
|
145
145
|
}
|
|
146
146
|
return value.replaceAll(/\s*:\s*/g, ':')
|
|
147
147
|
},
|
|
148
|
+
/**
|
|
149
|
+
* @param {string} value
|
|
150
|
+
*/
|
|
151
|
+
toURL (value) {
|
|
152
|
+
try {
|
|
153
|
+
return new URL(value)
|
|
154
|
+
} catch {}
|
|
155
|
+
},
|
|
148
156
|
validatePropagationStyles (value, optionName) {
|
|
149
157
|
value = transformers.toLowerCase(value)
|
|
150
158
|
for (let index = 0; index < value.length; index++) {
|
|
@@ -111,7 +111,8 @@
|
|
|
111
111
|
"aliases": [
|
|
112
112
|
"DATADOG_API_KEY"
|
|
113
113
|
],
|
|
114
|
-
"internalPropertyName": "apiKey"
|
|
114
|
+
"internalPropertyName": "apiKey",
|
|
115
|
+
"sensitive": true
|
|
115
116
|
}
|
|
116
117
|
],
|
|
117
118
|
"DD_API_SECURITY_ENABLED": [
|
|
@@ -423,7 +424,8 @@
|
|
|
423
424
|
{
|
|
424
425
|
"implementation": "A",
|
|
425
426
|
"type": "string",
|
|
426
|
-
"default": null
|
|
427
|
+
"default": null,
|
|
428
|
+
"sensitive": true
|
|
427
429
|
}
|
|
428
430
|
],
|
|
429
431
|
"DD_AZURE_RESOURCE_GROUP": [
|
|
@@ -444,6 +446,7 @@
|
|
|
444
446
|
{
|
|
445
447
|
"implementation": "A",
|
|
446
448
|
"type": "string",
|
|
449
|
+
"transform": "toURL",
|
|
447
450
|
"default": null
|
|
448
451
|
}
|
|
449
452
|
],
|
|
@@ -3913,7 +3916,8 @@
|
|
|
3913
3916
|
{
|
|
3914
3917
|
"implementation": "B",
|
|
3915
3918
|
"type": "map",
|
|
3916
|
-
"default": null
|
|
3919
|
+
"default": null,
|
|
3920
|
+
"sensitive": true
|
|
3917
3921
|
}
|
|
3918
3922
|
],
|
|
3919
3923
|
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": [
|
|
@@ -3930,7 +3934,8 @@
|
|
|
3930
3934
|
"default": null,
|
|
3931
3935
|
"aliases": [
|
|
3932
3936
|
"OTEL_EXPORTER_OTLP_HEADERS"
|
|
3933
|
-
]
|
|
3937
|
+
],
|
|
3938
|
+
"sensitive": true
|
|
3934
3939
|
}
|
|
3935
3940
|
],
|
|
3936
3941
|
"OTEL_EXPORTER_OTLP_TRACES_PROTOCOL": [
|
|
@@ -3968,7 +3973,8 @@
|
|
|
3968
3973
|
"default": null,
|
|
3969
3974
|
"aliases": [
|
|
3970
3975
|
"OTEL_EXPORTER_OTLP_HEADERS"
|
|
3971
|
-
]
|
|
3976
|
+
],
|
|
3977
|
+
"sensitive": true
|
|
3972
3978
|
}
|
|
3973
3979
|
],
|
|
3974
3980
|
"OTEL_EXPORTER_OTLP_LOGS_PROTOCOL": [
|
|
@@ -4006,7 +4012,8 @@
|
|
|
4006
4012
|
"default": null,
|
|
4007
4013
|
"aliases": [
|
|
4008
4014
|
"OTEL_EXPORTER_OTLP_HEADERS"
|
|
4009
|
-
]
|
|
4015
|
+
],
|
|
4016
|
+
"sensitive": true
|
|
4010
4017
|
}
|
|
4011
4018
|
],
|
|
4012
4019
|
"OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": [
|
|
@@ -7,7 +7,6 @@ const libdatadog = require('@datadog/libdatadog')
|
|
|
7
7
|
const binding = libdatadog.load('crashtracker')
|
|
8
8
|
|
|
9
9
|
const log = require('../log')
|
|
10
|
-
const { getAgentUrl } = require('../agent/url')
|
|
11
10
|
const pkg = require('../../../../package.json')
|
|
12
11
|
const processTags = require('../process-tags')
|
|
13
12
|
|
|
@@ -68,7 +67,7 @@ class Crashtracker {
|
|
|
68
67
|
* @param {import('../config/config-base')} config - Tracer configuration
|
|
69
68
|
*/
|
|
70
69
|
#getConfig (config) {
|
|
71
|
-
const url =
|
|
70
|
+
const url = config.url
|
|
72
71
|
|
|
73
72
|
// Out-of-process symbolication currently works on
|
|
74
73
|
// Linux only, does not work on Mac.
|
|
@@ -78,6 +77,7 @@ class Crashtracker {
|
|
|
78
77
|
|
|
79
78
|
return {
|
|
80
79
|
additional_files: [],
|
|
80
|
+
collect_all_threads: true,
|
|
81
81
|
create_alt_stack: true,
|
|
82
82
|
use_alt_stack: true,
|
|
83
83
|
endpoint: {
|
|
@@ -5,7 +5,6 @@ const pkg = require('../../../../package.json')
|
|
|
5
5
|
const log = require('../log')
|
|
6
6
|
const request = require('../exporters/common/request')
|
|
7
7
|
const { encode: encodeMsgpack } = require('../msgpack')
|
|
8
|
-
const { getAgentUrl } = require('../agent/url')
|
|
9
8
|
|
|
10
9
|
function makeRequest (data, url, cb) {
|
|
11
10
|
const options = {
|
|
@@ -29,7 +28,7 @@ function makeRequest (data, url, cb) {
|
|
|
29
28
|
|
|
30
29
|
class DataStreamsWriter {
|
|
31
30
|
constructor (config) {
|
|
32
|
-
this._url =
|
|
31
|
+
this._url = config.url
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
flush (payload) {
|
|
@@ -16,7 +16,7 @@ module.exports = function getDebuggerConfig (config, inputPath) {
|
|
|
16
16
|
repositoryUrl,
|
|
17
17
|
runtimeId: config.tags['runtime-id'],
|
|
18
18
|
service: config.service,
|
|
19
|
-
url: config.url
|
|
19
|
+
url: config.url.toString(),
|
|
20
20
|
version: config.version,
|
|
21
21
|
inputPath,
|
|
22
22
|
}
|