dd-trace 5.67.0 → 5.69.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 +6 -4
- package/README.md +0 -2
- package/ci/init.js +52 -54
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/index.d.ts +240 -3
- package/initialize.mjs +1 -1
- package/package.json +17 -11
- package/packages/datadog-core/src/storage.js +14 -13
- package/packages/datadog-esbuild/index.js +118 -26
- package/packages/datadog-instrumentations/src/aws-sdk.js +42 -4
- package/packages/datadog-instrumentations/src/azure-functions.js +1 -1
- package/packages/datadog-instrumentations/src/azure-service-bus.js +1 -1
- package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
- package/packages/datadog-instrumentations/src/connect.js +6 -2
- package/packages/datadog-instrumentations/src/cucumber.js +31 -6
- package/packages/datadog-instrumentations/src/express.js +5 -6
- package/packages/datadog-instrumentations/src/fastify.js +3 -3
- package/packages/datadog-instrumentations/src/helpers/hook.js +28 -15
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +15 -5
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -3
- package/packages/datadog-instrumentations/src/http2/client.js +1 -0
- package/packages/datadog-instrumentations/src/http2/server.js +0 -1
- package/packages/datadog-instrumentations/src/ioredis.js +12 -1
- package/packages/datadog-instrumentations/src/jest.js +48 -36
- package/packages/datadog-instrumentations/src/limitd-client.js +2 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +15 -7
- package/packages/datadog-instrumentations/src/mocha/utils.js +3 -0
- package/packages/datadog-instrumentations/src/mongoose.js +2 -1
- package/packages/datadog-instrumentations/src/oracledb.js +19 -13
- package/packages/datadog-instrumentations/src/pg.js +9 -5
- package/packages/datadog-instrumentations/src/pino.js +18 -6
- package/packages/datadog-instrumentations/src/playwright.js +15 -1
- package/packages/datadog-instrumentations/src/sequelize.js +1 -1
- package/packages/datadog-instrumentations/src/vitest.js +155 -62
- package/packages/datadog-plugin-ai/src/tracing.js +3 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +23 -8
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +101 -2
- package/packages/datadog-plugin-aws-sdk/src/util.js +1 -1
- package/packages/datadog-plugin-confluentinc-kafka-javascript/src/index.js +6 -0
- package/packages/datadog-plugin-cucumber/src/index.js +4 -56
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +6 -3
- package/packages/datadog-plugin-cypress/src/support.js +4 -0
- package/packages/datadog-plugin-express/src/code_origin.js +2 -2
- package/packages/datadog-plugin-fastify/src/code_origin.js +1 -2
- package/packages/datadog-plugin-jest/src/index.js +0 -21
- package/packages/datadog-plugin-mocha/src/index.js +3 -57
- package/packages/datadog-plugin-mongodb-core/src/index.js +38 -12
- package/packages/datadog-plugin-playwright/src/index.js +11 -5
- package/packages/datadog-plugin-vitest/src/index.js +5 -1
- package/packages/datadog-plugin-ws/src/close.js +1 -1
- package/packages/datadog-plugin-ws/src/producer.js +6 -1
- package/packages/datadog-plugin-ws/src/receiver.js +6 -1
- package/packages/dd-trace/src/aiguard/client.js +25 -0
- package/packages/dd-trace/src/aiguard/noop.js +9 -0
- package/packages/dd-trace/src/aiguard/sdk.js +173 -0
- package/packages/dd-trace/src/aiguard/tags.js +11 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +21 -4
- package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +6 -3
- package/packages/dd-trace/src/appsec/stack_trace.js +20 -1
- package/packages/dd-trace/src/appsec/telemetry/waf.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -4
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +11 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -1
- package/packages/dd-trace/src/config-helper.js +8 -1
- package/packages/dd-trace/src/config.js +92 -304
- package/packages/dd-trace/src/config_defaults.js +191 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -1
- package/packages/dd-trace/src/datastreams/fnv.js +2 -2
- package/packages/dd-trace/src/datastreams/index.js +23 -1
- package/packages/dd-trace/src/datastreams/writer.js +3 -2
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
- package/packages/dd-trace/src/dogstatsd.js +4 -3
- package/packages/dd-trace/src/encode/0.4.js +1 -5
- package/packages/dd-trace/src/exporter.js +1 -0
- package/packages/dd-trace/src/exporters/agent/index.js +3 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +1 -1
- package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +3 -2
- package/packages/dd-trace/src/exporters/common/request.js +2 -1
- package/packages/dd-trace/src/exporters/span-stats/index.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +15 -4
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +20 -7
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +40 -13
- package/packages/dd-trace/src/llmobs/plugins/openai.js +7 -1
- package/packages/dd-trace/src/llmobs/tagger.js +8 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/log/index.js +27 -16
- package/packages/dd-trace/src/log/log.js +29 -5
- package/packages/dd-trace/src/log/writer.js +5 -5
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/noop/span.js +1 -0
- package/packages/dd-trace/src/opentelemetry/span.js +14 -3
- package/packages/dd-trace/src/opentracing/span.js +19 -5
- package/packages/dd-trace/src/payload-tagging/config/index.js +16 -0
- package/packages/dd-trace/src/payload-tagging/index.js +26 -15
- package/packages/dd-trace/src/payload-tagging/tagging.js +17 -8
- package/packages/dd-trace/src/pkg.js +3 -1
- package/packages/dd-trace/src/plugin_manager.js +20 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +97 -3
- package/packages/dd-trace/src/plugins/composite.js +3 -0
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/plugin.js +67 -0
- package/packages/dd-trace/src/plugins/util/git-cache.js +129 -0
- package/packages/dd-trace/src/plugins/util/git.js +41 -27
- package/packages/dd-trace/src/plugins/util/test.js +56 -27
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- package/packages/dd-trace/src/priority_sampler.js +70 -46
- package/packages/dd-trace/src/profiler.js +4 -1
- package/packages/dd-trace/src/profiling/config.js +73 -42
- package/packages/dd-trace/src/profiling/profiler.js +3 -1
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -8
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +196 -117
- package/packages/dd-trace/src/proxy.js +15 -0
- package/packages/dd-trace/src/rate_limiter.js +26 -1
- package/packages/dd-trace/src/remote_config/capabilities.js +5 -0
- package/packages/dd-trace/src/remote_config/manager.js +3 -2
- package/packages/dd-trace/src/sampling_rule.js +124 -2
- package/packages/dd-trace/src/span_sampler.js +19 -0
- package/packages/dd-trace/src/standalone/product.js +9 -0
- package/packages/dd-trace/src/standalone/tracesource.js +16 -1
- package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +13 -0
- package/packages/dd-trace/src/startup-log.js +21 -2
- package/packages/dd-trace/src/supported-configurations.json +9 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
- package/packages/dd-trace/src/util.js +1 -1
- package/register.js +1 -1
- package/version.js +4 -2
|
@@ -389,7 +389,6 @@ class CypressPlugin {
|
|
|
389
389
|
|
|
390
390
|
getTestSpan ({ testName, testSuite, isUnskippable, isForcedToRun, testSourceFile, isDisabled, isQuarantined }) {
|
|
391
391
|
const testSuiteTags = {
|
|
392
|
-
[TEST_COMMAND]: this.command,
|
|
393
392
|
[TEST_COMMAND]: this.command,
|
|
394
393
|
[TEST_MODULE]: TEST_FRAMEWORK_NAME
|
|
395
394
|
}
|
|
@@ -482,8 +481,12 @@ class CypressPlugin {
|
|
|
482
481
|
this.isEarlyFlakeDetectionEnabled = false
|
|
483
482
|
this.isKnownTestsEnabled = false
|
|
484
483
|
} else {
|
|
485
|
-
|
|
486
|
-
|
|
484
|
+
if (knownTestsResponse.knownTests[TEST_FRAMEWORK_NAME]) {
|
|
485
|
+
this.knownTestsByTestSuite = knownTestsResponse.knownTests[TEST_FRAMEWORK_NAME]
|
|
486
|
+
} else {
|
|
487
|
+
this.isEarlyFlakeDetectionEnabled = false
|
|
488
|
+
this.isKnownTestsEnabled = false
|
|
489
|
+
}
|
|
487
490
|
}
|
|
488
491
|
}
|
|
489
492
|
|
|
@@ -26,6 +26,10 @@ function safeGetRum (window) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function isNewTest (test) {
|
|
29
|
+
// If for whatever reason the worker does not receive valid known tests, we don't consider it as new
|
|
30
|
+
if (!Array.isArray(knownTestsForSuite)) {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
29
33
|
return !knownTestsForSuite.includes(test.fullTitle())
|
|
30
34
|
}
|
|
31
35
|
|
|
@@ -15,7 +15,7 @@ class ExpressCodeOriginForSpansPlugin extends Plugin {
|
|
|
15
15
|
this.addSub('apm:express:middleware:enter', ({ req, layer }) => {
|
|
16
16
|
const tags = layerTags.get(layer)
|
|
17
17
|
if (!tags) return
|
|
18
|
-
web.getContext(req)
|
|
18
|
+
web.getContext(req)?.span?.addTags(tags)
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
this.addSub('apm:express:route:added', ({ topOfStackFunc, layer }) => {
|
|
@@ -26,7 +26,7 @@ class ExpressCodeOriginForSpansPlugin extends Plugin {
|
|
|
26
26
|
this.addSub('apm:router:middleware:enter', ({ req, layer }) => {
|
|
27
27
|
const tags = layerTags.get(layer)
|
|
28
28
|
if (!tags) return
|
|
29
|
-
web.getContext(req)
|
|
29
|
+
web.getContext(req)?.span?.addTags(tags)
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
this.addSub('apm:router:route:added', ({ topOfStackFunc, layer }) => {
|
|
@@ -15,8 +15,7 @@ class FastifyCodeOriginForSpansPlugin extends Plugin {
|
|
|
15
15
|
this.addSub('apm:fastify:request:handle', ({ req, routeConfig }) => {
|
|
16
16
|
const tags = routeConfig?.[kCodeOriginForSpansTagsSym]
|
|
17
17
|
if (!tags) return
|
|
18
|
-
|
|
19
|
-
context.span?.addTags(tags)
|
|
18
|
+
web.getContext(req)?.span?.addTags(tags)
|
|
20
19
|
})
|
|
21
20
|
|
|
22
21
|
this.addSub('apm:fastify:route:added', ({ routeOptions, onRoute }) => {
|
|
@@ -261,21 +261,6 @@ class JestPlugin extends CiPlugin {
|
|
|
261
261
|
}
|
|
262
262
|
})
|
|
263
263
|
|
|
264
|
-
this.addSub('ci:jest:worker-report:trace', traces => {
|
|
265
|
-
const formattedTraces = JSON.parse(traces).map(trace =>
|
|
266
|
-
trace.map(span => ({
|
|
267
|
-
...span,
|
|
268
|
-
span_id: id(span.span_id),
|
|
269
|
-
trace_id: id(span.trace_id),
|
|
270
|
-
parent_id: id(span.parent_id)
|
|
271
|
-
}))
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
formattedTraces.forEach(trace => {
|
|
275
|
-
this.tracer._exporter.export(trace)
|
|
276
|
-
})
|
|
277
|
-
})
|
|
278
|
-
|
|
279
264
|
this.addSub('ci:jest:worker-report:coverage', data => {
|
|
280
265
|
const formattedCoverages = JSON.parse(data).map(coverage => ({
|
|
281
266
|
sessionId: id(coverage.sessionId),
|
|
@@ -287,12 +272,6 @@ class JestPlugin extends CiPlugin {
|
|
|
287
272
|
})
|
|
288
273
|
})
|
|
289
274
|
|
|
290
|
-
this.addSub('ci:jest:worker-report:logs', (logsPayloads) => {
|
|
291
|
-
JSON.parse(logsPayloads).forEach(({ testConfiguration, logMessage }) => {
|
|
292
|
-
this.tracer._exporter.exportDiLogs(testConfiguration, logMessage)
|
|
293
|
-
})
|
|
294
|
-
})
|
|
295
|
-
|
|
296
275
|
this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error }) => {
|
|
297
276
|
this.testSuiteSpan.setTag(TEST_STATUS, status)
|
|
298
277
|
if (error) {
|
|
@@ -22,12 +22,6 @@ const {
|
|
|
22
22
|
TEST_IS_RETRY,
|
|
23
23
|
TEST_EARLY_FLAKE_ENABLED,
|
|
24
24
|
TEST_EARLY_FLAKE_ABORT_REASON,
|
|
25
|
-
TEST_SESSION_ID,
|
|
26
|
-
TEST_MODULE_ID,
|
|
27
|
-
TEST_MODULE,
|
|
28
|
-
TEST_SUITE_ID,
|
|
29
|
-
TEST_COMMAND,
|
|
30
|
-
TEST_SUITE,
|
|
31
25
|
MOCHA_IS_PARALLEL,
|
|
32
26
|
TEST_IS_RUM_ACTIVE,
|
|
33
27
|
TEST_BROWSER_DRIVER,
|
|
@@ -54,32 +48,15 @@ const {
|
|
|
54
48
|
TELEMETRY_CODE_COVERAGE_NUM_FILES,
|
|
55
49
|
TELEMETRY_TEST_SESSION
|
|
56
50
|
} = require('../../dd-trace/src/ci-visibility/telemetry')
|
|
57
|
-
const id = require('../../dd-trace/src/id')
|
|
58
|
-
const log = require('../../dd-trace/src/log')
|
|
59
51
|
|
|
60
52
|
const BREAKPOINT_SET_GRACE_PERIOD_MS = 200
|
|
61
53
|
|
|
62
|
-
function getTestSuiteLevelVisibilityTags (testSuiteSpan) {
|
|
63
|
-
const testSuiteSpanContext = testSuiteSpan.context()
|
|
64
|
-
const suiteTags = {
|
|
65
|
-
[TEST_SUITE_ID]: testSuiteSpanContext.toSpanId(),
|
|
66
|
-
[TEST_SESSION_ID]: testSuiteSpanContext.toTraceId(),
|
|
67
|
-
[TEST_COMMAND]: testSuiteSpanContext._tags[TEST_COMMAND],
|
|
68
|
-
[TEST_MODULE]: 'mocha'
|
|
69
|
-
}
|
|
70
|
-
if (testSuiteSpanContext._parentId) {
|
|
71
|
-
suiteTags[TEST_MODULE_ID] = testSuiteSpanContext._parentId.toString(10)
|
|
72
|
-
}
|
|
73
|
-
return suiteTags
|
|
74
|
-
}
|
|
75
|
-
|
|
76
54
|
class MochaPlugin extends CiPlugin {
|
|
77
55
|
static id = 'mocha'
|
|
78
56
|
|
|
79
57
|
constructor (...args) {
|
|
80
58
|
super(...args)
|
|
81
59
|
|
|
82
|
-
this._testSuites = new Map()
|
|
83
60
|
this._testTitleToParams = {}
|
|
84
61
|
this.sourceRoot = process.cwd()
|
|
85
62
|
|
|
@@ -88,7 +65,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
88
65
|
return
|
|
89
66
|
}
|
|
90
67
|
const testSuite = getTestSuitePath(suiteFile, this.sourceRoot)
|
|
91
|
-
const testSuiteSpan = this.
|
|
68
|
+
const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuite)
|
|
92
69
|
|
|
93
70
|
if (!coverageFiles.length) {
|
|
94
71
|
this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
|
|
@@ -163,7 +140,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
163
140
|
const store = storage('legacy').getStore()
|
|
164
141
|
ctx.parentStore = store
|
|
165
142
|
ctx.currentStore = { ...store, testSuiteSpan }
|
|
166
|
-
this.
|
|
143
|
+
this._testSuiteSpansByTestSuite.set(testSuite, testSuiteSpan)
|
|
167
144
|
})
|
|
168
145
|
|
|
169
146
|
this.addSub('ci:mocha:test-suite:finish', ({ testSuiteSpan, status }) => {
|
|
@@ -436,37 +413,6 @@ class MochaPlugin extends CiPlugin {
|
|
|
436
413
|
this.tracer._exporter.flush()
|
|
437
414
|
})
|
|
438
415
|
|
|
439
|
-
this.addSub('ci:mocha:worker-report:trace', (traces) => {
|
|
440
|
-
const formattedTraces = JSON.parse(traces).map(trace =>
|
|
441
|
-
trace.map(span => {
|
|
442
|
-
const formattedSpan = {
|
|
443
|
-
...span,
|
|
444
|
-
span_id: id(span.span_id),
|
|
445
|
-
trace_id: id(span.trace_id),
|
|
446
|
-
parent_id: id(span.parent_id)
|
|
447
|
-
}
|
|
448
|
-
if (formattedSpan.name === 'mocha.test') {
|
|
449
|
-
const testSuite = span.meta[TEST_SUITE]
|
|
450
|
-
const testSuiteSpan = this._testSuites.get(testSuite)
|
|
451
|
-
if (!testSuiteSpan) {
|
|
452
|
-
log.warn('Test suite span not found for test span with test suite', testSuite)
|
|
453
|
-
return formattedSpan
|
|
454
|
-
}
|
|
455
|
-
const suiteTags = getTestSuiteLevelVisibilityTags(testSuiteSpan)
|
|
456
|
-
formattedSpan.meta = {
|
|
457
|
-
...formattedSpan.meta,
|
|
458
|
-
...suiteTags
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return formattedSpan
|
|
462
|
-
})
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
formattedTraces.forEach(trace => {
|
|
466
|
-
this.tracer._exporter.export(trace)
|
|
467
|
-
})
|
|
468
|
-
})
|
|
469
|
-
|
|
470
416
|
this.addBind('ci:mocha:global:run', (ctx) => {
|
|
471
417
|
return ctx.currentStore
|
|
472
418
|
})
|
|
@@ -522,7 +468,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
522
468
|
}
|
|
523
469
|
|
|
524
470
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
|
|
525
|
-
const testSuiteSpan = this.
|
|
471
|
+
const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuite)
|
|
526
472
|
|
|
527
473
|
extraTags[TEST_SOURCE_FILE] = this.repositoryRoot !== this.sourceRoot && !!this.repositoryRoot
|
|
528
474
|
? getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const { isTrue } = require('../../dd-trace/src/util')
|
|
4
4
|
const DatabasePlugin = require('../../dd-trace/src/plugins/database')
|
|
5
|
-
const coalesce = require('koalas')
|
|
6
5
|
const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper')
|
|
7
6
|
|
|
8
7
|
class MongodbCorePlugin extends DatabasePlugin {
|
|
@@ -10,23 +9,26 @@ class MongodbCorePlugin extends DatabasePlugin {
|
|
|
10
9
|
static component = 'mongodb'
|
|
11
10
|
// avoid using db.name for peer.service since it includes the collection name
|
|
12
11
|
// should be removed if one day this will be fixed
|
|
12
|
+
/**
|
|
13
|
+
* @override
|
|
14
|
+
*/
|
|
13
15
|
static peerServicePrecursors = []
|
|
14
16
|
|
|
17
|
+
/**
|
|
18
|
+
* @override
|
|
19
|
+
*/
|
|
15
20
|
configure (config) {
|
|
16
21
|
super.configure(config)
|
|
17
22
|
|
|
18
23
|
const heartbeatFromEnv = getEnvironmentVariable('DD_TRACE_MONGODB_HEARTBEAT_ENABLED')
|
|
19
24
|
|
|
20
|
-
this.config.heartbeatEnabled =
|
|
21
|
-
|
|
22
|
-
heartbeatFromEnv && isTrue(heartbeatFromEnv),
|
|
25
|
+
this.config.heartbeatEnabled = config.heartbeatEnabled ??
|
|
26
|
+
(heartbeatFromEnv && isTrue(heartbeatFromEnv)) ??
|
|
23
27
|
true
|
|
24
|
-
)
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
bindStart (ctx) {
|
|
28
31
|
const { ns, ops, options = {}, name } = ctx
|
|
29
|
-
|
|
30
32
|
// heartbeat commands can be disabled if this.config.heartbeatEnabled is false
|
|
31
33
|
if (!this.config.heartbeatEnabled && isHeartbeat(ops, this.config)) {
|
|
32
34
|
return
|
|
@@ -55,11 +57,18 @@ class MongodbCorePlugin extends DatabasePlugin {
|
|
|
55
57
|
return ctx.currentStore
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
/**
|
|
61
|
+
* @override
|
|
62
|
+
*/
|
|
58
63
|
getPeerService (tags) {
|
|
59
|
-
|
|
64
|
+
let ns = tags['db.name']
|
|
60
65
|
if (ns && tags['peer.service'] === undefined) {
|
|
66
|
+
const dotIndex = ns.indexOf('.')
|
|
67
|
+
if (dotIndex !== -1) {
|
|
68
|
+
ns = ns.slice(0, dotIndex)
|
|
69
|
+
}
|
|
61
70
|
// the mongo ns is either dbName either dbName.collection. So we keep the first part
|
|
62
|
-
tags['peer.service'] = ns
|
|
71
|
+
tags['peer.service'] = ns
|
|
63
72
|
}
|
|
64
73
|
return super.getPeerService(tags)
|
|
65
74
|
}
|
|
@@ -90,21 +99,38 @@ function sanitizeBigInt (data) {
|
|
|
90
99
|
return JSON.stringify(data, (_key, value) => typeof value === 'bigint' ? value.toString() : value)
|
|
91
100
|
}
|
|
92
101
|
|
|
102
|
+
function extractQuery (statements) {
|
|
103
|
+
if (statements.length === 1 && statements[0].q) return statements[0].q
|
|
104
|
+
|
|
105
|
+
const extractedQueries = []
|
|
106
|
+
for (let i = 0; i < statements.length; i++) {
|
|
107
|
+
if (statements[i].q) {
|
|
108
|
+
extractedQueries.push(limitDepth(statements[i].q))
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return extractedQueries
|
|
113
|
+
}
|
|
114
|
+
|
|
93
115
|
function getQuery (cmd) {
|
|
94
|
-
if (!cmd || typeof cmd !== 'object'
|
|
116
|
+
if (!cmd || (typeof cmd !== 'object' && !Array.isArray(cmd))) return
|
|
117
|
+
|
|
118
|
+
if (Array.isArray(cmd)) return sanitizeBigInt(extractQuery(cmd))
|
|
95
119
|
if (cmd.query) return sanitizeBigInt(limitDepth(cmd.query))
|
|
96
120
|
if (cmd.filter) return sanitizeBigInt(limitDepth(cmd.filter))
|
|
97
121
|
if (cmd.pipeline) return sanitizeBigInt(limitDepth(cmd.pipeline))
|
|
122
|
+
if (cmd.deletes) return sanitizeBigInt(extractQuery(cmd.deletes))
|
|
123
|
+
if (cmd.updates) return sanitizeBigInt(extractQuery(cmd.updates))
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
function getResource (plugin, ns, query, operationName) {
|
|
101
|
-
|
|
127
|
+
let resource = `${operationName} ${ns}`
|
|
102
128
|
|
|
103
129
|
if (plugin.config.queryInResourceName && query) {
|
|
104
|
-
|
|
130
|
+
resource += ` ${query}`
|
|
105
131
|
}
|
|
106
132
|
|
|
107
|
-
return
|
|
133
|
+
return resource
|
|
108
134
|
}
|
|
109
135
|
|
|
110
136
|
function truncate (input) {
|
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
TEST_IS_NEW,
|
|
18
18
|
TEST_IS_RETRY,
|
|
19
19
|
TEST_EARLY_FLAKE_ENABLED,
|
|
20
|
+
TEST_EARLY_FLAKE_ABORT_REASON,
|
|
20
21
|
TELEMETRY_TEST_SESSION,
|
|
21
22
|
TEST_RETRY_REASON,
|
|
22
23
|
TEST_MANAGEMENT_IS_QUARANTINED,
|
|
@@ -53,7 +54,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
53
54
|
constructor (...args) {
|
|
54
55
|
super(...args)
|
|
55
56
|
|
|
56
|
-
this.
|
|
57
|
+
this._testSuiteSpansByTestSuiteAbsolutePath = new Map()
|
|
57
58
|
this.numFailedTests = 0
|
|
58
59
|
this.numFailedSuites = 0
|
|
59
60
|
|
|
@@ -70,6 +71,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
70
71
|
this.addSub('ci:playwright:session:finish', ({
|
|
71
72
|
status,
|
|
72
73
|
isEarlyFlakeDetectionEnabled,
|
|
74
|
+
isEarlyFlakeDetectionFaulty,
|
|
73
75
|
isTestManagementTestsEnabled,
|
|
74
76
|
onDone
|
|
75
77
|
}) => {
|
|
@@ -79,7 +81,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
79
81
|
if (isEarlyFlakeDetectionEnabled) {
|
|
80
82
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
|
|
81
83
|
}
|
|
82
|
-
|
|
84
|
+
if (isEarlyFlakeDetectionFaulty) {
|
|
85
|
+
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
86
|
+
}
|
|
83
87
|
if (this.numFailedSuites > 0) {
|
|
84
88
|
let errorMessage = `Test suites failed: ${this.numFailedSuites}.`
|
|
85
89
|
if (this.numFailedTests > 0) {
|
|
@@ -142,7 +146,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
142
146
|
ctx.parentStore = store
|
|
143
147
|
ctx.currentStore = { ...store, testSuiteSpan }
|
|
144
148
|
|
|
145
|
-
this.
|
|
149
|
+
this._testSuiteSpansByTestSuiteAbsolutePath.set(testSuiteAbsolutePath, testSuiteSpan)
|
|
146
150
|
|
|
147
151
|
return ctx.currentStore
|
|
148
152
|
})
|
|
@@ -247,7 +251,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
247
251
|
formattedSpan.meta[TEST_COMMAND] = this.command
|
|
248
252
|
formattedSpan.meta[TEST_MODULE] = this.constructor.id
|
|
249
253
|
// MISSING _trace.startTime and _trace.ticks - because by now the suite is already serialized
|
|
250
|
-
const testSuite = this.
|
|
254
|
+
const testSuite = this._testSuiteSpansByTestSuiteAbsolutePath.get(
|
|
255
|
+
formattedSpan.meta.test_suite_absolute_path
|
|
256
|
+
)
|
|
251
257
|
if (testSuite) {
|
|
252
258
|
formattedSpan.meta[TEST_SUITE_ID] = testSuite.context().toSpanId()
|
|
253
259
|
}
|
|
@@ -391,7 +397,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
391
397
|
|
|
392
398
|
// TODO: this runs both in worker and main process (main process: skipped tests that do not go through _runTest)
|
|
393
399
|
startTestSpan (testName, testSuiteAbsolutePath, testSuite, testSourceFile, testSourceLine, browserName) {
|
|
394
|
-
const testSuiteSpan = this.
|
|
400
|
+
const testSuiteSpan = this._testSuiteSpansByTestSuiteAbsolutePath.get(testSuiteAbsolutePath)
|
|
395
401
|
|
|
396
402
|
const extraTags = {
|
|
397
403
|
[TEST_SOURCE_START]: testSourceLine
|
|
@@ -55,8 +55,12 @@ class VitestPlugin extends CiPlugin {
|
|
|
55
55
|
this.taskToFinishTime = new WeakMap()
|
|
56
56
|
|
|
57
57
|
this.addSub('ci:vitest:test:is-new', ({ knownTests, testSuiteAbsolutePath, testName, onDone }) => {
|
|
58
|
+
// if for whatever reason the worker does not receive valid known tests, we don't consider it as new
|
|
59
|
+
if (!knownTests.vitest) {
|
|
60
|
+
return onDone(false)
|
|
61
|
+
}
|
|
58
62
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
59
|
-
const testsForThisTestSuite = knownTests[testSuite] || []
|
|
63
|
+
const testsForThisTestSuite = knownTests.vitest[testSuite] || []
|
|
60
64
|
onDone(!testsForThisTestSuite.includes(testName))
|
|
61
65
|
})
|
|
62
66
|
|
|
@@ -60,7 +60,7 @@ class WSClosePlugin extends TracingPlugin {
|
|
|
60
60
|
end (ctx) {
|
|
61
61
|
if (!Object.hasOwn(ctx, 'result')) return
|
|
62
62
|
|
|
63
|
-
if (ctx.socket.spanContext) ctx.span.addLink(ctx.socket.spanContext)
|
|
63
|
+
if (ctx.socket.spanContext) ctx.span.addLink({ context: ctx.socket.spanContext })
|
|
64
64
|
|
|
65
65
|
ctx.span.finish()
|
|
66
66
|
}
|
|
@@ -50,7 +50,12 @@ class WSProducerPlugin extends TracingPlugin {
|
|
|
50
50
|
end (ctx) {
|
|
51
51
|
if (!Object.hasOwn(ctx, 'result')) return
|
|
52
52
|
|
|
53
|
-
if (ctx.socket.spanContext)
|
|
53
|
+
if (ctx.socket.spanContext) {
|
|
54
|
+
ctx.span.addLink({
|
|
55
|
+
context: ctx.socket.spanContext,
|
|
56
|
+
attributes: { 'dd.kind': 'resuming' },
|
|
57
|
+
})
|
|
58
|
+
}
|
|
54
59
|
|
|
55
60
|
ctx.span.finish()
|
|
56
61
|
return ctx.parentStore
|
|
@@ -60,7 +60,12 @@ class WSReceiverPlugin extends TracingPlugin {
|
|
|
60
60
|
end (ctx) {
|
|
61
61
|
if (!Object.hasOwn(ctx, 'result')) return
|
|
62
62
|
|
|
63
|
-
if (ctx.socket.spanContext)
|
|
63
|
+
if (ctx.socket.spanContext) {
|
|
64
|
+
ctx.span.addLink({
|
|
65
|
+
context: ctx.socket.spanContext,
|
|
66
|
+
attributes: { 'dd.kind': 'executed_by' },
|
|
67
|
+
})
|
|
68
|
+
}
|
|
64
69
|
|
|
65
70
|
ctx.span.finish()
|
|
66
71
|
return ctx.parentStore
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
async function executeRequest (body, opts) {
|
|
4
|
+
const postData = JSON.stringify(body)
|
|
5
|
+
const headers = {
|
|
6
|
+
'Content-Type': 'application/json',
|
|
7
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
8
|
+
...opts.headers
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const response = await fetch(opts.url, {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers,
|
|
14
|
+
body: postData,
|
|
15
|
+
signal: AbortSignal.timeout(opts.timeout)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const responseBody = await response.json()
|
|
19
|
+
return {
|
|
20
|
+
status: response.status,
|
|
21
|
+
body: responseBody
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = executeRequest
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const NoopAIGuard = require('./noop')
|
|
4
|
+
const executeRequest = require('./client')
|
|
5
|
+
const {
|
|
6
|
+
AI_GUARD_RESOURCE,
|
|
7
|
+
AI_GUARD_TARGET_TAG_KEY,
|
|
8
|
+
AI_GUARD_REASON_TAG_KEY,
|
|
9
|
+
AI_GUARD_ACTION_TAG_KEY,
|
|
10
|
+
AI_GUARD_BLOCKED_TAG_KEY,
|
|
11
|
+
AI_GUARD_META_STRUCT_KEY,
|
|
12
|
+
AI_GUARD_TOOL_NAME_TAG_KEY
|
|
13
|
+
} = require('./tags')
|
|
14
|
+
const log = require('../log')
|
|
15
|
+
|
|
16
|
+
const ALLOW = 'ALLOW'
|
|
17
|
+
|
|
18
|
+
class AIGuardAbortError extends Error {
|
|
19
|
+
constructor (reason) {
|
|
20
|
+
super(reason)
|
|
21
|
+
this.name = 'AIGuardAbortError'
|
|
22
|
+
this.reason = reason
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class AIGuardClientError extends Error {
|
|
27
|
+
constructor (message, opts = {}) {
|
|
28
|
+
super(message)
|
|
29
|
+
this.name = 'AIGuardClientError'
|
|
30
|
+
if (opts.errors) {
|
|
31
|
+
this.errors = opts.errors
|
|
32
|
+
}
|
|
33
|
+
if (opts.cause) {
|
|
34
|
+
this.cause = opts.cause
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class AIGuard extends NoopAIGuard {
|
|
40
|
+
#initialized
|
|
41
|
+
#tracer
|
|
42
|
+
#headers
|
|
43
|
+
#evaluateUrl
|
|
44
|
+
#timeout
|
|
45
|
+
#maxMessagesLength
|
|
46
|
+
#maxContentSize
|
|
47
|
+
#meta
|
|
48
|
+
|
|
49
|
+
constructor (tracer, config) {
|
|
50
|
+
super()
|
|
51
|
+
|
|
52
|
+
if (!config.apiKey || !config.appKey) {
|
|
53
|
+
log.error('AIGuard: missing api and/or app keys, use env DD_API_KEY and DD_APP_KEY')
|
|
54
|
+
this.#initialized = false
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
this.#tracer = tracer
|
|
58
|
+
this.#headers = {
|
|
59
|
+
'DD-API-KEY': config.apiKey,
|
|
60
|
+
'DD-APPLICATION-KEY': config.appKey,
|
|
61
|
+
}
|
|
62
|
+
const endpoint = config.experimental.aiguard.endpoint || `https://app.${config.site}/api/v2/ai-guard`
|
|
63
|
+
this.#evaluateUrl = `${endpoint}/evaluate`
|
|
64
|
+
this.#timeout = config.experimental.aiguard.timeout
|
|
65
|
+
this.#maxMessagesLength = config.experimental.aiguard.maxMessagesLength
|
|
66
|
+
this.#maxContentSize = config.experimental.aiguard.maxContentSize
|
|
67
|
+
this.#meta = { service: config.service, env: config.env }
|
|
68
|
+
this.#initialized = true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#truncate (messages) {
|
|
72
|
+
const size = Math.min(messages.length, this.#maxMessagesLength)
|
|
73
|
+
const result = messages.slice(-size)
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < size; i++) {
|
|
76
|
+
const message = result[i]
|
|
77
|
+
if (message.content?.length > this.#maxContentSize) {
|
|
78
|
+
result[i] = { ...message, content: message.content.slice(0, this.#maxContentSize) }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#isToolCall (message) {
|
|
85
|
+
return message.tool_calls || message.tool_call_id
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#getToolName (message, history) {
|
|
89
|
+
// 1. assistant message with tool calls
|
|
90
|
+
if (message.tool_calls) {
|
|
91
|
+
const names = message.tool_calls.map((tool) => tool.function.name)
|
|
92
|
+
return names.length === 0 ? null : names.join(',')
|
|
93
|
+
}
|
|
94
|
+
// 2. assistant message with tool output (search the linked tool call in reverse order)
|
|
95
|
+
const id = message.tool_call_id
|
|
96
|
+
for (let i = history.length - 2; i >= 0; i--) {
|
|
97
|
+
const item = history[i]
|
|
98
|
+
if (item.tool_calls) {
|
|
99
|
+
for (const toolCall of item.tool_calls) {
|
|
100
|
+
if (toolCall.id === id) {
|
|
101
|
+
return toolCall.function.name
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
evaluate (messages, opts) {
|
|
110
|
+
if (!this.#initialized) {
|
|
111
|
+
return super.evaluate(messages, opts)
|
|
112
|
+
}
|
|
113
|
+
const { block = false } = opts ?? {}
|
|
114
|
+
return this.#tracer.trace(AI_GUARD_RESOURCE, {}, async (span) => {
|
|
115
|
+
const last = messages[messages.length - 1]
|
|
116
|
+
const target = this.#isToolCall(last) ? 'tool' : 'prompt'
|
|
117
|
+
span.setTag(AI_GUARD_TARGET_TAG_KEY, target)
|
|
118
|
+
if (target === 'tool') {
|
|
119
|
+
const name = this.#getToolName(last, messages)
|
|
120
|
+
if (name) {
|
|
121
|
+
span.setTag(AI_GUARD_TOOL_NAME_TAG_KEY, name)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
span.meta_struct = {
|
|
125
|
+
[AI_GUARD_META_STRUCT_KEY]: {
|
|
126
|
+
messages: this.#truncate(messages)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let response
|
|
130
|
+
try {
|
|
131
|
+
const payload = {
|
|
132
|
+
data: {
|
|
133
|
+
attributes: {
|
|
134
|
+
messages,
|
|
135
|
+
meta: this.#meta,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
response = await executeRequest(
|
|
140
|
+
payload,
|
|
141
|
+
{ url: this.#evaluateUrl, headers: this.#headers, timeout: this.#timeout })
|
|
142
|
+
} catch (e) {
|
|
143
|
+
throw new AIGuardClientError('Unexpected error calling AI Guard service', { cause: e })
|
|
144
|
+
}
|
|
145
|
+
if (response.status !== 200) {
|
|
146
|
+
throw new AIGuardClientError(
|
|
147
|
+
`AI Guard service call failed, status ${response.status}`,
|
|
148
|
+
{ errors: response.body?.errors })
|
|
149
|
+
}
|
|
150
|
+
let action, reason, blockingEnabled
|
|
151
|
+
try {
|
|
152
|
+
const attr = response.body.data.attributes
|
|
153
|
+
if (!attr.action) {
|
|
154
|
+
throw new Error('Action missing from response')
|
|
155
|
+
}
|
|
156
|
+
action = attr.action
|
|
157
|
+
reason = attr.reason
|
|
158
|
+
blockingEnabled = attr.is_blocking_enabled ?? false
|
|
159
|
+
} catch (e) {
|
|
160
|
+
throw new AIGuardClientError(`AI Guard service returned unexpected response : ${response.body}`, { cause: e })
|
|
161
|
+
}
|
|
162
|
+
span.setTag(AI_GUARD_ACTION_TAG_KEY, action)
|
|
163
|
+
span.setTag(AI_GUARD_REASON_TAG_KEY, reason)
|
|
164
|
+
if (block && blockingEnabled && action !== ALLOW) {
|
|
165
|
+
span.setTag(AI_GUARD_BLOCKED_TAG_KEY, 'true')
|
|
166
|
+
throw new AIGuardAbortError(reason)
|
|
167
|
+
}
|
|
168
|
+
return { action, reason }
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = AIGuard
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
AI_GUARD_RESOURCE: 'ai_guard',
|
|
5
|
+
AI_GUARD_TARGET_TAG_KEY: 'ai_guard.target',
|
|
6
|
+
AI_GUARD_TOOL_NAME_TAG_KEY: 'ai_guard.tool_name',
|
|
7
|
+
AI_GUARD_ACTION_TAG_KEY: 'ai_guard.action',
|
|
8
|
+
AI_GUARD_REASON_TAG_KEY: 'ai_guard.reason',
|
|
9
|
+
AI_GUARD_BLOCKED_TAG_KEY: 'ai_guard.blocked',
|
|
10
|
+
AI_GUARD_META_STRUCT_KEY: 'ai_guard'
|
|
11
|
+
}
|