dd-trace 4.44.0 → 4.46.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 +2 -1
- package/package.json +5 -5
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/apollo-server.js +1 -1
- package/packages/datadog-instrumentations/src/aws-sdk.js +4 -4
- package/packages/datadog-instrumentations/src/body-parser.js +17 -5
- package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
- package/packages/datadog-instrumentations/src/child_process.js +2 -2
- package/packages/datadog-instrumentations/src/connect.js +4 -4
- package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
- package/packages/datadog-instrumentations/src/couchbase.js +12 -12
- package/packages/datadog-instrumentations/src/cucumber.js +16 -5
- package/packages/datadog-instrumentations/src/dns.js +10 -10
- package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +3 -3
- package/packages/datadog-instrumentations/src/express.js +4 -4
- package/packages/datadog-instrumentations/src/fastify.js +6 -6
- package/packages/datadog-instrumentations/src/fetch.js +1 -1
- package/packages/datadog-instrumentations/src/find-my-way.js +2 -2
- package/packages/datadog-instrumentations/src/fs.js +2 -2
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +2 -2
- package/packages/datadog-instrumentations/src/grpc/client.js +4 -6
- package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
- package/packages/datadog-instrumentations/src/hapi.js +10 -13
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -2
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -2
- package/packages/datadog-instrumentations/src/http/client.js +3 -3
- package/packages/datadog-instrumentations/src/jest.js +5 -4
- package/packages/datadog-instrumentations/src/knex.js +2 -2
- package/packages/datadog-instrumentations/src/koa.js +5 -5
- package/packages/datadog-instrumentations/src/ldapjs.js +1 -1
- package/packages/datadog-instrumentations/src/mariadb.js +8 -8
- package/packages/datadog-instrumentations/src/memcached.js +2 -2
- package/packages/datadog-instrumentations/src/microgateway-core.js +4 -4
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +91 -70
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -3
- package/packages/datadog-instrumentations/src/mocha.js +4 -0
- package/packages/datadog-instrumentations/src/moleculer/server.js +2 -2
- package/packages/datadog-instrumentations/src/mongodb-core.js +7 -7
- package/packages/datadog-instrumentations/src/mongoose.js +5 -6
- package/packages/datadog-instrumentations/src/mysql.js +3 -3
- package/packages/datadog-instrumentations/src/mysql2.js +6 -6
- package/packages/datadog-instrumentations/src/net.js +2 -2
- package/packages/datadog-instrumentations/src/next.js +5 -5
- package/packages/datadog-instrumentations/src/nyc.js +23 -0
- package/packages/datadog-instrumentations/src/openai.js +58 -69
- package/packages/datadog-instrumentations/src/oracledb.js +8 -8
- package/packages/datadog-instrumentations/src/passport-http.js +1 -1
- package/packages/datadog-instrumentations/src/passport-local.js +1 -1
- package/packages/datadog-instrumentations/src/passport-utils.js +1 -1
- package/packages/datadog-instrumentations/src/pg.js +1 -1
- package/packages/datadog-instrumentations/src/pino.js +4 -4
- package/packages/datadog-instrumentations/src/playwright.js +6 -4
- package/packages/datadog-instrumentations/src/redis.js +2 -2
- package/packages/datadog-instrumentations/src/restify.js +4 -4
- package/packages/datadog-instrumentations/src/rhea.js +4 -4
- package/packages/datadog-instrumentations/src/router.js +5 -5
- package/packages/datadog-instrumentations/src/sharedb.js +2 -2
- package/packages/datadog-instrumentations/src/vitest.js +22 -5
- package/packages/datadog-instrumentations/src/winston.js +2 -3
- package/packages/datadog-plugin-cucumber/src/index.js +12 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +21 -10
- package/packages/datadog-plugin-hapi/src/index.js +2 -2
- package/packages/datadog-plugin-jest/src/index.js +18 -4
- package/packages/datadog-plugin-mocha/src/index.js +25 -6
- package/packages/datadog-plugin-nyc/src/index.js +35 -0
- package/packages/datadog-plugin-openai/src/index.js +58 -47
- package/packages/datadog-plugin-playwright/src/index.js +9 -4
- package/packages/datadog-plugin-vitest/src/index.js +30 -4
- package/packages/datadog-shimmer/src/shimmer.js +144 -10
- package/packages/dd-trace/src/appsec/blocking.js +23 -17
- package/packages/dd-trace/src/appsec/graphql.js +3 -1
- package/packages/dd-trace/src/appsec/iast/iast-log.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/manager.js +4 -1
- package/packages/dd-trace/src/appsec/rule_manager.js +8 -0
- package/packages/dd-trace/src/appsec/telemetry.js +3 -3
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +40 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -4
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -4
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +8 -7
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -4
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +2 -4
- package/packages/dd-trace/src/ci-visibility/telemetry.js +29 -2
- package/packages/dd-trace/src/config.js +157 -142
- package/packages/dd-trace/src/lambda/handler.js +1 -0
- package/packages/dd-trace/src/lambda/index.js +12 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -39
- package/packages/dd-trace/src/opentelemetry/span_context.js +2 -2
- package/packages/dd-trace/src/opentelemetry/tracer.js +23 -14
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +9 -1
- package/packages/dd-trace/src/opentracing/propagation/log.js +1 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +61 -6
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -2
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +14 -1
- package/packages/dd-trace/src/plugins/util/test.js +1 -5
- package/packages/dd-trace/src/profiler.js +15 -5
- package/packages/dd-trace/src/profiling/config.js +2 -4
- package/packages/dd-trace/src/profiling/exporter_cli.js +13 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +7 -1
- package/packages/dd-trace/src/profiling/profiler.js +0 -9
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +49 -58
- package/packages/dd-trace/src/proxy.js +21 -21
- package/packages/dd-trace/src/telemetry/index.js +24 -7
- package/packages/dd-trace/src/telemetry/logs/index.js +20 -0
|
@@ -31,6 +31,9 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
31
31
|
static get id () { return 'openai' }
|
|
32
32
|
static get operation () { return 'request' }
|
|
33
33
|
static get system () { return 'openai' }
|
|
34
|
+
static get prefix () {
|
|
35
|
+
return 'tracing:apm:openai:request'
|
|
36
|
+
}
|
|
34
37
|
|
|
35
38
|
constructor (...args) {
|
|
36
39
|
super(...args)
|
|
@@ -55,8 +58,10 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
55
58
|
super.configure(config)
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
bindStart (ctx) {
|
|
62
|
+
const { methodName, args, basePath, apiKey } = ctx
|
|
59
63
|
const payload = normalizeRequestPayload(methodName, args)
|
|
64
|
+
const store = storage.getStore() || {}
|
|
60
65
|
|
|
61
66
|
const span = this.startSpan('openai.request', {
|
|
62
67
|
service: this.config.service,
|
|
@@ -87,18 +92,16 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
87
92
|
'openai.request.user': payload.user,
|
|
88
93
|
'openai.request.file_id': payload.file_id // deleteFile, retrieveFile, downloadFile
|
|
89
94
|
}
|
|
90
|
-
})
|
|
95
|
+
}, false)
|
|
91
96
|
|
|
92
|
-
const
|
|
93
|
-
const store = Object.create(null)
|
|
94
|
-
fullStore.openai = store // namespacing these fields
|
|
97
|
+
const openaiStore = Object.create(null)
|
|
95
98
|
|
|
96
99
|
const tags = {} // The remaining tags are added one at a time
|
|
97
100
|
|
|
98
101
|
// createChatCompletion, createCompletion, createImage, createImageEdit, createTranscription, createTranslation
|
|
99
102
|
if (payload.prompt) {
|
|
100
103
|
const prompt = payload.prompt
|
|
101
|
-
|
|
104
|
+
openaiStore.prompt = prompt
|
|
102
105
|
if (typeof prompt === 'string' || (Array.isArray(prompt) && typeof prompt[0] === 'number')) {
|
|
103
106
|
// This is a single prompt, either String or [Number]
|
|
104
107
|
tags['openai.request.prompt'] = normalizeStringOrTokenArray(prompt, true)
|
|
@@ -114,7 +117,7 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
114
117
|
if (payload.input) {
|
|
115
118
|
const normalized = normalizeStringOrTokenArray(payload.input, false)
|
|
116
119
|
tags['openai.request.input'] = truncateText(normalized)
|
|
117
|
-
|
|
120
|
+
openaiStore.input = normalized
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
// createChatCompletion, createCompletion
|
|
@@ -141,12 +144,12 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
141
144
|
case 'images.edit':
|
|
142
145
|
case 'createImageVariation':
|
|
143
146
|
case 'images.createVariation':
|
|
144
|
-
commonCreateImageRequestExtraction(tags, payload,
|
|
147
|
+
commonCreateImageRequestExtraction(tags, payload, openaiStore)
|
|
145
148
|
break
|
|
146
149
|
|
|
147
150
|
case 'createChatCompletion':
|
|
148
151
|
case 'chat.completions.create':
|
|
149
|
-
createChatCompletionRequestExtraction(tags, payload,
|
|
152
|
+
createChatCompletionRequestExtraction(tags, payload, openaiStore)
|
|
150
153
|
break
|
|
151
154
|
|
|
152
155
|
case 'createFile':
|
|
@@ -160,7 +163,7 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
160
163
|
case 'audio.transcriptions.create':
|
|
161
164
|
case 'createTranslation':
|
|
162
165
|
case 'audio.translations.create':
|
|
163
|
-
commonCreateAudioRequestExtraction(tags, payload,
|
|
166
|
+
commonCreateAudioRequestExtraction(tags, payload, openaiStore)
|
|
164
167
|
break
|
|
165
168
|
|
|
166
169
|
case 'retrieveModel':
|
|
@@ -184,23 +187,32 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
184
187
|
|
|
185
188
|
case 'createEdit':
|
|
186
189
|
case 'edits.create':
|
|
187
|
-
createEditRequestExtraction(tags, payload,
|
|
190
|
+
createEditRequestExtraction(tags, payload, openaiStore)
|
|
188
191
|
break
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
span.addTags(tags)
|
|
195
|
+
|
|
196
|
+
ctx.currentStore = { ...store, span, openai: openaiStore }
|
|
197
|
+
|
|
198
|
+
return ctx.currentStore
|
|
192
199
|
}
|
|
193
200
|
|
|
194
|
-
|
|
195
|
-
const
|
|
201
|
+
asyncEnd (ctx) {
|
|
202
|
+
const { result } = ctx
|
|
203
|
+
const store = ctx.currentStore
|
|
204
|
+
|
|
205
|
+
const span = store?.span
|
|
206
|
+
if (!span) return
|
|
207
|
+
|
|
196
208
|
const error = !!span.context()._tags.error
|
|
197
209
|
|
|
198
210
|
let headers, body, method, path
|
|
199
211
|
if (!error) {
|
|
200
|
-
headers =
|
|
201
|
-
body =
|
|
202
|
-
method =
|
|
203
|
-
path =
|
|
212
|
+
headers = result.headers
|
|
213
|
+
body = result.data
|
|
214
|
+
method = result.request.method
|
|
215
|
+
path = result.request.path
|
|
204
216
|
}
|
|
205
217
|
|
|
206
218
|
if (!error && headers?.constructor.name === 'Headers') {
|
|
@@ -210,10 +222,9 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
210
222
|
|
|
211
223
|
body = coerceResponseBody(body, methodName)
|
|
212
224
|
|
|
213
|
-
const
|
|
214
|
-
const store = fullStore.openai
|
|
225
|
+
const openaiStore = store.openai
|
|
215
226
|
|
|
216
|
-
if (!error && (path
|
|
227
|
+
if (!error && (path?.startsWith('https://') || path?.startsWith('http://'))) {
|
|
217
228
|
// basic checking for if the path was set as a full URL
|
|
218
229
|
// not using a full regex as it will likely be "https://api.openai.com/..."
|
|
219
230
|
path = new URL(path).pathname
|
|
@@ -239,11 +250,11 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
239
250
|
'openai.response.created_at': body.created_at
|
|
240
251
|
}
|
|
241
252
|
|
|
242
|
-
responseDataExtractionByMethod(methodName, tags, body,
|
|
253
|
+
responseDataExtractionByMethod(methodName, tags, body, openaiStore)
|
|
243
254
|
span.addTags(tags)
|
|
244
255
|
|
|
245
|
-
|
|
246
|
-
this.sendLog(methodName, span, tags,
|
|
256
|
+
span.finish()
|
|
257
|
+
this.sendLog(methodName, span, tags, openaiStore, error)
|
|
247
258
|
this.sendMetrics(headers, body, endpoint, span._duration, error, tags)
|
|
248
259
|
}
|
|
249
260
|
|
|
@@ -308,15 +319,15 @@ class OpenApiPlugin extends TracingPlugin {
|
|
|
308
319
|
}
|
|
309
320
|
}
|
|
310
321
|
|
|
311
|
-
sendLog (methodName, span, tags,
|
|
312
|
-
if (!
|
|
313
|
-
if (!Object.keys(
|
|
322
|
+
sendLog (methodName, span, tags, openaiStore, error) {
|
|
323
|
+
if (!openaiStore) return
|
|
324
|
+
if (!Object.keys(openaiStore).length) return
|
|
314
325
|
if (!this.sampler.isSampled()) return
|
|
315
326
|
|
|
316
327
|
const log = {
|
|
317
328
|
status: error ? 'error' : 'info',
|
|
318
329
|
message: `sampled ${methodName}`,
|
|
319
|
-
...
|
|
330
|
+
...openaiStore
|
|
320
331
|
}
|
|
321
332
|
|
|
322
333
|
this.logger.log(log, span, tags)
|
|
@@ -400,21 +411,21 @@ function countTokens (content, model) {
|
|
|
400
411
|
}
|
|
401
412
|
}
|
|
402
413
|
|
|
403
|
-
function createEditRequestExtraction (tags, payload,
|
|
414
|
+
function createEditRequestExtraction (tags, payload, openaiStore) {
|
|
404
415
|
const instruction = payload.instruction
|
|
405
416
|
tags['openai.request.instruction'] = instruction
|
|
406
|
-
|
|
417
|
+
openaiStore.instruction = instruction
|
|
407
418
|
}
|
|
408
419
|
|
|
409
420
|
function retrieveModelRequestExtraction (tags, payload) {
|
|
410
421
|
tags['openai.request.id'] = payload.id
|
|
411
422
|
}
|
|
412
423
|
|
|
413
|
-
function createChatCompletionRequestExtraction (tags, payload,
|
|
424
|
+
function createChatCompletionRequestExtraction (tags, payload, openaiStore) {
|
|
414
425
|
const messages = payload.messages
|
|
415
426
|
if (!defensiveArrayLength(messages)) return
|
|
416
427
|
|
|
417
|
-
|
|
428
|
+
openaiStore.messages = payload.messages
|
|
418
429
|
for (let i = 0; i < payload.messages.length; i++) {
|
|
419
430
|
const message = payload.messages[i]
|
|
420
431
|
tagChatCompletionRequestContent(message.content, i, tags)
|
|
@@ -424,20 +435,20 @@ function createChatCompletionRequestExtraction (tags, payload, store) {
|
|
|
424
435
|
}
|
|
425
436
|
}
|
|
426
437
|
|
|
427
|
-
function commonCreateImageRequestExtraction (tags, payload,
|
|
438
|
+
function commonCreateImageRequestExtraction (tags, payload, openaiStore) {
|
|
428
439
|
// createImageEdit, createImageVariation
|
|
429
440
|
const img = payload.file || payload.image
|
|
430
441
|
if (img !== null && typeof img === 'object' && img.path) {
|
|
431
442
|
const file = path.basename(img.path)
|
|
432
443
|
tags['openai.request.image'] = file
|
|
433
|
-
|
|
444
|
+
openaiStore.file = file
|
|
434
445
|
}
|
|
435
446
|
|
|
436
447
|
// createImageEdit
|
|
437
448
|
if (payload.mask !== null && typeof payload.mask === 'object' && payload.mask.path) {
|
|
438
449
|
const mask = path.basename(payload.mask.path)
|
|
439
450
|
tags['openai.request.mask'] = mask
|
|
440
|
-
|
|
451
|
+
openaiStore.mask = mask
|
|
441
452
|
}
|
|
442
453
|
|
|
443
454
|
tags['openai.request.size'] = payload.size
|
|
@@ -445,7 +456,7 @@ function commonCreateImageRequestExtraction (tags, payload, store) {
|
|
|
445
456
|
tags['openai.request.language'] = payload.language
|
|
446
457
|
}
|
|
447
458
|
|
|
448
|
-
function responseDataExtractionByMethod (methodName, tags, body,
|
|
459
|
+
function responseDataExtractionByMethod (methodName, tags, body, openaiStore) {
|
|
449
460
|
switch (methodName) {
|
|
450
461
|
case 'createModeration':
|
|
451
462
|
case 'moderations.create':
|
|
@@ -458,7 +469,7 @@ function responseDataExtractionByMethod (methodName, tags, body, store) {
|
|
|
458
469
|
case 'chat.completions.create':
|
|
459
470
|
case 'createEdit':
|
|
460
471
|
case 'edits.create':
|
|
461
|
-
commonCreateResponseExtraction(tags, body,
|
|
472
|
+
commonCreateResponseExtraction(tags, body, openaiStore, methodName)
|
|
462
473
|
break
|
|
463
474
|
|
|
464
475
|
case 'listFiles':
|
|
@@ -474,7 +485,7 @@ function responseDataExtractionByMethod (methodName, tags, body, store) {
|
|
|
474
485
|
|
|
475
486
|
case 'createEmbedding':
|
|
476
487
|
case 'embeddings.create':
|
|
477
|
-
createEmbeddingResponseExtraction(tags, body)
|
|
488
|
+
createEmbeddingResponseExtraction(tags, body, openaiStore)
|
|
478
489
|
break
|
|
479
490
|
|
|
480
491
|
case 'createFile':
|
|
@@ -629,14 +640,14 @@ function deleteFileResponseExtraction (tags, body) {
|
|
|
629
640
|
tags['openai.response.id'] = body.id
|
|
630
641
|
}
|
|
631
642
|
|
|
632
|
-
function commonCreateAudioRequestExtraction (tags, body,
|
|
643
|
+
function commonCreateAudioRequestExtraction (tags, body, openaiStore) {
|
|
633
644
|
tags['openai.request.response_format'] = body.response_format
|
|
634
645
|
tags['openai.request.language'] = body.language
|
|
635
646
|
|
|
636
647
|
if (body.file !== null && typeof body.file === 'object' && body.file.path) {
|
|
637
648
|
const filename = path.basename(body.file.path)
|
|
638
649
|
tags['openai.request.filename'] = filename
|
|
639
|
-
|
|
650
|
+
openaiStore.file = filename
|
|
640
651
|
}
|
|
641
652
|
}
|
|
642
653
|
|
|
@@ -659,8 +670,8 @@ function createRetrieveFileResponseExtraction (tags, body) {
|
|
|
659
670
|
tags['openai.response.status_details'] = body.status_details
|
|
660
671
|
}
|
|
661
672
|
|
|
662
|
-
function createEmbeddingResponseExtraction (tags, body) {
|
|
663
|
-
usageExtraction(tags, body)
|
|
673
|
+
function createEmbeddingResponseExtraction (tags, body, openaiStore) {
|
|
674
|
+
usageExtraction(tags, body, openaiStore)
|
|
664
675
|
|
|
665
676
|
if (!body.data) return
|
|
666
677
|
|
|
@@ -694,14 +705,14 @@ function createModerationResponseExtraction (tags, body) {
|
|
|
694
705
|
}
|
|
695
706
|
|
|
696
707
|
// createCompletion, createChatCompletion, createEdit
|
|
697
|
-
function commonCreateResponseExtraction (tags, body,
|
|
698
|
-
usageExtraction(tags, body, methodName)
|
|
708
|
+
function commonCreateResponseExtraction (tags, body, openaiStore, methodName) {
|
|
709
|
+
usageExtraction(tags, body, methodName, openaiStore)
|
|
699
710
|
|
|
700
711
|
if (!body.choices) return
|
|
701
712
|
|
|
702
713
|
tags['openai.response.choices_count'] = body.choices.length
|
|
703
714
|
|
|
704
|
-
|
|
715
|
+
openaiStore.choices = body.choices
|
|
705
716
|
|
|
706
717
|
for (let choiceIdx = 0; choiceIdx < body.choices.length; choiceIdx++) {
|
|
707
718
|
const choice = body.choices[choiceIdx]
|
|
@@ -735,7 +746,7 @@ function commonCreateResponseExtraction (tags, body, store, methodName) {
|
|
|
735
746
|
}
|
|
736
747
|
|
|
737
748
|
// createCompletion, createChatCompletion, createEdit, createEmbedding
|
|
738
|
-
function usageExtraction (tags, body, methodName) {
|
|
749
|
+
function usageExtraction (tags, body, methodName, openaiStore) {
|
|
739
750
|
let promptTokens = 0
|
|
740
751
|
let completionTokens = 0
|
|
741
752
|
let totalTokens = 0
|
|
@@ -743,14 +754,14 @@ function usageExtraction (tags, body, methodName) {
|
|
|
743
754
|
promptTokens = body.usage.prompt_tokens
|
|
744
755
|
completionTokens = body.usage.completion_tokens
|
|
745
756
|
totalTokens = body.usage.total_tokens
|
|
746
|
-
} else if (['chat.completions.create', 'completions.create'].includes(methodName)) {
|
|
757
|
+
} else if (body.model && ['chat.completions.create', 'completions.create'].includes(methodName)) {
|
|
747
758
|
// estimate tokens based on method name for completions and chat completions
|
|
748
759
|
const { model } = body
|
|
749
760
|
let promptEstimated = false
|
|
750
761
|
let completionEstimated = false
|
|
751
762
|
|
|
752
763
|
// prompt tokens
|
|
753
|
-
const payload =
|
|
764
|
+
const payload = openaiStore
|
|
754
765
|
const promptTokensCount = countPromptTokens(methodName, payload, model)
|
|
755
766
|
promptTokens = promptTokensCount.promptTokens
|
|
756
767
|
promptEstimated = promptTokensCount.promptEstimated
|
|
@@ -14,7 +14,8 @@ const {
|
|
|
14
14
|
TEST_CONFIGURATION_BROWSER_NAME,
|
|
15
15
|
TEST_IS_NEW,
|
|
16
16
|
TEST_IS_RETRY,
|
|
17
|
-
TEST_EARLY_FLAKE_ENABLED
|
|
17
|
+
TEST_EARLY_FLAKE_ENABLED,
|
|
18
|
+
TELEMETRY_TEST_SESSION
|
|
18
19
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
19
20
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
20
21
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -59,6 +60,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
59
60
|
this.testSessionSpan.finish()
|
|
60
61
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
61
62
|
finishAllTraceSpans(this.testSessionSpan)
|
|
63
|
+
this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName })
|
|
62
64
|
appClosingTelemetry()
|
|
63
65
|
this.tracer._exporter.flush(onDone)
|
|
64
66
|
this.numFailedTests = 0
|
|
@@ -160,8 +162,6 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
160
162
|
stepSpan.finish(stepStartTime + stepDuration)
|
|
161
163
|
})
|
|
162
164
|
|
|
163
|
-
span.finish()
|
|
164
|
-
|
|
165
165
|
if (testStatus === 'fail') {
|
|
166
166
|
this.numFailedTests++
|
|
167
167
|
}
|
|
@@ -169,8 +169,13 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
169
169
|
this.telemetry.ciVisEvent(
|
|
170
170
|
TELEMETRY_EVENT_FINISHED,
|
|
171
171
|
'test',
|
|
172
|
-
{
|
|
172
|
+
{
|
|
173
|
+
hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS],
|
|
174
|
+
isNew,
|
|
175
|
+
browserDriver: 'playwright'
|
|
176
|
+
}
|
|
173
177
|
)
|
|
178
|
+
span.finish()
|
|
174
179
|
|
|
175
180
|
finishAllTraceSpans(span)
|
|
176
181
|
})
|
|
@@ -7,9 +7,16 @@ const {
|
|
|
7
7
|
getTestSuitePath,
|
|
8
8
|
getTestSuiteCommonTags,
|
|
9
9
|
TEST_SOURCE_FILE,
|
|
10
|
-
TEST_IS_RETRY
|
|
10
|
+
TEST_IS_RETRY,
|
|
11
|
+
TEST_CODE_COVERAGE_LINES_PCT,
|
|
12
|
+
TEST_CODE_OWNERS
|
|
11
13
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
12
14
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
15
|
+
const {
|
|
16
|
+
TELEMETRY_EVENT_CREATED,
|
|
17
|
+
TELEMETRY_EVENT_FINISHED,
|
|
18
|
+
TELEMETRY_TEST_SESSION
|
|
19
|
+
} = require('../../dd-trace/src/ci-visibility/telemetry')
|
|
13
20
|
|
|
14
21
|
// Milliseconds that we subtract from the error test duration
|
|
15
22
|
// so that they do not overlap with the following test
|
|
@@ -64,6 +71,9 @@ class VitestPlugin extends CiPlugin {
|
|
|
64
71
|
const span = store?.span
|
|
65
72
|
|
|
66
73
|
if (span) {
|
|
74
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
|
|
75
|
+
hasCodeowners: !!span.context()._tags[TEST_CODE_OWNERS]
|
|
76
|
+
})
|
|
67
77
|
span.setTag(TEST_STATUS, 'pass')
|
|
68
78
|
span.finish(this.taskToFinishTime.get(task))
|
|
69
79
|
finishAllTraceSpans(span)
|
|
@@ -75,6 +85,9 @@ class VitestPlugin extends CiPlugin {
|
|
|
75
85
|
const span = store?.span
|
|
76
86
|
|
|
77
87
|
if (span) {
|
|
88
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
|
|
89
|
+
hasCodeowners: !!span.context()._tags[TEST_CODE_OWNERS]
|
|
90
|
+
})
|
|
78
91
|
span.setTag(TEST_STATUS, 'fail')
|
|
79
92
|
|
|
80
93
|
if (error) {
|
|
@@ -91,7 +104,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
91
104
|
|
|
92
105
|
this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath }) => {
|
|
93
106
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
94
|
-
this.startTestSpan(
|
|
107
|
+
const testSpan = this.startTestSpan(
|
|
95
108
|
testName,
|
|
96
109
|
testSuite,
|
|
97
110
|
this.testSuiteSpan,
|
|
@@ -99,7 +112,11 @@ class VitestPlugin extends CiPlugin {
|
|
|
99
112
|
[TEST_SOURCE_FILE]: testSuite,
|
|
100
113
|
[TEST_STATUS]: 'skip'
|
|
101
114
|
}
|
|
102
|
-
)
|
|
115
|
+
)
|
|
116
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
|
|
117
|
+
hasCodeowners: !!testSpan.context()._tags[TEST_CODE_OWNERS]
|
|
118
|
+
})
|
|
119
|
+
testSpan.finish()
|
|
103
120
|
})
|
|
104
121
|
|
|
105
122
|
this.addSub('ci:vitest:test-suite:start', ({ testSuiteAbsolutePath, frameworkVersion }) => {
|
|
@@ -124,6 +141,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
124
141
|
...testSuiteMetadata
|
|
125
142
|
}
|
|
126
143
|
})
|
|
144
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
127
145
|
const store = storage.getStore()
|
|
128
146
|
this.enter(testSuiteSpan, store)
|
|
129
147
|
this.testSuiteSpan = testSuiteSpan
|
|
@@ -137,6 +155,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
137
155
|
span.finish()
|
|
138
156
|
finishAllTraceSpans(span)
|
|
139
157
|
}
|
|
158
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
|
|
140
159
|
// TODO: too frequent flush - find for method in worker to decrease frequency
|
|
141
160
|
this.tracer._exporter.flush(onFinish)
|
|
142
161
|
})
|
|
@@ -150,16 +169,23 @@ class VitestPlugin extends CiPlugin {
|
|
|
150
169
|
}
|
|
151
170
|
})
|
|
152
171
|
|
|
153
|
-
this.addSub('ci:vitest:session:finish', ({ status, onFinish, error }) => {
|
|
172
|
+
this.addSub('ci:vitest:session:finish', ({ status, onFinish, error, testCodeCoverageLinesTotal }) => {
|
|
154
173
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
155
174
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
156
175
|
if (error) {
|
|
157
176
|
this.testModuleSpan.setTag('error', error)
|
|
158
177
|
this.testSessionSpan.setTag('error', error)
|
|
159
178
|
}
|
|
179
|
+
if (testCodeCoverageLinesTotal) {
|
|
180
|
+
this.testModuleSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
181
|
+
this.testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
182
|
+
}
|
|
160
183
|
this.testModuleSpan.finish()
|
|
184
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
161
185
|
this.testSessionSpan.finish()
|
|
186
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
162
187
|
finishAllTraceSpans(this.testSessionSpan)
|
|
188
|
+
this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName })
|
|
163
189
|
this.tracer._exporter.flush(onFinish)
|
|
164
190
|
})
|
|
165
191
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../../dd-trace/src/log')
|
|
4
|
+
|
|
3
5
|
// Use a weak map to avoid polluting the wrapped function/method.
|
|
4
6
|
const unwrappers = new WeakMap()
|
|
5
7
|
|
|
@@ -18,9 +20,12 @@ function copyProperties (original, wrapped) {
|
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
function wrapFunction (original, wrapper) {
|
|
24
|
+
if (typeof original === 'function') assertNotClass(original)
|
|
25
|
+
// TODO This needs to be re-done so that this and wrapMethod are distinct.
|
|
26
|
+
const target = { func: original }
|
|
27
|
+
wrapMethod(target, 'func', wrapper, typeof original !== 'function')
|
|
28
|
+
let delegate = target.func
|
|
24
29
|
|
|
25
30
|
const shim = function shim () {
|
|
26
31
|
return delegate.apply(this, arguments)
|
|
@@ -30,17 +35,144 @@ function wrapFn (original, delegate) {
|
|
|
30
35
|
delegate = original
|
|
31
36
|
})
|
|
32
37
|
|
|
33
|
-
copyProperties(original, shim)
|
|
38
|
+
if (typeof original === 'function') copyProperties(original, shim)
|
|
34
39
|
|
|
35
40
|
return shim
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const wrapFn = function (original, delegate) {
|
|
44
|
+
throw new Error('calling `wrap()` with 2 args is deprecated. Use wrapFunction instead.')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// This is only used in safe mode. It's a simple state machine to track if the
|
|
48
|
+
// original method was called and if it returned. We need this to determine if
|
|
49
|
+
// an error was thrown by the original method, or by us. We'll use one of these
|
|
50
|
+
// per call to a wrapped method.
|
|
51
|
+
class CallState {
|
|
52
|
+
constructor () {
|
|
53
|
+
this.called = false
|
|
54
|
+
this.completed = false
|
|
55
|
+
this.retVal = undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
startCall () {
|
|
59
|
+
this.called = true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
endCall (retVal) {
|
|
63
|
+
this.completed = true
|
|
64
|
+
this.retVal = retVal
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isPromise (obj) {
|
|
69
|
+
return obj && typeof obj === 'object' && typeof obj.then === 'function'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let safeMode = !!process.env.DD_INEJCTION_ENABLED
|
|
73
|
+
function setSafe (value) {
|
|
74
|
+
safeMode = value
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function wrapMethod (target, name, wrapper, noAssert) {
|
|
78
|
+
if (!noAssert) {
|
|
79
|
+
assertMethod(target, name)
|
|
80
|
+
assertFunction(wrapper)
|
|
81
|
+
}
|
|
41
82
|
|
|
42
83
|
const original = target[name]
|
|
43
|
-
|
|
84
|
+
let wrapped
|
|
85
|
+
|
|
86
|
+
if (safeMode && original) {
|
|
87
|
+
// In this mode, we make a best-effort attempt to handle errors that are thrown
|
|
88
|
+
// by us, rather than wrapped code. With such errors, we log them, and then attempt
|
|
89
|
+
// to return the result as if no wrapping was done at all.
|
|
90
|
+
//
|
|
91
|
+
// Caveats:
|
|
92
|
+
// * If the original function is called in a later iteration of the event loop,
|
|
93
|
+
// and we throw _then_, then it won't be caught by this. In practice, we always call
|
|
94
|
+
// the original function synchronously, so this is not a problem.
|
|
95
|
+
// * While async errors are dealt with here, errors in callbacks are not. This
|
|
96
|
+
// is because we don't necessarily know _for sure_ that any function arguments
|
|
97
|
+
// are wrapped by us. We could wrap them all anyway and just make that assumption,
|
|
98
|
+
// or just assume that the last argument is always a callback set by us if it's a
|
|
99
|
+
// function, but those don't seem like things we can rely on. We could add a
|
|
100
|
+
// `shimmer.markCallbackAsWrapped()` function that's a no-op outside safe-mode,
|
|
101
|
+
// but that means modifying every instrumentation. Even then, the complexity of
|
|
102
|
+
// this code increases because then we'd need to effectively do the reverse of
|
|
103
|
+
// what we're doing for synchronous functions. This is a TODO.
|
|
104
|
+
|
|
105
|
+
// We're going to hold on to current callState in this variable in this scope,
|
|
106
|
+
// which is fine because any time we reference it, we're referencing it synchronously.
|
|
107
|
+
// We'll use it in the our wrapper (which, again, is called syncrhonously), and in the
|
|
108
|
+
// errorHandler, which will already have been bound to this callState.
|
|
109
|
+
let currentCallState
|
|
110
|
+
|
|
111
|
+
// Rather than calling the original function directly from the shim wrapper, we wrap
|
|
112
|
+
// it again so that we can track if it was called and if it returned. This is because
|
|
113
|
+
// we need to know if an error was thrown by the original function, or by us.
|
|
114
|
+
// We could do this inside the `wrapper` function defined below, which would simplify
|
|
115
|
+
// managing the callState, but then we'd be calling `wrapper` on each invocation, so
|
|
116
|
+
// instead we do it here, once.
|
|
117
|
+
const innerWrapped = wrapper(function (...args) {
|
|
118
|
+
// We need to stash the callState here because of recursion.
|
|
119
|
+
const callState = currentCallState
|
|
120
|
+
callState.startCall()
|
|
121
|
+
const retVal = original.apply(this, args)
|
|
122
|
+
if (isPromise(retVal)) {
|
|
123
|
+
retVal.then(callState.endCall.bind(callState))
|
|
124
|
+
} else {
|
|
125
|
+
callState.endCall(retVal)
|
|
126
|
+
}
|
|
127
|
+
return retVal
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// This is the crux of what we're doing in safe mode. It handles errors
|
|
131
|
+
// that _we_ cause, by logging them, and transparently providing results
|
|
132
|
+
// as if no wrapping was done at all. That means detecting (via callState)
|
|
133
|
+
// whether the function has already run or not, and if it has, returning
|
|
134
|
+
// the result, and otherwise calling the original function unwrapped.
|
|
135
|
+
const handleError = function (args, callState, e) {
|
|
136
|
+
if (callState.completed) {
|
|
137
|
+
// error was thrown after original function returned/resolved, so
|
|
138
|
+
// it was us. log it.
|
|
139
|
+
log.error(e)
|
|
140
|
+
// original ran and returned something. return it.
|
|
141
|
+
return callState.retVal
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!callState.called) {
|
|
145
|
+
// error was thrown before original function was called, so
|
|
146
|
+
// it was us. log it.
|
|
147
|
+
log.error(e)
|
|
148
|
+
// original never ran. call it unwrapped.
|
|
149
|
+
return original.apply(this, args)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// error was thrown during original function execution, so
|
|
153
|
+
// it was them. throw.
|
|
154
|
+
throw e
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// The wrapped function is the one that will be called by the user.
|
|
158
|
+
// It calls our version of the original function, which manages the
|
|
159
|
+
// callState. That way when we use the errorHandler, it can tell where
|
|
160
|
+
// the error originated.
|
|
161
|
+
wrapped = function (...args) {
|
|
162
|
+
currentCallState = new CallState()
|
|
163
|
+
const errorHandler = handleError.bind(this, args, currentCallState)
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const retVal = innerWrapped.apply(this, args)
|
|
167
|
+
return isPromise(retVal) ? retVal.catch(errorHandler) : retVal
|
|
168
|
+
} catch (e) {
|
|
169
|
+
return errorHandler(e)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
// In non-safe mode, we just wrap the original function directly.
|
|
174
|
+
wrapped = wrapper(original)
|
|
175
|
+
}
|
|
44
176
|
const descriptor = Object.getOwnPropertyDescriptor(target, name)
|
|
45
177
|
|
|
46
178
|
const attributes = {
|
|
@@ -48,7 +180,7 @@ function wrapMethod (target, name, wrapper) {
|
|
|
48
180
|
...descriptor
|
|
49
181
|
}
|
|
50
182
|
|
|
51
|
-
copyProperties(original, wrapped)
|
|
183
|
+
if (typeof original === 'function') copyProperties(original, wrapped)
|
|
52
184
|
|
|
53
185
|
if (descriptor) {
|
|
54
186
|
unwrappers.set(wrapped, () => Object.defineProperty(target, name, descriptor))
|
|
@@ -156,7 +288,9 @@ function assertNotClass (target) {
|
|
|
156
288
|
|
|
157
289
|
module.exports = {
|
|
158
290
|
wrap,
|
|
291
|
+
wrapFunction,
|
|
159
292
|
massWrap,
|
|
160
293
|
unwrap,
|
|
161
|
-
massUnwrap
|
|
294
|
+
massUnwrap,
|
|
295
|
+
setSafe
|
|
162
296
|
}
|