dd-trace 5.21.0 → 5.23.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.
Files changed (153) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +20 -8
  3. package/package.json +11 -5
  4. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  5. package/packages/datadog-instrumentations/src/apollo-server.js +1 -1
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +4 -4
  7. package/packages/datadog-instrumentations/src/body-parser.js +4 -4
  8. package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
  9. package/packages/datadog-instrumentations/src/child_process.js +2 -2
  10. package/packages/datadog-instrumentations/src/connect.js +4 -4
  11. package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
  12. package/packages/datadog-instrumentations/src/couchbase.js +12 -12
  13. package/packages/datadog-instrumentations/src/cucumber.js +294 -56
  14. package/packages/datadog-instrumentations/src/dns.js +10 -10
  15. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  16. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +3 -3
  17. package/packages/datadog-instrumentations/src/express.js +4 -4
  18. package/packages/datadog-instrumentations/src/fastify.js +6 -6
  19. package/packages/datadog-instrumentations/src/fetch.js +1 -1
  20. package/packages/datadog-instrumentations/src/find-my-way.js +2 -2
  21. package/packages/datadog-instrumentations/src/fs.js +2 -2
  22. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +2 -2
  23. package/packages/datadog-instrumentations/src/grpc/client.js +4 -6
  24. package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
  25. package/packages/datadog-instrumentations/src/hapi.js +10 -13
  26. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  27. package/packages/datadog-instrumentations/src/http/client.js +3 -3
  28. package/packages/datadog-instrumentations/src/jest.js +8 -5
  29. package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
  30. package/packages/datadog-instrumentations/src/knex.js +2 -2
  31. package/packages/datadog-instrumentations/src/koa.js +5 -5
  32. package/packages/datadog-instrumentations/src/ldapjs.js +1 -1
  33. package/packages/datadog-instrumentations/src/mariadb.js +8 -8
  34. package/packages/datadog-instrumentations/src/memcached.js +2 -2
  35. package/packages/datadog-instrumentations/src/microgateway-core.js +7 -5
  36. package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
  37. package/packages/datadog-instrumentations/src/mocha/main.js +139 -53
  38. package/packages/datadog-instrumentations/src/mocha/utils.js +37 -18
  39. package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
  40. package/packages/datadog-instrumentations/src/mocha.js +4 -0
  41. package/packages/datadog-instrumentations/src/moleculer/server.js +2 -2
  42. package/packages/datadog-instrumentations/src/mongodb-core.js +7 -7
  43. package/packages/datadog-instrumentations/src/mongoose.js +5 -6
  44. package/packages/datadog-instrumentations/src/mysql.js +3 -3
  45. package/packages/datadog-instrumentations/src/mysql2.js +6 -6
  46. package/packages/datadog-instrumentations/src/net.js +2 -2
  47. package/packages/datadog-instrumentations/src/next.js +5 -5
  48. package/packages/datadog-instrumentations/src/openai.js +62 -71
  49. package/packages/datadog-instrumentations/src/oracledb.js +8 -8
  50. package/packages/datadog-instrumentations/src/passport-http.js +1 -1
  51. package/packages/datadog-instrumentations/src/passport-local.js +1 -1
  52. package/packages/datadog-instrumentations/src/passport-utils.js +1 -1
  53. package/packages/datadog-instrumentations/src/pg.js +60 -5
  54. package/packages/datadog-instrumentations/src/pino.js +4 -4
  55. package/packages/datadog-instrumentations/src/playwright.js +6 -4
  56. package/packages/datadog-instrumentations/src/redis.js +2 -2
  57. package/packages/datadog-instrumentations/src/restify.js +4 -4
  58. package/packages/datadog-instrumentations/src/rhea.js +4 -4
  59. package/packages/datadog-instrumentations/src/router.js +5 -5
  60. package/packages/datadog-instrumentations/src/sharedb.js +2 -2
  61. package/packages/datadog-instrumentations/src/vitest.js +188 -12
  62. package/packages/datadog-instrumentations/src/winston.js +2 -3
  63. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  64. package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
  65. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  66. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
  67. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  68. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  69. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +39 -10
  70. package/packages/datadog-plugin-cypress/src/support.js +4 -1
  71. package/packages/datadog-plugin-hapi/src/index.js +2 -2
  72. package/packages/datadog-plugin-http/src/client.js +1 -42
  73. package/packages/datadog-plugin-http2/src/client.js +1 -26
  74. package/packages/datadog-plugin-jest/src/index.js +18 -1
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
  76. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
  77. package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +18 -0
  79. package/packages/datadog-plugin-openai/src/index.js +85 -65
  80. package/packages/datadog-plugin-playwright/src/index.js +9 -0
  81. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  82. package/packages/datadog-plugin-vitest/src/index.js +68 -3
  83. package/packages/datadog-shimmer/src/shimmer.js +144 -10
  84. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  85. package/packages/dd-trace/src/appsec/blocking.js +23 -17
  86. package/packages/dd-trace/src/appsec/channels.js +4 -2
  87. package/packages/dd-trace/src/appsec/graphql.js +3 -1
  88. package/packages/dd-trace/src/appsec/iast/iast-log.js +2 -1
  89. package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
  90. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
  91. package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
  92. package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
  93. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
  94. package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
  95. package/packages/dd-trace/src/appsec/remote_config/manager.js +93 -52
  96. package/packages/dd-trace/src/appsec/rule_manager.js +8 -0
  97. package/packages/dd-trace/src/appsec/telemetry.js +3 -3
  98. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
  99. package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
  100. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
  101. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +15 -1
  102. package/packages/dd-trace/src/config.js +100 -40
  103. package/packages/dd-trace/src/constants.js +11 -1
  104. package/packages/dd-trace/src/data_streams_context.js +3 -0
  105. package/packages/dd-trace/src/datastreams/fnv.js +23 -0
  106. package/packages/dd-trace/src/datastreams/pathway.js +12 -5
  107. package/packages/dd-trace/src/datastreams/processor.js +35 -0
  108. package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
  109. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
  110. package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
  111. package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
  112. package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
  113. package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
  114. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
  115. package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
  116. package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
  117. package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
  118. package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
  119. package/packages/dd-trace/src/debugger/index.js +92 -0
  120. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
  121. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  122. package/packages/dd-trace/src/lambda/handler.js +1 -0
  123. package/packages/dd-trace/src/lambda/index.js +12 -1
  124. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -6
  125. package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
  126. package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
  127. package/packages/dd-trace/src/payload-tagging/index.js +93 -0
  128. package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
  129. package/packages/dd-trace/src/plugin_manager.js +11 -10
  130. package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
  131. package/packages/dd-trace/src/plugins/util/env.js +5 -2
  132. package/packages/dd-trace/src/plugins/util/test.js +24 -4
  133. package/packages/dd-trace/src/profiler.js +15 -5
  134. package/packages/dd-trace/src/profiling/config.js +7 -4
  135. package/packages/dd-trace/src/profiling/exporter_cli.js +13 -1
  136. package/packages/dd-trace/src/profiling/exporters/agent.js +8 -2
  137. package/packages/dd-trace/src/profiling/profiler.js +0 -9
  138. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
  139. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
  140. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
  141. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
  142. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
  143. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
  144. package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
  145. package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
  146. package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
  148. package/packages/dd-trace/src/profiling/ssi-heuristics.js +59 -60
  149. package/packages/dd-trace/src/proxy.js +31 -24
  150. package/packages/dd-trace/src/span_stats.js +4 -2
  151. package/packages/dd-trace/src/telemetry/index.js +23 -6
  152. package/packages/dd-trace/src/telemetry/logs/index.js +20 -0
  153. package/packages/dd-trace/src/appsec/rasp.js +0 -176
@@ -58,7 +58,7 @@ class HttpClientPlugin extends ClientPlugin {
58
58
  span._spanContext._trace.record = false
59
59
  }
60
60
 
61
- if (this.shouldInjectTraceHeaders(options, uri)) {
61
+ if (this.config.propagationFilter(uri)) {
62
62
  this.tracer.inject(span, HTTP_HEADERS, options.headers)
63
63
  }
64
64
 
@@ -71,18 +71,6 @@ class HttpClientPlugin extends ClientPlugin {
71
71
  return message.currentStore
72
72
  }
73
73
 
74
- shouldInjectTraceHeaders (options, uri) {
75
- if (hasAmazonSignature(options) && !this.config.enablePropagationWithAmazonHeaders) {
76
- return false
77
- }
78
-
79
- if (!this.config.propagationFilter(uri)) {
80
- return false
81
- }
82
-
83
- return true
84
- }
85
-
86
74
  bindAsyncStart ({ parentStore }) {
87
75
  return parentStore
88
76
  }
@@ -212,31 +200,6 @@ function getHooks (config) {
212
200
  return { request }
213
201
  }
214
202
 
215
- function hasAmazonSignature (options) {
216
- if (!options) {
217
- return false
218
- }
219
-
220
- if (options.headers) {
221
- const headers = Object.keys(options.headers)
222
- .reduce((prev, next) => Object.assign(prev, {
223
- [next.toLowerCase()]: options.headers[next]
224
- }), {})
225
-
226
- if (headers['x-amz-signature']) {
227
- return true
228
- }
229
-
230
- if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) {
231
- return true
232
- }
233
- }
234
-
235
- const search = options.search || options.path
236
-
237
- return search && search.toLowerCase().indexOf('x-amz-signature=') !== -1
238
- }
239
-
240
203
  function extractSessionDetails (options) {
241
204
  if (typeof options === 'string') {
242
205
  return new URL(options).host
@@ -248,8 +211,4 @@ function extractSessionDetails (options) {
248
211
  return { host, port }
249
212
  }
250
213
 
251
- function startsWith (searchString) {
252
- return value => String(value).startsWith(searchString)
253
- }
254
-
255
214
  module.exports = HttpClientPlugin
@@ -62,9 +62,7 @@ class Http2ClientPlugin extends ClientPlugin {
62
62
 
63
63
  addHeaderTags(span, headers, HTTP_REQUEST_HEADERS, this.config)
64
64
 
65
- if (!hasAmazonSignature(headers, path)) {
66
- this.tracer.inject(span, HTTP_HEADERS, headers)
67
- }
65
+ this.tracer.inject(span, HTTP_HEADERS, headers)
68
66
 
69
67
  message.parentStore = store
70
68
  message.currentStore = { ...store, span }
@@ -134,29 +132,6 @@ function extractSessionDetails (authority, options) {
134
132
  return { protocol, port, host }
135
133
  }
136
134
 
137
- function hasAmazonSignature (headers, path) {
138
- if (headers) {
139
- headers = Object.keys(headers)
140
- .reduce((prev, next) => Object.assign(prev, {
141
- [next.toLowerCase()]: headers[next]
142
- }), {})
143
-
144
- if (headers['x-amz-signature']) {
145
- return true
146
- }
147
-
148
- if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) {
149
- return true
150
- }
151
- }
152
-
153
- return path && path.toLowerCase().indexOf('x-amz-signature=') !== -1
154
- }
155
-
156
- function startsWith (searchString) {
157
- return value => String(value).startsWith(searchString)
158
- }
159
-
160
135
  function getStatusValidator (config) {
161
136
  if (typeof config.validateStatus === 'function') {
162
137
  return config.validateStatus
@@ -154,10 +154,17 @@ class JestPlugin extends CiPlugin {
154
154
  config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
155
155
  config._ddRepositoryRoot = this.repositoryRoot
156
156
  config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
157
+ config._ddFlakyTestRetriesCount = this.libraryConfig?.flakyTestRetriesCount
157
158
  })
158
159
  })
159
160
 
160
- this.addSub('ci:jest:test-suite:start', ({ testSuite, testEnvironmentOptions, frameworkVersion, displayName }) => {
161
+ this.addSub('ci:jest:test-suite:start', ({
162
+ testSuite,
163
+ testSourceFile,
164
+ testEnvironmentOptions,
165
+ frameworkVersion,
166
+ displayName
167
+ }) => {
161
168
  const {
162
169
  _ddTestSessionId: testSessionId,
163
170
  _ddTestCommand: testCommand,
@@ -195,6 +202,16 @@ class JestPlugin extends CiPlugin {
195
202
  if (displayName) {
196
203
  testSuiteMetadata[JEST_DISPLAY_NAME] = displayName
197
204
  }
205
+ if (testSourceFile) {
206
+ testSuiteMetadata[TEST_SOURCE_FILE] = testSourceFile
207
+ // Test suite is the whole test file, so we can use the first line as the start
208
+ testSuiteMetadata[TEST_SOURCE_START] = 1
209
+ }
210
+
211
+ const codeOwners = this.getCodeOwners(testSuiteMetadata)
212
+ if (codeOwners) {
213
+ testSuiteMetadata[TEST_CODE_OWNERS] = codeOwners
214
+ }
198
215
 
199
216
  this.testSuiteSpan = this.tracer.startSpan('jest.test_suite', {
200
217
  childOf: testSessionSpanContext,
@@ -0,0 +1,20 @@
1
+ const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
2
+ const { getMessageSize } = require('../../dd-trace/src/datastreams/processor')
3
+
4
+ class KafkajsBatchConsumerPlugin extends ConsumerPlugin {
5
+ static get id () { return 'kafkajs' }
6
+ static get operation () { return 'consume-batch' }
7
+
8
+ start ({ topic, partition, messages, groupId }) {
9
+ if (!this.config.dsmEnabled) return
10
+ for (const message of messages) {
11
+ if (!message || !message.headers) continue
12
+ const payloadSize = getMessageSize(message)
13
+ this.tracer.decodeDataStreamsContext(message.headers)
14
+ this.tracer
15
+ .setCheckpoint(['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka'], null, payloadSize)
16
+ }
17
+ }
18
+ }
19
+
20
+ module.exports = KafkajsBatchConsumerPlugin
@@ -2,7 +2,6 @@
2
2
 
3
3
  const dc = require('dc-polyfill')
4
4
  const { getMessageSize } = require('../../dd-trace/src/datastreams/processor')
5
- const { DsmPathwayCodec } = require('../../dd-trace/src/datastreams/pathway')
6
5
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
7
6
 
8
7
  const afterStartCh = dc.channel('dd-trace:kafkajs:consumer:afterStart')
@@ -78,7 +77,7 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
78
77
  'kafka.partition': partition
79
78
  }
80
79
  })
81
- if (this.config.dsmEnabled && message?.headers && DsmPathwayCodec.contextExists(message.headers)) {
80
+ if (this.config.dsmEnabled && message?.headers) {
82
81
  const payloadSize = getMessageSize(message)
83
82
  this.tracer.decodeDataStreamsContext(message.headers)
84
83
  this.tracer
@@ -2,6 +2,7 @@
2
2
 
3
3
  const ProducerPlugin = require('./producer')
4
4
  const ConsumerPlugin = require('./consumer')
5
+ const BatchConsumerPlugin = require('./batch-consumer')
5
6
  const CompositePlugin = require('../../dd-trace/src/plugins/composite')
6
7
 
7
8
  class KafkajsPlugin extends CompositePlugin {
@@ -9,7 +10,8 @@ class KafkajsPlugin extends CompositePlugin {
9
10
  static get plugins () {
10
11
  return {
11
12
  producer: ProducerPlugin,
12
- consumer: ConsumerPlugin
13
+ consumer: ConsumerPlugin,
14
+ batchConsumer: BatchConsumerPlugin
13
15
  }
14
16
  }
15
17
  }
@@ -21,6 +21,7 @@ const {
21
21
  TEST_IS_NEW,
22
22
  TEST_IS_RETRY,
23
23
  TEST_EARLY_FLAKE_ENABLED,
24
+ TEST_EARLY_FLAKE_ABORT_REASON,
24
25
  TEST_SESSION_ID,
25
26
  TEST_MODULE_ID,
26
27
  TEST_MODULE,
@@ -124,6 +125,19 @@ class MochaPlugin extends CiPlugin {
124
125
  testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
125
126
  this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
126
127
  }
128
+ if (this.repositoryRoot !== this.sourceRoot && !!this.repositoryRoot) {
129
+ testSuiteMetadata[TEST_SOURCE_FILE] = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
130
+ } else {
131
+ testSuiteMetadata[TEST_SOURCE_FILE] = testSuite
132
+ }
133
+ if (testSuiteMetadata[TEST_SOURCE_FILE]) {
134
+ testSuiteMetadata[TEST_SOURCE_START] = 1
135
+ }
136
+
137
+ const codeOwners = this.getCodeOwners(testSuiteMetadata)
138
+ if (codeOwners) {
139
+ testSuiteMetadata[TEST_CODE_OWNERS] = codeOwners
140
+ }
127
141
 
128
142
  const testSuiteSpan = this.tracer.startSpan('mocha.test_suite', {
129
143
  childOf: this.testModuleSpan,
@@ -267,6 +281,7 @@ class MochaPlugin extends CiPlugin {
267
281
  hasUnskippableSuites,
268
282
  error,
269
283
  isEarlyFlakeDetectionEnabled,
284
+ isEarlyFlakeDetectionFaulty,
270
285
  isParallel
271
286
  }) => {
272
287
  if (this.testSessionSpan) {
@@ -301,6 +316,9 @@ class MochaPlugin extends CiPlugin {
301
316
  if (isEarlyFlakeDetectionEnabled) {
302
317
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
303
318
  }
319
+ if (isEarlyFlakeDetectionFaulty) {
320
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
321
+ }
304
322
 
305
323
  this.testModuleSpan.finish()
306
324
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
@@ -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
- start ({ methodName, args, basePath, apiKey }) {
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 fullStore = storage.getStore() || {} // certain request body fields are later used for logs
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
- store.prompt = prompt
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
- store.input = normalized
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, store)
147
+ commonCreateImageRequestExtraction(tags, payload, openaiStore)
145
148
  break
146
149
 
147
150
  case 'createChatCompletion':
148
151
  case 'chat.completions.create':
149
- createChatCompletionRequestExtraction(tags, payload, store)
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, store)
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, store)
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
- finish (response) {
195
- const span = this.activeSpan
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 = response.headers
201
- body = response.body
202
- method = response.method
203
- path = response.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 fullStore = storage.getStore()
214
- const store = fullStore.openai
225
+ const openaiStore = store.openai
215
226
 
216
- if (!error && (path.startsWith('https://') || path.startsWith('http://'))) {
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, store)
253
+ responseDataExtractionByMethod(methodName, tags, body, openaiStore)
243
254
  span.addTags(tags)
244
255
 
245
- super.finish()
246
- this.sendLog(methodName, span, tags, store, error)
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
 
@@ -265,25 +276,34 @@ class OpenApiPlugin extends TracingPlugin {
265
276
  const completionTokens = spanTags['openai.response.usage.completion_tokens']
266
277
  const completionTokensEstimated = spanTags['openai.response.usage.completion_tokens_estimated']
267
278
 
279
+ const totalTokens = spanTags['openai.response.usage.total_tokens']
280
+
268
281
  if (!error) {
269
- if (promptTokensEstimated) {
270
- this.metrics.distribution(
271
- 'openai.tokens.prompt', promptTokens, [...tags, 'openai.estimated:true'])
272
- } else {
273
- this.metrics.distribution('openai.tokens.prompt', promptTokens, tags)
282
+ if (promptTokens != null) {
283
+ if (promptTokensEstimated) {
284
+ this.metrics.distribution(
285
+ 'openai.tokens.prompt', promptTokens, [...tags, 'openai.estimated:true'])
286
+ } else {
287
+ this.metrics.distribution('openai.tokens.prompt', promptTokens, tags)
288
+ }
274
289
  }
275
- if (completionTokensEstimated) {
276
- this.metrics.distribution(
277
- 'openai.tokens.completion', completionTokens, [...tags, 'openai.estimated:true'])
278
- } else {
279
- this.metrics.distribution('openai.tokens.completion', completionTokens, tags)
290
+
291
+ if (completionTokens != null) {
292
+ if (completionTokensEstimated) {
293
+ this.metrics.distribution(
294
+ 'openai.tokens.completion', completionTokens, [...tags, 'openai.estimated:true'])
295
+ } else {
296
+ this.metrics.distribution('openai.tokens.completion', completionTokens, tags)
297
+ }
280
298
  }
281
299
 
282
- if (promptTokensEstimated || completionTokensEstimated) {
283
- this.metrics.distribution(
284
- 'openai.tokens.total', promptTokens + completionTokens, [...tags, 'openai.estimated:true'])
285
- } else {
286
- this.metrics.distribution('openai.tokens.total', promptTokens + completionTokens, tags)
300
+ if (totalTokens != null) {
301
+ if (promptTokensEstimated || completionTokensEstimated) {
302
+ this.metrics.distribution(
303
+ 'openai.tokens.total', totalTokens, [...tags, 'openai.estimated:true'])
304
+ } else {
305
+ this.metrics.distribution('openai.tokens.total', totalTokens, tags)
306
+ }
287
307
  }
288
308
  }
289
309
 
@@ -308,15 +328,15 @@ class OpenApiPlugin extends TracingPlugin {
308
328
  }
309
329
  }
310
330
 
311
- sendLog (methodName, span, tags, store, error) {
312
- if (!store) return
313
- if (!Object.keys(store).length) return
331
+ sendLog (methodName, span, tags, openaiStore, error) {
332
+ if (!openaiStore) return
333
+ if (!Object.keys(openaiStore).length) return
314
334
  if (!this.sampler.isSampled()) return
315
335
 
316
336
  const log = {
317
337
  status: error ? 'error' : 'info',
318
338
  message: `sampled ${methodName}`,
319
- ...store
339
+ ...openaiStore
320
340
  }
321
341
 
322
342
  this.logger.log(log, span, tags)
@@ -400,21 +420,21 @@ function countTokens (content, model) {
400
420
  }
401
421
  }
402
422
 
403
- function createEditRequestExtraction (tags, payload, store) {
423
+ function createEditRequestExtraction (tags, payload, openaiStore) {
404
424
  const instruction = payload.instruction
405
425
  tags['openai.request.instruction'] = instruction
406
- store.instruction = instruction
426
+ openaiStore.instruction = instruction
407
427
  }
408
428
 
409
429
  function retrieveModelRequestExtraction (tags, payload) {
410
430
  tags['openai.request.id'] = payload.id
411
431
  }
412
432
 
413
- function createChatCompletionRequestExtraction (tags, payload, store) {
433
+ function createChatCompletionRequestExtraction (tags, payload, openaiStore) {
414
434
  const messages = payload.messages
415
435
  if (!defensiveArrayLength(messages)) return
416
436
 
417
- store.messages = payload.messages
437
+ openaiStore.messages = payload.messages
418
438
  for (let i = 0; i < payload.messages.length; i++) {
419
439
  const message = payload.messages[i]
420
440
  tagChatCompletionRequestContent(message.content, i, tags)
@@ -424,20 +444,20 @@ function createChatCompletionRequestExtraction (tags, payload, store) {
424
444
  }
425
445
  }
426
446
 
427
- function commonCreateImageRequestExtraction (tags, payload, store) {
447
+ function commonCreateImageRequestExtraction (tags, payload, openaiStore) {
428
448
  // createImageEdit, createImageVariation
429
449
  const img = payload.file || payload.image
430
450
  if (img !== null && typeof img === 'object' && img.path) {
431
451
  const file = path.basename(img.path)
432
452
  tags['openai.request.image'] = file
433
- store.file = file
453
+ openaiStore.file = file
434
454
  }
435
455
 
436
456
  // createImageEdit
437
457
  if (payload.mask !== null && typeof payload.mask === 'object' && payload.mask.path) {
438
458
  const mask = path.basename(payload.mask.path)
439
459
  tags['openai.request.mask'] = mask
440
- store.mask = mask
460
+ openaiStore.mask = mask
441
461
  }
442
462
 
443
463
  tags['openai.request.size'] = payload.size
@@ -445,7 +465,7 @@ function commonCreateImageRequestExtraction (tags, payload, store) {
445
465
  tags['openai.request.language'] = payload.language
446
466
  }
447
467
 
448
- function responseDataExtractionByMethod (methodName, tags, body, store) {
468
+ function responseDataExtractionByMethod (methodName, tags, body, openaiStore) {
449
469
  switch (methodName) {
450
470
  case 'createModeration':
451
471
  case 'moderations.create':
@@ -458,7 +478,7 @@ function responseDataExtractionByMethod (methodName, tags, body, store) {
458
478
  case 'chat.completions.create':
459
479
  case 'createEdit':
460
480
  case 'edits.create':
461
- commonCreateResponseExtraction(tags, body, store, methodName)
481
+ commonCreateResponseExtraction(tags, body, openaiStore, methodName)
462
482
  break
463
483
 
464
484
  case 'listFiles':
@@ -474,7 +494,7 @@ function responseDataExtractionByMethod (methodName, tags, body, store) {
474
494
 
475
495
  case 'createEmbedding':
476
496
  case 'embeddings.create':
477
- createEmbeddingResponseExtraction(tags, body)
497
+ createEmbeddingResponseExtraction(tags, body, openaiStore)
478
498
  break
479
499
 
480
500
  case 'createFile':
@@ -629,14 +649,14 @@ function deleteFileResponseExtraction (tags, body) {
629
649
  tags['openai.response.id'] = body.id
630
650
  }
631
651
 
632
- function commonCreateAudioRequestExtraction (tags, body, store) {
652
+ function commonCreateAudioRequestExtraction (tags, body, openaiStore) {
633
653
  tags['openai.request.response_format'] = body.response_format
634
654
  tags['openai.request.language'] = body.language
635
655
 
636
656
  if (body.file !== null && typeof body.file === 'object' && body.file.path) {
637
657
  const filename = path.basename(body.file.path)
638
658
  tags['openai.request.filename'] = filename
639
- store.file = filename
659
+ openaiStore.file = filename
640
660
  }
641
661
  }
642
662
 
@@ -659,8 +679,8 @@ function createRetrieveFileResponseExtraction (tags, body) {
659
679
  tags['openai.response.status_details'] = body.status_details
660
680
  }
661
681
 
662
- function createEmbeddingResponseExtraction (tags, body) {
663
- usageExtraction(tags, body)
682
+ function createEmbeddingResponseExtraction (tags, body, openaiStore) {
683
+ usageExtraction(tags, body, openaiStore)
664
684
 
665
685
  if (!body.data) return
666
686
 
@@ -694,14 +714,14 @@ function createModerationResponseExtraction (tags, body) {
694
714
  }
695
715
 
696
716
  // createCompletion, createChatCompletion, createEdit
697
- function commonCreateResponseExtraction (tags, body, store, methodName) {
698
- usageExtraction(tags, body, methodName)
717
+ function commonCreateResponseExtraction (tags, body, openaiStore, methodName) {
718
+ usageExtraction(tags, body, methodName, openaiStore)
699
719
 
700
720
  if (!body.choices) return
701
721
 
702
722
  tags['openai.response.choices_count'] = body.choices.length
703
723
 
704
- store.choices = body.choices
724
+ openaiStore.choices = body.choices
705
725
 
706
726
  for (let choiceIdx = 0; choiceIdx < body.choices.length; choiceIdx++) {
707
727
  const choice = body.choices[choiceIdx]
@@ -735,7 +755,7 @@ function commonCreateResponseExtraction (tags, body, store, methodName) {
735
755
  }
736
756
 
737
757
  // createCompletion, createChatCompletion, createEdit, createEmbedding
738
- function usageExtraction (tags, body, methodName) {
758
+ function usageExtraction (tags, body, methodName, openaiStore) {
739
759
  let promptTokens = 0
740
760
  let completionTokens = 0
741
761
  let totalTokens = 0
@@ -743,14 +763,14 @@ function usageExtraction (tags, body, methodName) {
743
763
  promptTokens = body.usage.prompt_tokens
744
764
  completionTokens = body.usage.completion_tokens
745
765
  totalTokens = body.usage.total_tokens
746
- } else if (['chat.completions.create', 'completions.create'].includes(methodName)) {
766
+ } else if (body.model && ['chat.completions.create', 'completions.create'].includes(methodName)) {
747
767
  // estimate tokens based on method name for completions and chat completions
748
768
  const { model } = body
749
769
  let promptEstimated = false
750
770
  let completionEstimated = false
751
771
 
752
772
  // prompt tokens
753
- const payload = storage.getStore().openai
773
+ const payload = openaiStore
754
774
  const promptTokensCount = countPromptTokens(methodName, payload, model)
755
775
  promptTokens = promptTokensCount.promptTokens
756
776
  promptEstimated = promptTokensCount.promptEstimated
@@ -766,9 +786,9 @@ function usageExtraction (tags, body, methodName) {
766
786
  if (completionEstimated) tags['openai.response.usage.completion_tokens_estimated'] = true
767
787
  }
768
788
 
769
- if (promptTokens) tags['openai.response.usage.prompt_tokens'] = promptTokens
770
- if (completionTokens) tags['openai.response.usage.completion_tokens'] = completionTokens
771
- if (totalTokens) tags['openai.response.usage.total_tokens'] = totalTokens
789
+ if (promptTokens != null) tags['openai.response.usage.prompt_tokens'] = promptTokens
790
+ if (completionTokens != null) tags['openai.response.usage.completion_tokens'] = completionTokens
791
+ if (totalTokens != null) tags['openai.response.usage.total_tokens'] = totalTokens
772
792
  }
773
793
 
774
794
  function truncateApiKey (apiKey) {
@@ -69,6 +69,7 @@ class PlaywrightPlugin extends CiPlugin {
69
69
  this.addSub('ci:playwright:test-suite:start', (testSuiteAbsolutePath) => {
70
70
  const store = storage.getStore()
71
71
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
72
+ const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
72
73
 
73
74
  const testSuiteMetadata = getTestSuiteCommonTags(
74
75
  this.command,
@@ -76,6 +77,14 @@ class PlaywrightPlugin extends CiPlugin {
76
77
  testSuite,
77
78
  'playwright'
78
79
  )
80
+ if (testSourceFile) {
81
+ testSuiteMetadata[TEST_SOURCE_FILE] = testSourceFile
82
+ testSuiteMetadata[TEST_SOURCE_START] = 1
83
+ }
84
+ const codeOwners = this.getCodeOwners(testSuiteMetadata)
85
+ if (codeOwners) {
86
+ testSuiteMetadata[TEST_CODE_OWNERS] = codeOwners
87
+ }
79
88
 
80
89
  const testSuiteSpan = this.tracer.startSpan('playwright.test_suite', {
81
90
  childOf: this.testModuleSpan,
@@ -3,7 +3,6 @@
3
3
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const { getAmqpMessageSize } = require('../../dd-trace/src/datastreams/processor')
6
- const { DsmPathwayCodec } = require('../../dd-trace/src/datastreams/pathway')
7
6
 
8
7
  class RheaConsumerPlugin extends ConsumerPlugin {
9
8
  static get id () { return 'rhea' }
@@ -34,8 +33,7 @@ class RheaConsumerPlugin extends ConsumerPlugin {
34
33
 
35
34
  if (
36
35
  this.config.dsmEnabled &&
37
- msgObj?.message?.delivery_annotations &&
38
- DsmPathwayCodec.contextExists(msgObj.message.delivery_annotations)
36
+ msgObj?.message?.delivery_annotations
39
37
  ) {
40
38
  const payloadSize = getAmqpMessageSize(
41
39
  { headers: msgObj.message.delivery_annotations, content: msgObj.message.body }