dd-trace 6.0.0 → 6.2.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 -2
- package/index.d.ts +28 -1
- package/initialize.mjs +4 -2
- package/package.json +34 -30
- package/packages/datadog-instrumentations/src/ai.js +45 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
- package/packages/datadog-instrumentations/src/child_process.js +3 -3
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
- package/packages/datadog-instrumentations/src/cucumber.js +102 -43
- package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
- package/packages/datadog-instrumentations/src/fastify.js +27 -8
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +5 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +31 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +19 -5
- package/packages/datadog-instrumentations/src/jest.js +713 -63
- package/packages/datadog-instrumentations/src/mercurius.js +11 -0
- package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
- package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
- package/packages/datadog-instrumentations/src/mongodb.js +74 -0
- package/packages/datadog-instrumentations/src/mongoose.js +4 -5
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +15 -0
- package/packages/datadog-instrumentations/src/playwright.js +13 -9
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +26 -1
- package/packages/datadog-instrumentations/src/vitest-util.js +2 -0
- package/packages/datadog-instrumentations/src/vitest-worker.js +103 -34
- package/packages/datadog-plugin-ai/src/index.js +1 -1
- package/packages/datadog-plugin-ai/src/tracing.js +66 -3
- package/packages/datadog-plugin-ai/src/utils.js +17 -4
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
- package/packages/datadog-plugin-child_process/src/index.js +13 -2
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
- package/packages/datadog-plugin-cucumber/src/index.js +14 -23
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
- package/packages/datadog-plugin-cypress/src/index.js +47 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
- package/packages/datadog-plugin-graphql/src/execute.js +25 -25
- package/packages/datadog-plugin-graphql/src/index.js +23 -1
- package/packages/datadog-plugin-graphql/src/request.js +104 -0
- package/packages/datadog-plugin-graphql/src/utils.js +186 -2
- package/packages/datadog-plugin-graphql/src/validate.js +25 -2
- package/packages/datadog-plugin-jest/src/index.js +30 -14
- package/packages/datadog-plugin-mocha/src/index.js +39 -14
- package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
- package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
- package/packages/datadog-plugin-playwright/src/index.js +3 -2
- package/packages/datadog-plugin-vitest/src/index.js +43 -9
- package/packages/dd-trace/src/aiguard/index.js +9 -14
- package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
- package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
- package/packages/dd-trace/src/appsec/graphql.js +9 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
- package/packages/dd-trace/src/appsec/lambda.js +8 -8
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +37 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/datastreams/writer.js +14 -2
- package/packages/dd-trace/src/encode/0.4.js +23 -2
- package/packages/dd-trace/src/encode/0.5.js +12 -1
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +11 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +20 -2
- package/packages/dd-trace/src/exporters/common/writer.js +17 -1
- package/packages/dd-trace/src/guardrails/index.js +3 -1
- package/packages/dd-trace/src/guardrails/telemetry.js +40 -3
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
- package/packages/dd-trace/src/msgpack/chunk.js +33 -1
- package/packages/dd-trace/src/msgpack/index.js +6 -1
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/plugin_manager.js +16 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
- package/packages/dd-trace/src/plugins/index.js +5 -0
- package/packages/dd-trace/src/plugins/util/test.js +4 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
- package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +6 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +8 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +5 -6
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { isMap, isRegExp } = require('node:util').types
|
|
4
|
+
|
|
5
|
+
const DatabasePlugin = require('../../dd-trace/src/plugins/database')
|
|
6
|
+
|
|
7
|
+
class MongodbCoreQueryPlugin extends DatabasePlugin {
|
|
8
|
+
static id = 'mongodb-core'
|
|
9
|
+
static component = 'mongodb'
|
|
10
|
+
// avoid using db.name for peer.service since it includes the collection name
|
|
11
|
+
// should be removed if one day this will be fixed
|
|
12
|
+
/**
|
|
13
|
+
* @override
|
|
14
|
+
*/
|
|
15
|
+
static peerServicePrecursors = []
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @override
|
|
19
|
+
*/
|
|
20
|
+
configure (config) {
|
|
21
|
+
super.configure(config)
|
|
22
|
+
|
|
23
|
+
this.config.heartbeatEnabled = config.heartbeatEnabled ??
|
|
24
|
+
this._tracerConfig.DD_TRACE_MONGODB_HEARTBEAT_ENABLED
|
|
25
|
+
this.config.obfuscateQuery = normaliseObfuscateQuery(
|
|
26
|
+
config.obfuscateQuery ?? this._tracerConfig.DD_TRACE_MONGODB_OBFUSCATE_QUERY
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
bindStart (ctx) {
|
|
31
|
+
const { ns, ops, options = {}, name } = ctx
|
|
32
|
+
// heartbeat commands can be disabled if this.config.heartbeatEnabled is false
|
|
33
|
+
if (!this.config.heartbeatEnabled && isHeartbeat(ops, this.config)) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
const query = getQuery(ops, this.config.obfuscateQuery)
|
|
37
|
+
const resource = truncate(getResource(this, ns, query, name))
|
|
38
|
+
const serviceResult = this.serviceName({ pluginConfig: this.config })
|
|
39
|
+
const span = this.startSpan(this.operationName(), {
|
|
40
|
+
service: serviceResult,
|
|
41
|
+
resource,
|
|
42
|
+
type: 'mongodb',
|
|
43
|
+
kind: 'client',
|
|
44
|
+
meta: {
|
|
45
|
+
// this is not technically correct since it includes the collection but we changing will break customer stuff
|
|
46
|
+
'db.name': ns,
|
|
47
|
+
'mongodb.query': query,
|
|
48
|
+
'out.host': options.host,
|
|
49
|
+
'out.port': options.port,
|
|
50
|
+
},
|
|
51
|
+
}, ctx)
|
|
52
|
+
const comment = this.injectDbmComment(span, ops.comment, serviceResult.name)
|
|
53
|
+
if (comment) {
|
|
54
|
+
ops.comment = comment
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return ctx.currentStore
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @override
|
|
62
|
+
*/
|
|
63
|
+
getPeerService (tags) {
|
|
64
|
+
let ns = tags['db.name']
|
|
65
|
+
if (ns && tags['peer.service'] === undefined) {
|
|
66
|
+
const dotIndex = ns.indexOf('.')
|
|
67
|
+
if (dotIndex !== -1) {
|
|
68
|
+
ns = ns.slice(0, dotIndex)
|
|
69
|
+
}
|
|
70
|
+
// the mongo ns is either dbName either dbName.collection. So we keep the first part
|
|
71
|
+
tags['peer.service'] = ns
|
|
72
|
+
}
|
|
73
|
+
return super.getPeerService(tags)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
injectDbmComment (span, comment, serviceName) {
|
|
77
|
+
const dbmTraceComment = this.createDbmComment(span, serviceName)
|
|
78
|
+
|
|
79
|
+
if (!dbmTraceComment) {
|
|
80
|
+
return comment
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (comment) {
|
|
84
|
+
// if the command already has a comment, append the dbm trace comment
|
|
85
|
+
if (typeof comment === 'string') {
|
|
86
|
+
comment += `,${dbmTraceComment}`
|
|
87
|
+
} else if (Array.isArray(comment)) {
|
|
88
|
+
comment.push(dbmTraceComment)
|
|
89
|
+
} // do nothing if the comment is not a string or an array
|
|
90
|
+
} else {
|
|
91
|
+
comment = dbmTraceComment
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return comment
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const MAX_DEPTH = 10
|
|
99
|
+
const MAX_QUERY_LENGTH = 10_000
|
|
100
|
+
|
|
101
|
+
function extractQuery (statements) {
|
|
102
|
+
if (statements.length === 1 && statements[0].q) return statements[0].q
|
|
103
|
+
|
|
104
|
+
const extractedQueries = []
|
|
105
|
+
for (let i = 0; i < statements.length; i++) {
|
|
106
|
+
if (statements[i].q) {
|
|
107
|
+
extractedQueries.push(statements[i].q)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return extractedQueries
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {Record<string, unknown> | unknown[] | undefined} cmd
|
|
116
|
+
* @param {'none' | 'types' | 'redact'} mode
|
|
117
|
+
*/
|
|
118
|
+
function getQuery (cmd, mode) {
|
|
119
|
+
if (!cmd || (typeof cmd !== 'object' && !Array.isArray(cmd))) return
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(cmd)) return sanitiseAndStringify(extractQuery(cmd), mode)
|
|
122
|
+
if (cmd.query) return sanitiseAndStringify(cmd.query, mode)
|
|
123
|
+
if (cmd.filter) return sanitiseAndStringify(cmd.filter, mode)
|
|
124
|
+
if (cmd.pipeline) return sanitiseAndStringify(cmd.pipeline, mode)
|
|
125
|
+
if (cmd.deletes) return sanitiseAndStringify(extractQuery(cmd.deletes), mode)
|
|
126
|
+
if (cmd.updates) return sanitiseAndStringify(extractQuery(cmd.updates), mode)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getResource (plugin, ns, query, operationName) {
|
|
130
|
+
let resource = `${operationName} ${ns}`
|
|
131
|
+
|
|
132
|
+
if (plugin.config.queryInResourceName && query) {
|
|
133
|
+
resource += ` ${query}`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return resource
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function truncate (input) {
|
|
140
|
+
return input.length > MAX_QUERY_LENGTH ? input.slice(0, MAX_QUERY_LENGTH) : input
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Depth doubles as the cycle bound: a cycle pushes past MAX_DEPTH and bails,
|
|
144
|
+
// after which the slow path catches it via its ancestor stack.
|
|
145
|
+
/** @param {unknown} input */
|
|
146
|
+
function canStringifyDirect (input) {
|
|
147
|
+
if (input === null ||
|
|
148
|
+
typeof input !== 'object' ||
|
|
149
|
+
ArrayBuffer.isView(input) ||
|
|
150
|
+
input._bsontype !== undefined ||
|
|
151
|
+
isRegExp(input) ||
|
|
152
|
+
isMap(input) ||
|
|
153
|
+
typeof input.toJSON === 'function') {
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
return canStringifyDirectWalk(input, 1)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {Record<string, unknown> | unknown[]} value
|
|
161
|
+
* @param {number} depth
|
|
162
|
+
*/
|
|
163
|
+
function canStringifyDirectWalk (value, depth) {
|
|
164
|
+
if (depth > MAX_DEPTH) return false
|
|
165
|
+
const children = Array.isArray(value) ? value : Object.values(value)
|
|
166
|
+
for (const child of children) {
|
|
167
|
+
if (child === null ||
|
|
168
|
+
typeof child === 'string' ||
|
|
169
|
+
typeof child === 'number' ||
|
|
170
|
+
typeof child === 'boolean') {
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
if (typeof child !== 'object' ||
|
|
174
|
+
ArrayBuffer.isView(child) ||
|
|
175
|
+
child._bsontype !== undefined ||
|
|
176
|
+
isRegExp(child) ||
|
|
177
|
+
isMap(child) ||
|
|
178
|
+
typeof child.toJSON === 'function') {
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
if (!canStringifyDirectWalk(child, depth + 1)) return false
|
|
182
|
+
}
|
|
183
|
+
return true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {Record<string, unknown> | unknown[]} input
|
|
188
|
+
* @param {'none' | 'types' | 'redact'} mode
|
|
189
|
+
*/
|
|
190
|
+
function sanitiseAndStringify (input, mode) {
|
|
191
|
+
if (mode === 'none') {
|
|
192
|
+
if (canStringifyDirect(input)) return JSON.stringify(input)
|
|
193
|
+
return buildNone(input, [])
|
|
194
|
+
}
|
|
195
|
+
if (mode === 'redact') return buildRedact(input, [])
|
|
196
|
+
return buildTypes(input, [])
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const REDACT_LEAF = '"?"'
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {RegExp} value
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
function stringifyRegExp (value) {
|
|
206
|
+
return `{"$regex":${JSON.stringify(value.source)},"$options":${JSON.stringify(value.flags)}}`
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @param {Record<string, unknown> | unknown[]} value
|
|
211
|
+
* @param {object[]} ancestors
|
|
212
|
+
* @returns {string | undefined}
|
|
213
|
+
*/
|
|
214
|
+
function buildNone (value, ancestors) {
|
|
215
|
+
// ArrayBuffer views (Buffer, every TypedArray, DataView) and Binary BSON
|
|
216
|
+
// wrappers redact at the leaf; the walker neither recurses into the bytes
|
|
217
|
+
// nor invokes any custom conversion.
|
|
218
|
+
const bsontype = value._bsontype
|
|
219
|
+
if (ArrayBuffer.isView(value) || bsontype === 'Binary' ||
|
|
220
|
+
ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
|
|
221
|
+
return REDACT_LEAF
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (isRegExp(value)) return stringifyRegExp(value)
|
|
225
|
+
|
|
226
|
+
// Mirror JSON.stringify's contract: when `toJSON` is present, walk its
|
|
227
|
+
// result (wrappers like Timestamp / Decimal128 expand to a small object,
|
|
228
|
+
// ObjectId / Date flatten to a primitive).
|
|
229
|
+
if (typeof value.toJSON === 'function') {
|
|
230
|
+
const json = value.toJSON()
|
|
231
|
+
if (json === value) return REDACT_LEAF
|
|
232
|
+
// JSON.stringify keeps a null result as null (an invalid Date's toJSON
|
|
233
|
+
// returns null); only function / symbol / undefined results drop the key.
|
|
234
|
+
if (json === null) return 'null'
|
|
235
|
+
if (typeof json !== 'object') return classifyLeafForNone(json)
|
|
236
|
+
// A wrapper that exposes binary state through toJSON (Buffer-backed
|
|
237
|
+
// class with WeakMap state, etc.) returns a TypedArray here. Re-screen
|
|
238
|
+
// before the per-key walk would expand it element by element.
|
|
239
|
+
if (ArrayBuffer.isView(json) || json._bsontype === 'Binary') return REDACT_LEAF
|
|
240
|
+
value = json
|
|
241
|
+
} else if (bsontype !== undefined) {
|
|
242
|
+
return REDACT_LEAF
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// The driver serializes a Map via its entries; mirror that as a document so
|
|
246
|
+
// the tag matches the wire shape.
|
|
247
|
+
if (isMap(value)) value = Object.fromEntries(value)
|
|
248
|
+
|
|
249
|
+
ancestors.push(value)
|
|
250
|
+
|
|
251
|
+
let result
|
|
252
|
+
if (Array.isArray(value)) {
|
|
253
|
+
result = '['
|
|
254
|
+
let sep = ''
|
|
255
|
+
for (let i = 0; i < value.length; i++) {
|
|
256
|
+
// JSON.stringify renders unsupported leaves (function, symbol, undefined) as null in arrays.
|
|
257
|
+
result += sep + (classifyForNone(value[i], ancestors) ?? 'null')
|
|
258
|
+
sep = ','
|
|
259
|
+
}
|
|
260
|
+
result += ']'
|
|
261
|
+
} else {
|
|
262
|
+
result = '{'
|
|
263
|
+
let sep = ''
|
|
264
|
+
for (const key of Object.keys(value)) {
|
|
265
|
+
const childResult = classifyForNone(value[key], ancestors)
|
|
266
|
+
if (childResult === undefined) continue
|
|
267
|
+
result += sep + JSON.stringify(key) + ':' + childResult
|
|
268
|
+
sep = ','
|
|
269
|
+
}
|
|
270
|
+
result += '}'
|
|
271
|
+
}
|
|
272
|
+
ancestors.pop()
|
|
273
|
+
return result
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {unknown} child
|
|
278
|
+
* @param {object[]} ancestors
|
|
279
|
+
* @returns {string | undefined}
|
|
280
|
+
*/
|
|
281
|
+
function classifyForNone (child, ancestors) {
|
|
282
|
+
if (typeof child !== 'object') return classifyLeafForNone(child)
|
|
283
|
+
if (child === null) return 'null'
|
|
284
|
+
return buildNone(child, ancestors)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @param {unknown} leaf
|
|
289
|
+
* @returns {string | undefined}
|
|
290
|
+
*/
|
|
291
|
+
function classifyLeafForNone (leaf) {
|
|
292
|
+
// Implicit `undefined` for function / symbol / undefined matches the
|
|
293
|
+
// contract callers rely on: JSON.stringify drops those property values
|
|
294
|
+
// inside objects and writes `null` in arrays.
|
|
295
|
+
switch (typeof leaf) {
|
|
296
|
+
case 'string': return JSON.stringify(leaf)
|
|
297
|
+
case 'number': return Number.isFinite(leaf) ? String(leaf) : 'null'
|
|
298
|
+
case 'boolean': return leaf ? 'true' : 'false'
|
|
299
|
+
case 'bigint': return `"${String(leaf)}"`
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @param {Record<string, unknown> | unknown[]} value
|
|
305
|
+
* @param {object[]} ancestors
|
|
306
|
+
*/
|
|
307
|
+
function buildRedact (value, ancestors) {
|
|
308
|
+
const bsontype = value._bsontype
|
|
309
|
+
if (ArrayBuffer.isView(value) || bsontype === 'Binary' || isRegExp(value) ||
|
|
310
|
+
ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
|
|
311
|
+
return REDACT_LEAF
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Mirror JSON.stringify: when `toJSON` is present, walk its result (which
|
|
315
|
+
// wrappers like Timestamp / Decimal128 expand to `{$timestamp: "..."}` etc).
|
|
316
|
+
// A primitive, null, or self-reference collapses to the sentinel — master's
|
|
317
|
+
// `value === original` short-circuit.
|
|
318
|
+
if (typeof value.toJSON === 'function') {
|
|
319
|
+
const json = value.toJSON()
|
|
320
|
+
if (typeof json !== 'object' || json === null || json === value) return REDACT_LEAF
|
|
321
|
+
// Re-screen: toJSON can return a TypedArray or Binary BSON wrapper.
|
|
322
|
+
if (ArrayBuffer.isView(json) || json._bsontype === 'Binary') return REDACT_LEAF
|
|
323
|
+
value = json
|
|
324
|
+
} else if (bsontype !== undefined) {
|
|
325
|
+
return REDACT_LEAF
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (isMap(value)) value = Object.fromEntries(value)
|
|
329
|
+
|
|
330
|
+
ancestors.push(value)
|
|
331
|
+
|
|
332
|
+
let result
|
|
333
|
+
if (Array.isArray(value)) {
|
|
334
|
+
result = '['
|
|
335
|
+
let sep = ''
|
|
336
|
+
for (let i = 0; i < value.length; i++) {
|
|
337
|
+
result += sep + classifyForRedact(value[i], ancestors)
|
|
338
|
+
sep = ','
|
|
339
|
+
}
|
|
340
|
+
result += ']'
|
|
341
|
+
} else {
|
|
342
|
+
result = '{'
|
|
343
|
+
let sep = ''
|
|
344
|
+
for (const key of Object.keys(value)) {
|
|
345
|
+
result += sep + JSON.stringify(key) + ':' + classifyForRedact(value[key], ancestors)
|
|
346
|
+
sep = ','
|
|
347
|
+
}
|
|
348
|
+
result += '}'
|
|
349
|
+
}
|
|
350
|
+
ancestors.pop()
|
|
351
|
+
return result
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* @param {unknown} child
|
|
356
|
+
* @param {object[]} ancestors
|
|
357
|
+
*/
|
|
358
|
+
function classifyForRedact (child, ancestors) {
|
|
359
|
+
if (typeof child !== 'object' || child === null) return REDACT_LEAF
|
|
360
|
+
return buildRedact(child, ancestors)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const TYPE_OBJECT = '"object"'
|
|
364
|
+
const TYPE_NULL = '"null"'
|
|
365
|
+
const TYPE_BY_TYPEOF = {
|
|
366
|
+
string: '"string"',
|
|
367
|
+
number: '"number"',
|
|
368
|
+
boolean: '"boolean"',
|
|
369
|
+
bigint: '"bigint"',
|
|
370
|
+
undefined: '"undefined"',
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @param {Record<string, unknown> | unknown[]} value
|
|
375
|
+
* @param {object[]} ancestors
|
|
376
|
+
*/
|
|
377
|
+
function buildTypes (value, ancestors) {
|
|
378
|
+
const bsontype = value._bsontype
|
|
379
|
+
if (ArrayBuffer.isView(value) || bsontype === 'Binary' || isRegExp(value) ||
|
|
380
|
+
ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
|
|
381
|
+
return TYPE_OBJECT
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (typeof value.toJSON === 'function') {
|
|
385
|
+
const json = value.toJSON()
|
|
386
|
+
if (typeof json !== 'object' ||
|
|
387
|
+
json === null ||
|
|
388
|
+
json === value ||
|
|
389
|
+
ArrayBuffer.isView(json) ||
|
|
390
|
+
json._bsontype === 'Binary') {
|
|
391
|
+
return TYPE_OBJECT
|
|
392
|
+
}
|
|
393
|
+
value = json
|
|
394
|
+
} else if (bsontype !== undefined) {
|
|
395
|
+
return TYPE_OBJECT
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (isMap(value)) value = Object.fromEntries(value)
|
|
399
|
+
|
|
400
|
+
ancestors.push(value)
|
|
401
|
+
|
|
402
|
+
let result
|
|
403
|
+
if (Array.isArray(value)) {
|
|
404
|
+
result = '['
|
|
405
|
+
let sep = ''
|
|
406
|
+
for (let i = 0; i < value.length; i++) {
|
|
407
|
+
// JSON.stringify renders unsupported leaves (function, symbol) as null in arrays.
|
|
408
|
+
result += sep + (classifyForTypes(value[i], ancestors) ?? 'null')
|
|
409
|
+
sep = ','
|
|
410
|
+
}
|
|
411
|
+
result += ']'
|
|
412
|
+
} else {
|
|
413
|
+
result = '{'
|
|
414
|
+
let sep = ''
|
|
415
|
+
for (const key of Object.keys(value)) {
|
|
416
|
+
const childResult = classifyForTypes(value[key], ancestors)
|
|
417
|
+
if (childResult === undefined) continue
|
|
418
|
+
result += sep + JSON.stringify(key) + ':' + childResult
|
|
419
|
+
sep = ','
|
|
420
|
+
}
|
|
421
|
+
result += '}'
|
|
422
|
+
}
|
|
423
|
+
ancestors.pop()
|
|
424
|
+
return result
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* @param {unknown} child
|
|
429
|
+
* @param {object[]} ancestors
|
|
430
|
+
*/
|
|
431
|
+
function classifyForTypes (child, ancestors) {
|
|
432
|
+
if (typeof child !== 'object') return TYPE_BY_TYPEOF[typeof child]
|
|
433
|
+
if (child === null) return TYPE_NULL
|
|
434
|
+
return buildTypes(child, ancestors)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** @param {unknown} value */
|
|
438
|
+
function normaliseObfuscateQuery (value) {
|
|
439
|
+
if (value === 'types' || value === 'redact') return value
|
|
440
|
+
return 'none'
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function isHeartbeat (ops, config) {
|
|
444
|
+
// Check if it's a heartbeat command https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.md
|
|
445
|
+
return (
|
|
446
|
+
ops &&
|
|
447
|
+
typeof ops === 'object' &&
|
|
448
|
+
(ops.hello === 1 || ops.helloOk === true || ops.ismaster === 1 || ops.isMaster === 1)
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
module.exports = MongodbCoreQueryPlugin
|
|
@@ -87,7 +87,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
87
87
|
if (isEarlyFlakeDetectionFaulty) {
|
|
88
88
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
89
89
|
}
|
|
90
|
-
if (this.numFailedSuites > 0) {
|
|
90
|
+
if (status === 'fail' && this.numFailedSuites > 0) {
|
|
91
91
|
let errorMessage = `Test suites failed: ${this.numFailedSuites}.`
|
|
92
92
|
if (this.numFailedTests > 0) {
|
|
93
93
|
errorMessage += ` Tests failed: ${this.numFailedTests}`
|
|
@@ -113,6 +113,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
113
113
|
appClosingTelemetry()
|
|
114
114
|
this.tracer._exporter.flush(onDone)
|
|
115
115
|
this.numFailedTests = 0
|
|
116
|
+
this.numFailedSuites = 0
|
|
116
117
|
})
|
|
117
118
|
|
|
118
119
|
this.addBind('ci:playwright:test-suite:start', (ctx) => {
|
|
@@ -408,7 +409,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
408
409
|
}
|
|
409
410
|
stepSpan.finish(stepStartTime + stepDuration)
|
|
410
411
|
}
|
|
411
|
-
if (
|
|
412
|
+
if (finalStatus === 'fail') {
|
|
412
413
|
this.numFailedTests++
|
|
413
414
|
}
|
|
414
415
|
|
|
@@ -192,7 +192,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
192
192
|
return ctx.currentStore
|
|
193
193
|
})
|
|
194
194
|
|
|
195
|
-
this.addSub('ci:vitest:test:pass', ({ span, task, finalStatus, earlyFlakeAbortReason }) => {
|
|
195
|
+
this.addSub('ci:vitest:test:pass', ({ span, task, finalStatus, earlyFlakeAbortReason, promises }) => {
|
|
196
196
|
if (span) {
|
|
197
197
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(span))
|
|
198
198
|
span.setTag(TEST_STATUS, 'pass')
|
|
@@ -202,8 +202,21 @@ class VitestPlugin extends CiPlugin {
|
|
|
202
202
|
if (earlyFlakeAbortReason) {
|
|
203
203
|
span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
|
|
204
204
|
}
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
const finish = () => {
|
|
206
|
+
span.finish(this.taskToFinishTime.get(task))
|
|
207
|
+
finishAllTraceSpans(span)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (finalStatus) {
|
|
211
|
+
if (promises && this.diBreakpointHitPromise) {
|
|
212
|
+
promises.hitBreakpointPromise = this.waitForPreparedDiBreakpointHit().then(finish)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
finish()
|
|
216
|
+
this.cancelDiBreakpointHitWait()
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
finish()
|
|
207
220
|
}
|
|
208
221
|
})
|
|
209
222
|
|
|
@@ -212,6 +225,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
212
225
|
duration,
|
|
213
226
|
error,
|
|
214
227
|
shouldSetProbe,
|
|
228
|
+
shouldWaitForHitProbe,
|
|
215
229
|
promises,
|
|
216
230
|
hasFailedAllRetries,
|
|
217
231
|
attemptToFixFailed,
|
|
@@ -227,7 +241,8 @@ class VitestPlugin extends CiPlugin {
|
|
|
227
241
|
const { file, line, stackIndex, setProbePromise } = probeInformation
|
|
228
242
|
this.runningTestProbe = { file, line }
|
|
229
243
|
this.testErrorStackIndex = stackIndex
|
|
230
|
-
|
|
244
|
+
this.prepareDiBreakpointHitWait()
|
|
245
|
+
promises.setProbePromise = this.waitForDiOperation(setProbePromise)
|
|
231
246
|
}
|
|
232
247
|
}
|
|
233
248
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(span))
|
|
@@ -248,12 +263,31 @@ class VitestPlugin extends CiPlugin {
|
|
|
248
263
|
if (earlyFlakeAbortReason) {
|
|
249
264
|
span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
|
|
250
265
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
266
|
+
const finish = () => {
|
|
267
|
+
if (duration) {
|
|
268
|
+
span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds
|
|
269
|
+
} else {
|
|
270
|
+
span.finish() // `duration` is empty for retries, so we'll use clock time
|
|
271
|
+
}
|
|
272
|
+
finishAllTraceSpans(span)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!shouldSetProbe && finalStatus && promises && this.diBreakpointHitPromise) {
|
|
276
|
+
promises.hitBreakpointPromise = this.waitForPreparedDiBreakpointHit().then(finish)
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
finish()
|
|
280
|
+
if (shouldWaitForHitProbe) {
|
|
281
|
+
this.prepareDiBreakpointHitWait()
|
|
282
|
+
} else if (!shouldSetProbe) {
|
|
283
|
+
this.cancelDiBreakpointHitWait()
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
this.addSub('ci:vitest:test:di:wait', ({ promises }) => {
|
|
288
|
+
if (this.di) {
|
|
289
|
+
promises.hitBreakpointPromise = this.waitForDiBreakpointHits()
|
|
255
290
|
}
|
|
256
|
-
finishAllTraceSpans(span)
|
|
257
291
|
})
|
|
258
292
|
|
|
259
293
|
this.addSub('ci:vitest:test:skip', ({
|
|
@@ -2,15 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
const log = require('../log')
|
|
4
4
|
const { incomingHttpRequestStart } = require('./channels')
|
|
5
|
-
const
|
|
6
|
-
const vercelAiIntegration = require('./integrations/vercel-ai')
|
|
5
|
+
const integrations = require('./integrations')
|
|
7
6
|
const AIGuard = require('./sdk')
|
|
8
7
|
|
|
9
8
|
let isEnabled = false
|
|
10
9
|
let aiguard
|
|
11
|
-
let block
|
|
12
|
-
let disableOpenAIIntegration
|
|
13
|
-
let disableVercelAiIntegration
|
|
14
10
|
|
|
15
11
|
function onIncomingHttpRequestStart () {
|
|
16
12
|
// No-op: subscribing ensures the HTTP plugin spreads req onto the store
|
|
@@ -21,31 +17,30 @@ function enable (tracer, config) {
|
|
|
21
17
|
|
|
22
18
|
try {
|
|
23
19
|
aiguard = new AIGuard(tracer, config)
|
|
24
|
-
block = config.experimental?.aiguard?.block !== false
|
|
20
|
+
const block = config.experimental?.aiguard?.block !== false
|
|
25
21
|
|
|
26
22
|
incomingHttpRequestStart.subscribe(onIncomingHttpRequestStart)
|
|
27
|
-
|
|
28
|
-
disableVercelAiIntegration = vercelAiIntegration.enable(aiguard, block)
|
|
23
|
+
integrations.enable(aiguard, block)
|
|
29
24
|
|
|
30
25
|
isEnabled = true
|
|
31
26
|
} catch (err) {
|
|
32
27
|
log.error('AIGuard: unexpected error during initialization: %s', err.message)
|
|
33
|
-
|
|
28
|
+
reset()
|
|
34
29
|
}
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
function disable () {
|
|
38
33
|
if (!isEnabled) return
|
|
39
34
|
|
|
35
|
+
reset()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function reset () {
|
|
40
39
|
incomingHttpRequestStart.unsubscribe(onIncomingHttpRequestStart)
|
|
41
|
-
|
|
42
|
-
disableVercelAiIntegration?.()
|
|
40
|
+
integrations.disable()
|
|
43
41
|
|
|
44
42
|
aiguard = undefined
|
|
45
43
|
isEnabled = false
|
|
46
|
-
block = false
|
|
47
|
-
disableOpenAIIntegration = undefined
|
|
48
|
-
disableVercelAiIntegration = undefined
|
|
49
44
|
}
|
|
50
45
|
|
|
51
46
|
module.exports = { enable, disable }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const openai = require('./openai')
|
|
4
|
+
const vercelAi = require('./vercel-ai')
|
|
5
|
+
|
|
6
|
+
let isEnabled = false
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Enables all AI Guard provider integrations.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} aiguard
|
|
12
|
+
* @param {boolean} block
|
|
13
|
+
*/
|
|
14
|
+
function enable (aiguard, block) {
|
|
15
|
+
if (isEnabled) return
|
|
16
|
+
|
|
17
|
+
openai.enable(aiguard, block)
|
|
18
|
+
vercelAi.enable(aiguard, block)
|
|
19
|
+
|
|
20
|
+
isEnabled = true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function disable () {
|
|
24
|
+
vercelAi.disable()
|
|
25
|
+
openai.disable()
|
|
26
|
+
isEnabled = false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
enable,
|
|
31
|
+
disable,
|
|
32
|
+
openai,
|
|
33
|
+
vercelAi,
|
|
34
|
+
}
|