dd-trace 5.74.0 → 5.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +1 -0
- package/index.d.ts +11 -7
- package/loader-hook.mjs +52 -1
- package/package.json +5 -16
- package/packages/datadog-esbuild/index.js +1 -0
- package/packages/datadog-instrumentations/src/cookie-parser.js +0 -2
- package/packages/datadog-instrumentations/src/cucumber.js +2 -2
- package/packages/datadog-instrumentations/src/express-session.js +0 -1
- package/packages/datadog-instrumentations/src/mariadb.js +9 -7
- package/packages/datadog-instrumentations/src/playwright.js +116 -37
- package/packages/datadog-instrumentations/src/vitest.js +44 -12
- package/packages/datadog-plugin-aws-sdk/src/base.js +0 -1
- package/packages/datadog-plugin-playwright/src/index.js +74 -31
- package/packages/datadog-shimmer/src/shimmer.js +2 -0
- package/packages/dd-trace/src/aiguard/sdk.js +25 -3
- package/packages/dd-trace/src/aiguard/tags.js +4 -1
- package/packages/dd-trace/src/config-helper.js +1 -0
- package/packages/dd-trace/src/config.js +345 -329
- package/packages/dd-trace/src/config_defaults.js +9 -0
- package/packages/dd-trace/src/proxy.js +1 -1
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +5 -3
- package/register.js +1 -11
|
@@ -47,6 +47,7 @@ const {
|
|
|
47
47
|
TELEMETRY_EVENT_FINISHED
|
|
48
48
|
} = require('../../dd-trace/src/ci-visibility/telemetry')
|
|
49
49
|
const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry')
|
|
50
|
+
const log = require('../../dd-trace/src/log')
|
|
50
51
|
|
|
51
52
|
class PlaywrightPlugin extends CiPlugin {
|
|
52
53
|
static id = 'playwright'
|
|
@@ -174,7 +175,10 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
174
175
|
}) => {
|
|
175
176
|
const store = storage('legacy').getStore()
|
|
176
177
|
const span = store && store.span
|
|
177
|
-
if (!span)
|
|
178
|
+
if (!span) {
|
|
179
|
+
log.error('ci:playwright:test:page-goto: test span not found')
|
|
180
|
+
return
|
|
181
|
+
}
|
|
178
182
|
|
|
179
183
|
if (isRumActive) {
|
|
180
184
|
span.setTag(TEST_IS_RUM_ACTIVE, 'true')
|
|
@@ -198,36 +202,6 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
198
202
|
}
|
|
199
203
|
})
|
|
200
204
|
|
|
201
|
-
this.addBind('ci:playwright:test:start', (ctx) => {
|
|
202
|
-
const {
|
|
203
|
-
testName,
|
|
204
|
-
testSuiteAbsolutePath,
|
|
205
|
-
testSourceLine,
|
|
206
|
-
browserName,
|
|
207
|
-
isDisabled
|
|
208
|
-
} = ctx
|
|
209
|
-
const store = storage('legacy').getStore()
|
|
210
|
-
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
|
|
211
|
-
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
212
|
-
const span = this.startTestSpan(
|
|
213
|
-
testName,
|
|
214
|
-
testSuiteAbsolutePath,
|
|
215
|
-
testSuite,
|
|
216
|
-
testSourceFile,
|
|
217
|
-
testSourceLine,
|
|
218
|
-
browserName
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
if (isDisabled) {
|
|
222
|
-
span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
ctx.parentStore = store
|
|
226
|
-
ctx.currentStore = { ...store, span }
|
|
227
|
-
|
|
228
|
-
return ctx.currentStore
|
|
229
|
-
})
|
|
230
|
-
|
|
231
205
|
this.addSub('ci:playwright:worker:report', (serializedTraces) => {
|
|
232
206
|
const traces = JSON.parse(serializedTraces)
|
|
233
207
|
const formattedTraces = []
|
|
@@ -277,6 +251,36 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
277
251
|
})
|
|
278
252
|
})
|
|
279
253
|
|
|
254
|
+
this.addBind('ci:playwright:test:start', (ctx) => {
|
|
255
|
+
const {
|
|
256
|
+
testName,
|
|
257
|
+
testSuiteAbsolutePath,
|
|
258
|
+
testSourceLine,
|
|
259
|
+
browserName,
|
|
260
|
+
isDisabled
|
|
261
|
+
} = ctx
|
|
262
|
+
const store = storage('legacy').getStore()
|
|
263
|
+
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
|
|
264
|
+
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
265
|
+
const span = this.startTestSpan(
|
|
266
|
+
testName,
|
|
267
|
+
testSuiteAbsolutePath,
|
|
268
|
+
testSuite,
|
|
269
|
+
testSourceFile,
|
|
270
|
+
testSourceLine,
|
|
271
|
+
browserName
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if (isDisabled) {
|
|
275
|
+
span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
ctx.parentStore = store
|
|
279
|
+
ctx.currentStore = { ...store, span }
|
|
280
|
+
|
|
281
|
+
return ctx.currentStore
|
|
282
|
+
})
|
|
283
|
+
|
|
280
284
|
this.addSub('ci:playwright:test:finish', ({
|
|
281
285
|
span,
|
|
282
286
|
testStatus,
|
|
@@ -393,6 +397,45 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
393
397
|
this.tracer._exporter.flush(onDone)
|
|
394
398
|
}
|
|
395
399
|
})
|
|
400
|
+
|
|
401
|
+
this.addSub('ci:playwright:test:skip', ({
|
|
402
|
+
testName,
|
|
403
|
+
testSuiteAbsolutePath,
|
|
404
|
+
testSourceLine,
|
|
405
|
+
browserName,
|
|
406
|
+
isNew,
|
|
407
|
+
isDisabled,
|
|
408
|
+
isModified,
|
|
409
|
+
isQuarantined
|
|
410
|
+
}) => {
|
|
411
|
+
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
|
|
412
|
+
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
413
|
+
const span = this.startTestSpan(
|
|
414
|
+
testName,
|
|
415
|
+
testSuiteAbsolutePath,
|
|
416
|
+
testSuite,
|
|
417
|
+
testSourceFile,
|
|
418
|
+
testSourceLine,
|
|
419
|
+
browserName
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
span.setTag(TEST_STATUS, 'skip')
|
|
423
|
+
|
|
424
|
+
if (isNew) {
|
|
425
|
+
span.setTag(TEST_IS_NEW, 'true')
|
|
426
|
+
}
|
|
427
|
+
if (isDisabled) {
|
|
428
|
+
span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
|
|
429
|
+
}
|
|
430
|
+
if (isModified) {
|
|
431
|
+
span.setTag(TEST_IS_MODIFIED, 'true')
|
|
432
|
+
}
|
|
433
|
+
if (isQuarantined) {
|
|
434
|
+
span.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
span.finish()
|
|
438
|
+
})
|
|
396
439
|
}
|
|
397
440
|
|
|
398
441
|
// TODO: this runs both in worker and main process (main process: skipped tests that do not go through _runTest)
|
|
@@ -73,6 +73,8 @@ function copyObjectProperties (original, wrapped, skipKey) {
|
|
|
73
73
|
* @returns {Function} The wrapped function.
|
|
74
74
|
*/
|
|
75
75
|
function wrapFunction (original, wrapper) {
|
|
76
|
+
if (typeof original !== 'function') return original
|
|
77
|
+
|
|
76
78
|
const wrapped = wrapper(original)
|
|
77
79
|
|
|
78
80
|
if (typeof original === 'function') {
|
|
@@ -9,9 +9,15 @@ const {
|
|
|
9
9
|
AI_GUARD_ACTION_TAG_KEY,
|
|
10
10
|
AI_GUARD_BLOCKED_TAG_KEY,
|
|
11
11
|
AI_GUARD_META_STRUCT_KEY,
|
|
12
|
-
AI_GUARD_TOOL_NAME_TAG_KEY
|
|
12
|
+
AI_GUARD_TOOL_NAME_TAG_KEY,
|
|
13
|
+
AI_GUARD_TELEMETRY_REQUESTS,
|
|
14
|
+
AI_GUARD_TELEMETRY_TRUNCATED
|
|
13
15
|
} = require('./tags')
|
|
14
16
|
const log = require('../log')
|
|
17
|
+
const telemetryMetrics = require('../telemetry/metrics')
|
|
18
|
+
const tracerVersion = require('../../../../package.json').version
|
|
19
|
+
|
|
20
|
+
const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
|
|
15
21
|
|
|
16
22
|
const ALLOW = 'ALLOW'
|
|
17
23
|
|
|
@@ -58,6 +64,9 @@ class AIGuard extends NoopAIGuard {
|
|
|
58
64
|
this.#headers = {
|
|
59
65
|
'DD-API-KEY': config.apiKey,
|
|
60
66
|
'DD-APPLICATION-KEY': config.appKey,
|
|
67
|
+
'DD-AI-GUARD-VERSION': tracerVersion,
|
|
68
|
+
'DD-AI-GUARD-SOURCE': 'SDK',
|
|
69
|
+
'DD-AI-GUARD-LANGUAGE': 'nodejs'
|
|
61
70
|
}
|
|
62
71
|
const endpoint = config.experimental.aiguard.endpoint || `https://app.${config.site}/api/v2/ai-guard`
|
|
63
72
|
this.#evaluateUrl = `${endpoint}/evaluate`
|
|
@@ -70,14 +79,22 @@ class AIGuard extends NoopAIGuard {
|
|
|
70
79
|
|
|
71
80
|
#truncate (messages) {
|
|
72
81
|
const size = Math.min(messages.length, this.#maxMessagesLength)
|
|
82
|
+
if (messages.length > size) {
|
|
83
|
+
appsecMetrics.count(AI_GUARD_TELEMETRY_TRUNCATED, { type: 'messages' }).inc(1)
|
|
84
|
+
}
|
|
73
85
|
const result = messages.slice(-size)
|
|
74
86
|
|
|
87
|
+
let contentTruncated = false
|
|
75
88
|
for (let i = 0; i < size; i++) {
|
|
76
89
|
const message = result[i]
|
|
77
90
|
if (message.content?.length > this.#maxContentSize) {
|
|
91
|
+
contentTruncated = true
|
|
78
92
|
result[i] = { ...message, content: message.content.slice(0, this.#maxContentSize) }
|
|
79
93
|
}
|
|
80
94
|
}
|
|
95
|
+
if (contentTruncated) {
|
|
96
|
+
appsecMetrics.count(AI_GUARD_TELEMETRY_TRUNCATED, { type: 'content' }).inc(1)
|
|
97
|
+
}
|
|
81
98
|
return result
|
|
82
99
|
}
|
|
83
100
|
|
|
@@ -140,9 +157,11 @@ class AIGuard extends NoopAIGuard {
|
|
|
140
157
|
payload,
|
|
141
158
|
{ url: this.#evaluateUrl, headers: this.#headers, timeout: this.#timeout })
|
|
142
159
|
} catch (e) {
|
|
143
|
-
|
|
160
|
+
appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
|
|
161
|
+
throw new AIGuardClientError(`Unexpected error calling AI Guard service: ${e.message}`, { cause: e })
|
|
144
162
|
}
|
|
145
163
|
if (response.status !== 200) {
|
|
164
|
+
appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
|
|
146
165
|
throw new AIGuardClientError(
|
|
147
166
|
`AI Guard service call failed, status ${response.status}`,
|
|
148
167
|
{ errors: response.body?.errors })
|
|
@@ -157,11 +176,14 @@ class AIGuard extends NoopAIGuard {
|
|
|
157
176
|
reason = attr.reason
|
|
158
177
|
blockingEnabled = attr.is_blocking_enabled ?? false
|
|
159
178
|
} catch (e) {
|
|
179
|
+
appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
|
|
160
180
|
throw new AIGuardClientError(`AI Guard service returned unexpected response : ${response.body}`, { cause: e })
|
|
161
181
|
}
|
|
182
|
+
const shouldBlock = block && blockingEnabled && action !== ALLOW
|
|
183
|
+
appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { action, error: false, block: shouldBlock }).inc(1)
|
|
162
184
|
span.setTag(AI_GUARD_ACTION_TAG_KEY, action)
|
|
163
185
|
span.setTag(AI_GUARD_REASON_TAG_KEY, reason)
|
|
164
|
-
if (
|
|
186
|
+
if (shouldBlock) {
|
|
165
187
|
span.setTag(AI_GUARD_BLOCKED_TAG_KEY, 'true')
|
|
166
188
|
throw new AIGuardAbortError(reason)
|
|
167
189
|
}
|
|
@@ -7,5 +7,8 @@ module.exports = {
|
|
|
7
7
|
AI_GUARD_ACTION_TAG_KEY: 'ai_guard.action',
|
|
8
8
|
AI_GUARD_REASON_TAG_KEY: 'ai_guard.reason',
|
|
9
9
|
AI_GUARD_BLOCKED_TAG_KEY: 'ai_guard.blocked',
|
|
10
|
-
AI_GUARD_META_STRUCT_KEY: 'ai_guard'
|
|
10
|
+
AI_GUARD_META_STRUCT_KEY: 'ai_guard',
|
|
11
|
+
|
|
12
|
+
AI_GUARD_TELEMETRY_REQUESTS: 'ai_guard.requests',
|
|
13
|
+
AI_GUARD_TELEMETRY_TRUNCATED: 'ai_guard.truncated'
|
|
11
14
|
}
|
|
@@ -80,6 +80,7 @@ module.exports = {
|
|
|
80
80
|
* @returns {string|undefined}
|
|
81
81
|
* @throws {Error} if the configuration is not supported
|
|
82
82
|
*/
|
|
83
|
+
// This method, and callers of this method, need to be updated to check for declarative config sources as well.
|
|
83
84
|
getEnvironmentVariable (name) {
|
|
84
85
|
if ((name.startsWith('DD_') || name.startsWith('OTEL_') || aliasToCanonical[name]) &&
|
|
85
86
|
!supportedConfigurations[name]) {
|