dd-trace 5.103.0 → 5.105.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 (213) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +107 -6
  3. package/package.json +18 -17
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +15 -2
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
  11. package/packages/datadog-instrumentations/src/cucumber.js +181 -35
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  14. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  15. package/packages/datadog-instrumentations/src/graphql.js +188 -67
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  18. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  22. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  23. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  24. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
  32. package/packages/datadog-instrumentations/src/hono.js +54 -3
  33. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  34. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  35. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  36. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  37. package/packages/datadog-instrumentations/src/jest.js +390 -183
  38. package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
  39. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  40. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  41. package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
  42. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  43. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  44. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  45. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  46. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  47. package/packages/datadog-instrumentations/src/nats.js +182 -0
  48. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  49. package/packages/datadog-instrumentations/src/openai.js +33 -18
  50. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  51. package/packages/datadog-instrumentations/src/pg.js +1 -1
  52. package/packages/datadog-instrumentations/src/pino.js +17 -5
  53. package/packages/datadog-instrumentations/src/playwright.js +537 -297
  54. package/packages/datadog-instrumentations/src/router.js +80 -34
  55. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  56. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  58. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  59. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  60. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  61. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  62. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  64. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
  65. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  66. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  67. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  68. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  69. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  71. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  72. package/packages/datadog-plugin-http/src/server.js +40 -15
  73. package/packages/datadog-plugin-jest/src/index.js +11 -3
  74. package/packages/datadog-plugin-jest/src/util.js +15 -8
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  76. package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
  77. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  79. package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
  80. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  81. package/packages/datadog-plugin-nats/src/index.js +20 -0
  82. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  83. package/packages/datadog-plugin-nats/src/util.js +33 -0
  84. package/packages/datadog-plugin-next/src/index.js +5 -3
  85. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  86. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  87. package/packages/datadog-plugin-pino/src/index.js +42 -0
  88. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  89. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  90. package/packages/datadog-plugin-redis/src/index.js +37 -2
  91. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  92. package/packages/datadog-plugin-router/src/index.js +33 -44
  93. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  94. package/packages/datadog-plugin-undici/src/index.js +19 -0
  95. package/packages/datadog-plugin-vitest/src/index.js +24 -20
  96. package/packages/datadog-plugin-winston/src/index.js +30 -0
  97. package/packages/datadog-shimmer/src/shimmer.js +49 -21
  98. package/packages/dd-trace/src/aiguard/index.js +1 -1
  99. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  100. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  101. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  102. package/packages/dd-trace/src/appsec/index.js +11 -4
  103. package/packages/dd-trace/src/appsec/reporter.js +24 -11
  104. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  105. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  106. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  107. package/packages/dd-trace/src/baggage.js +7 -1
  108. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  109. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  110. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  111. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  112. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
  114. package/packages/dd-trace/src/config/supported-configurations.json +36 -8
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  116. package/packages/dd-trace/src/datastreams/context.js +4 -2
  117. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  118. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  119. package/packages/dd-trace/src/encode/0.4.js +124 -108
  120. package/packages/dd-trace/src/encode/0.5.js +114 -26
  121. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
  122. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  123. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  124. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  125. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  126. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  127. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler.js +2 -4
  130. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  131. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  134. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  135. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  136. package/packages/dd-trace/src/llmobs/sdk.js +10 -16
  137. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  138. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  139. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  140. package/packages/dd-trace/src/llmobs/util.js +66 -3
  141. package/packages/dd-trace/src/log/index.js +1 -1
  142. package/packages/dd-trace/src/log/writer.js +3 -1
  143. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  144. package/packages/dd-trace/src/msgpack/index.js +96 -2
  145. package/packages/dd-trace/src/noop/span.js +3 -1
  146. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  147. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  148. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  149. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  151. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  153. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  154. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  155. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  156. package/packages/dd-trace/src/opentracing/span.js +59 -19
  157. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  158. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  159. package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
  160. package/packages/dd-trace/src/plugins/database.js +7 -6
  161. package/packages/dd-trace/src/plugins/index.js +4 -0
  162. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  163. package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
  164. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  165. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  166. package/packages/dd-trace/src/plugins/tracing.js +48 -8
  167. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  168. package/packages/dd-trace/src/plugins/util/test.js +318 -13
  169. package/packages/dd-trace/src/plugins/util/web.js +89 -64
  170. package/packages/dd-trace/src/priority_sampler.js +2 -2
  171. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  172. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  173. package/packages/dd-trace/src/sampling_rule.js +7 -7
  174. package/packages/dd-trace/src/scope.js +7 -5
  175. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  176. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  177. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  178. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  179. package/packages/dd-trace/src/span_format.js +190 -58
  180. package/packages/dd-trace/src/spanleak.js +1 -1
  181. package/packages/dd-trace/src/standalone/index.js +3 -3
  182. package/packages/dd-trace/src/tagger.js +0 -2
  183. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  184. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  185. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  186. package/vendor/dist/protobufjs/index.js +1 -1
  187. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  188. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  189. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
  190. package/vendor/dist/opentracing/LICENSE +0 -201
  191. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  192. package/vendor/dist/opentracing/constants.d.ts +0 -61
  193. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  194. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  195. package/vendor/dist/opentracing/functions.d.ts +0 -20
  196. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  197. package/vendor/dist/opentracing/index.d.ts +0 -12
  198. package/vendor/dist/opentracing/index.js +0 -1
  199. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  200. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  201. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  202. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  203. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  204. package/vendor/dist/opentracing/noop.d.ts +0 -8
  205. package/vendor/dist/opentracing/reference.d.ts +0 -33
  206. package/vendor/dist/opentracing/span.d.ts +0 -147
  207. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  208. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  209. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  210. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  211. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  212. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  213. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -24,19 +24,30 @@ class OracledbPlugin extends DatabasePlugin {
24
24
  dbInstance ??= dbInfo.dbInstance
25
25
  }
26
26
 
27
- this.startSpan(this.operationName(), {
27
+ // oracledb >= 6.4 accepts `execute({ statement, values })` (sql-template-tag form)
28
+ // in addition to a plain SQL string. Extract the SQL text either way so we can tag
29
+ // the resource and inject DBM into the statement, then re-wrap if needed to keep
30
+ // the caller's binds.
31
+ const sql = query?.statement ?? query
32
+
33
+ const span = this.startSpan(this.operationName(), {
28
34
  service,
29
- resource: query,
35
+ resource: sql,
30
36
  type: 'sql',
31
37
  kind: 'client',
32
38
  meta: {
33
39
  'db.user': this.config.user,
34
40
  'db.instance': dbInstance,
41
+ 'db.name': dbInstance,
35
42
  'db.hostname': hostname,
43
+ 'out.host': hostname,
36
44
  [CLIENT_PORT_KEY]: port,
37
45
  },
38
46
  }, ctx)
39
47
 
48
+ const injected = this.injectDbmQuery(span, sql, service.name)
49
+ ctx.injected = query?.statement ? { ...query, statement: injected } : injected
50
+
40
51
  return ctx.currentStore
41
52
  }
42
53
  }
@@ -1,9 +1,51 @@
1
1
  'use strict'
2
2
 
3
+ const { buildLogHolder, messageProxy } = require('../../dd-trace/src/plugins/log_injection')
3
4
  const LogPlugin = require('../../dd-trace/src/plugins/log_plugin')
4
5
 
5
6
  class PinoPlugin extends LogPlugin {
6
7
  static id = 'pino'
8
+
9
+ constructor (...args) {
10
+ super(...args)
11
+ this.addSub('apm:pino:log:json', (payload) => this.handleJsonLine(payload))
12
+ this.addSub('apm:pino:log', (arg) => this.handlePrettyMessage(arg))
13
+ }
14
+
15
+ /**
16
+ * Splice `,"dd":<json>` into the JSON line pino has already produced.
17
+ * The caller-owned message object is never observed -- user Proxies and
18
+ * custom serialisers see nothing because there is no mutation to see.
19
+ *
20
+ * @param {{ line: string }} payload
21
+ */
22
+ handleJsonLine (payload) {
23
+ const logHolder = buildLogHolder(this.tracer)
24
+ if (!logHolder) return
25
+
26
+ const line = payload.line
27
+ const lastClose = line.lastIndexOf('}')
28
+ if (lastClose < 1) return
29
+
30
+ const ddJson = JSON.stringify(logHolder.dd)
31
+ const sep = line.charCodeAt(lastClose - 1) === 0x7B ? '' : ','
32
+ payload.line = line.slice(0, lastClose) + sep + '"dd":' + ddJson + line.slice(lastClose)
33
+ }
34
+
35
+ /**
36
+ * `pino-pretty` (bundled with pino 5/7, separate package on >=8) reads
37
+ * the original message object rather than the JSON line, so the splice
38
+ * above is invisible to it. Wrap the message in a Proxy that exposes a
39
+ * virtual `dd` field for the prettifier to pick up.
40
+ *
41
+ * @param {{ message: object }} arg
42
+ */
43
+ handlePrettyMessage (arg) {
44
+ const logHolder = buildLogHolder(this.tracer)
45
+ if (!logHolder) return
46
+
47
+ arg.message = messageProxy(arg.message, logHolder)
48
+ }
7
49
  }
8
50
 
9
51
  module.exports = PinoPlugin
@@ -12,9 +12,9 @@ const {
12
12
  TEST_BROWSER_NAME,
13
13
  TEST_BROWSER_VERSION,
14
14
  TEST_CODE_OWNERS,
15
- TEST_COMMAND,
16
15
  TEST_EARLY_FLAKE_ABORT_REASON,
17
16
  TEST_EARLY_FLAKE_ENABLED,
17
+ TEST_FRAMEWORK_VERSION,
18
18
  TEST_HAS_FAILED_ALL_RETRIES,
19
19
  TEST_IS_MODIFIED,
20
20
  TEST_IS_NEW,
@@ -234,7 +234,7 @@ class PlaywrightPlugin extends CiPlugin {
234
234
  formattedSpan.meta[TEST_SESSION_ID] = this.testSessionSpan.context().toTraceId()
235
235
  formattedSpan.meta[TEST_MODULE_ID] = this.testModuleSpan.context().toSpanId()
236
236
  Object.assign(formattedSpan.meta, this.getSessionRequestErrorTags())
237
- formattedSpan.meta[TEST_COMMAND] = this.command
237
+ formattedSpan.meta[TEST_FRAMEWORK_VERSION] = this.frameworkVersion
238
238
  formattedSpan.meta[TEST_MODULE] = this.constructor.id
239
239
  // MISSING _trace.startTime and _trace.ticks - because by now the suite is already serialized
240
240
  const testSuite = this._testSuiteSpansByTestSuiteAbsolutePath.get(
@@ -326,7 +326,7 @@ class PlaywrightPlugin extends CiPlugin {
326
326
  }) => {
327
327
  if (!span) return
328
328
 
329
- const isRUMActive = span.context()._tags[TEST_IS_RUM_ACTIVE]
329
+ const isRUMActive = span.context().getTag(TEST_IS_RUM_ACTIVE)
330
330
 
331
331
  span.setTag(TEST_STATUS, testStatus)
332
332
 
@@ -416,7 +416,7 @@ class PlaywrightPlugin extends CiPlugin {
416
416
  TELEMETRY_EVENT_FINISHED,
417
417
  'test',
418
418
  {
419
- hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS],
419
+ hasCodeOwners: !!span.context().getTag(TEST_CODE_OWNERS),
420
420
  isNew,
421
421
  isRum: isRUMActive,
422
422
  browserDriver: 'playwright',
@@ -135,7 +135,7 @@ class SchemaExtractor {
135
135
  return
136
136
  }
137
137
 
138
- if (span.context()._tags[SCHEMA_TYPE] && operation === 'serialization') {
138
+ if (span.context().getTag(SCHEMA_TYPE) && operation === 'serialization') {
139
139
  // we have already added a schema to this span, this call is an encode of nested schema types
140
140
  return
141
141
  }
@@ -11,6 +11,17 @@ class RedisPlugin extends CachePlugin {
11
11
  static id = 'redis'
12
12
  static system = 'redis'
13
13
 
14
+ /** @type {string} */
15
+ #rawCommandKey
16
+ /** @type {Map<string | undefined, { name: string, source: string | undefined }>} */
17
+ #serviceByConnection = new Map()
18
+ // `nomenclature.config` identity at last cache fill. `withNamingSchema` swaps it without
19
+ // running this plugin's `configure`, so identity drives invalidation in `bindStart`.
20
+ /** @type {object | undefined} */
21
+ #lastNomenclatureConfig
22
+ /** @type {string | undefined} */
23
+ #cachedOperationName
24
+
14
25
  constructor (...args) {
15
26
  super(...args)
16
27
  this._spanType = 'redis'
@@ -25,14 +36,27 @@ class RedisPlugin extends CachePlugin {
25
36
  return { noop: true }
26
37
  }
27
38
 
39
+ const nomConfig = this._tracer._nomenclature.config
40
+ if (this.#lastNomenclatureConfig !== nomConfig) {
41
+ this.#lastNomenclatureConfig = nomConfig
42
+ this.#cachedOperationName = undefined
43
+ this.#serviceByConnection.clear()
44
+ }
45
+
46
+ let service = this.#serviceByConnection.get(connectionName)
47
+ if (service === undefined) {
48
+ service = this.serviceName({ pluginConfig: this.config, system: this.system, connectionName })
49
+ this.#serviceByConnection.set(connectionName, service)
50
+ }
51
+
28
52
  this.startSpan({
29
53
  resource,
30
- service: this.serviceName({ pluginConfig: this.config, system: this.system, connectionName }),
54
+ service,
31
55
  type: this._spanType,
32
56
  meta: {
33
57
  'db.type': this._spanType,
34
58
  'db.name': db || '0',
35
- [`${this._spanType}.raw_command`]: formatCommand(normalizedCommand, args, argsStartIndex),
59
+ [this.#rawCommandKey]: formatCommand(normalizedCommand, args, argsStartIndex),
36
60
  'out.host': connectionOptions.host,
37
61
  [CLIENT_PORT_KEY]: connectionOptions.port,
38
62
  },
@@ -41,8 +65,19 @@ class RedisPlugin extends CachePlugin {
41
65
  return ctx.currentStore
42
66
  }
43
67
 
68
+ operationName () {
69
+ this.#cachedOperationName ??= super.operationName()
70
+ return this.#cachedOperationName
71
+ }
72
+
44
73
  configure (config) {
45
74
  super.configure(normalizeConfig(config))
75
+ // Subclasses (iovalkey) overwrite `_spanType` in their constructor, before any `configure`,
76
+ // so reading it here is stable.
77
+ this.#rawCommandKey = `${this._spanType}.raw_command`
78
+ this.#lastNomenclatureConfig = undefined
79
+ this.#cachedOperationName = undefined
80
+ this.#serviceByConnection.clear()
46
81
  }
47
82
  }
48
83
 
@@ -42,7 +42,7 @@ function addDeliveryAnnotations (msg, tracer, span) {
42
42
  tracer.inject(span, 'text_map', msg.delivery_annotations)
43
43
 
44
44
  if (tracer._config.dsmEnabled) {
45
- const targetName = span.context()._tags['amqp.link.target.address']
45
+ const targetName = span.context().getTag('amqp.link.target.address')
46
46
  const payloadSize = getAmqpMessageSize({ content: msg.body, headers: msg.delivery_annotations })
47
47
  const dataStreamsContext = tracer
48
48
  .setCheckpoint(['direction:out', `exchange:${targetName}`, 'type:rabbitmq'], span, payloadSize)
@@ -9,31 +9,35 @@ const { COMPONENT } = require('../../dd-trace/src/constants')
9
9
  class RouterPlugin extends WebPlugin {
10
10
  static id = 'router'
11
11
 
12
- #storeStacks = new WeakMap()
13
12
  #contexts = new WeakMap()
14
13
 
15
14
  constructor (...args) {
16
15
  super(...args)
17
16
 
18
17
  this.addSub(`apm:${this.constructor.id}:middleware:enter`, ({ req, name, route }) => {
19
- const childOf = this.#getActive(req) || this.#getStoreSpan()
20
-
18
+ // One ALS hop covers both the parent-span fallback (when no
19
+ // per-request context exists yet) and the `storeStack` push below.
20
+ // The previous shape paid an ALS read inside `#getStoreSpan` and a
21
+ // second one here for the saved-store push.
22
+ const store = storage('legacy').getStore()
23
+ let context = this.#contexts.get(req)
24
+ let childOf
25
+ if (context !== undefined) {
26
+ const middleware = context.middleware
27
+ childOf = middleware.length === 0 ? context.span : middleware[middleware.length - 1]
28
+ } else if (store) {
29
+ childOf = store.span
30
+ }
21
31
  if (!childOf) return
22
32
 
23
33
  const span = this.#getMiddlewareSpan(name, childOf)
24
- const context = this.#createContext(req, route, childOf)
34
+ context = this.#updateContext(req, context, route, childOf)
25
35
 
26
36
  if (childOf !== span) {
27
37
  context.middleware.push(span)
28
38
  }
29
39
 
30
- const store = storage('legacy').getStore()
31
- let storeStack = this.#storeStacks.get(req)
32
- if (!storeStack) {
33
- storeStack = []
34
- this.#storeStacks.set(req, storeStack)
35
- }
36
- storeStack.push(store)
40
+ context.storeStack.push(store)
37
41
  this.enter(span, store)
38
42
 
39
43
  web.patch(req)
@@ -57,11 +61,8 @@ class RouterPlugin extends WebPlugin {
57
61
  })
58
62
 
59
63
  this.addSub(`apm:${this.constructor.id}:middleware:exit`, ({ req }) => {
60
- const storeStack = this.#storeStacks.get(req)
61
- const savedStore = storeStack && storeStack.pop()
62
- if (storeStack && storeStack.length === 0) {
63
- this.#storeStacks.delete(req)
64
- }
64
+ const context = this.#contexts.get(req)
65
+ const savedStore = context && context.storeStack.pop()
65
66
  const span = savedStore && savedStore.span
66
67
  this.enter(span, savedStore)
67
68
  })
@@ -71,8 +72,10 @@ class RouterPlugin extends WebPlugin {
71
72
 
72
73
  if (!this.config.middleware) return
73
74
 
74
- const span = this.#getActive(req)
75
-
75
+ const context = this.#contexts.get(req)
76
+ if (!context) return
77
+ const middleware = context.middleware
78
+ const span = middleware.length === 0 ? context.span : middleware[middleware.length - 1]
76
79
  if (!span) return
77
80
 
78
81
  span.setTag('error', error)
@@ -91,21 +94,6 @@ class RouterPlugin extends WebPlugin {
91
94
  })
92
95
  }
93
96
 
94
- #getActive (req) {
95
- const context = this.#contexts.get(req)
96
-
97
- if (!context) return
98
- if (context.middleware.length === 0) return context.span
99
-
100
- return context.middleware.at(-1)
101
- }
102
-
103
- #getStoreSpan () {
104
- const store = storage('legacy').getStore()
105
-
106
- return store && store.span
107
- }
108
-
109
97
  #getMiddlewareSpan (name, childOf) {
110
98
  if (this.config.middleware === false) {
111
99
  return childOf
@@ -125,9 +113,7 @@ class RouterPlugin extends WebPlugin {
125
113
  return span
126
114
  }
127
115
 
128
- #createContext (req, route, span) {
129
- let context = this.#contexts.get(req)
130
-
116
+ #updateContext (req, context, route, span) {
131
117
  if (!route || route === '/' || route === '*') {
132
118
  route = ''
133
119
  }
@@ -141,17 +127,20 @@ class RouterPlugin extends WebPlugin {
141
127
  if (isMoreSpecificThan(route, context.route)) {
142
128
  context.route = route
143
129
  }
144
- } else {
145
- context = {
146
- span,
147
- stack: [route],
148
- route,
149
- middleware: [],
150
- }
130
+ return context
131
+ }
151
132
 
152
- this.#contexts.set(req, context)
133
+ // Five-property shape pinned at allocation so every request shares the
134
+ // same hidden class — no per-field transitions after construction.
135
+ context = {
136
+ span,
137
+ stack: [route],
138
+ route,
139
+ middleware: [],
140
+ storeStack: [],
153
141
  }
154
142
 
143
+ this.#contexts.set(req, context)
155
144
  return context
156
145
  }
157
146
  }
@@ -14,7 +14,7 @@ const {
14
14
  const { SPAN_TYPE } = require('../../../ext/tags')
15
15
 
16
16
  function isTestSpan (span) {
17
- return span.context()._tags[SPAN_TYPE] === 'test'
17
+ return span.context().getTag(SPAN_TYPE) === 'test'
18
18
  }
19
19
 
20
20
  function getTestSpanFromTrace (trace) {
@@ -27,6 +27,7 @@ class UndiciPlugin extends HttpClientPlugin {
27
27
  // Subscribe to native undici diagnostic channels for undici >= 4.7.0
28
28
  // These channels fire for ALL undici requests (fetch, request, stream, etc.)
29
29
  this.addSub('undici:request:create', this.#onNativeRequestCreate.bind(this))
30
+ this.addSub('undici:request:bodySent', this.#onNativeRequestBodySent.bind(this))
30
31
  this.addSub('undici:request:headers', this.#onNativeRequestHeaders.bind(this))
31
32
  this.addSub('undici:request:trailers', this.#onNativeRequestTrailers.bind(this))
32
33
  this.addSub('undici:request:error', this.#onNativeRequestError.bind(this))
@@ -109,12 +110,30 @@ class UndiciPlugin extends HttpClientPlugin {
109
110
  span,
110
111
  store,
111
112
  uri,
113
+ method,
112
114
  })
113
115
 
114
116
  // Enter the span context
115
117
  storage('legacy').enterWith({ ...store, span })
116
118
  }
117
119
 
120
+ #onNativeRequestBodySent ({ request }) {
121
+ const ctx = requestContexts.get(request)
122
+ if (!ctx || ctx.method !== 'CONNECT') return
123
+
124
+ const { span, store } = ctx
125
+
126
+ this.config.hooks.request(span, null, null)
127
+
128
+ span.finish()
129
+
130
+ requestContexts.delete(request)
131
+
132
+ if (store) {
133
+ storage('legacy').enterWith(store)
134
+ }
135
+ }
136
+
118
137
  #onNativeRequestHeaders ({ request, response }) {
119
138
  const ctx = requestContexts.get(request)
120
139
  if (!ctx) return
@@ -15,7 +15,7 @@ const {
15
15
  TEST_IS_RETRY,
16
16
  TEST_CODE_COVERAGE_LINES_PCT,
17
17
  TEST_CODE_OWNERS,
18
- TEST_LEVEL_EVENT_TYPES,
18
+ TEST_COMMAND,
19
19
  TEST_SESSION_NAME,
20
20
  TEST_SOURCE_START,
21
21
  TEST_IS_NEW,
@@ -56,6 +56,16 @@ class VitestPlugin extends CiPlugin {
56
56
 
57
57
  this.taskToFinishTime = new WeakMap()
58
58
 
59
+ this.addSub('ci:vitest:session:configuration', ({ onDone }) => {
60
+ const testSessionSpanContext = this.testSessionSpan?.context()
61
+ const testModuleSpanContext = this.testModuleSpan?.context()
62
+ onDone({
63
+ testSessionId: testSessionSpanContext?.toTraceId(),
64
+ testModuleId: testModuleSpanContext?.toSpanId(),
65
+ testCommand: this.command,
66
+ })
67
+ })
68
+
59
69
  this.addSub('ci:vitest:test:is-new', ({ knownTests, testSuiteAbsolutePath, testName, onDone }) => {
60
70
  // if for whatever reason the worker does not receive valid known tests, we don't consider it as new
61
71
  if (!knownTests.vitest) {
@@ -299,31 +309,25 @@ class VitestPlugin extends CiPlugin {
299
309
  this.addBind('ci:vitest:test-suite:start', (ctx) => {
300
310
  const { testSuiteAbsolutePath, frameworkVersion } = ctx
301
311
 
302
- // TODO: Handle case where the command is not set
303
- this.command = this._tracerConfig.DD_CIVISIBILITY_TEST_COMMAND
312
+ const testCommand = ctx.testCommand || 'vitest run'
313
+ const { testSessionId, testModuleId } = ctx
314
+ this.command = testCommand
304
315
  this.frameworkVersion = frameworkVersion
305
- const testSessionSpanContext = this.tracer.extract('text_map', {
306
- // TODO: Handle case where the session ID or module ID is not set
307
- 'x-datadog-trace-id': this._tracerConfig.DD_CIVISIBILITY_TEST_SESSION_ID,
308
- 'x-datadog-parent-id': this._tracerConfig.DD_CIVISIBILITY_TEST_MODULE_ID,
309
- })
316
+ const testSessionSpanContext = testSessionId && testModuleId
317
+ ? this.tracer.extract('text_map', {
318
+ 'x-datadog-trace-id': testSessionId,
319
+ 'x-datadog-parent-id': testModuleId,
320
+ })
321
+ : undefined
310
322
 
311
323
  const trimmedCommand = DD_MAJOR < 6 ? this.command : 'vitest run'
312
324
  // test suites run in a different process, so they also need to init the metadata dictionary
313
325
  const testSessionName = getTestSessionName(this.config, trimmedCommand, this.testEnvironmentMetadata)
314
- const metadataTags = {}
315
- for (const testLevel of TEST_LEVEL_EVENT_TYPES) {
316
- metadataTags[testLevel] = {
317
- [TEST_SESSION_NAME]: testSessionName,
318
- }
319
- }
320
326
  if (this.tracer._exporter.addMetadataTags) {
321
- const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id)
322
- metadataTags.test = {
323
- ...metadataTags.test,
324
- ...libraryCapabilitiesTags,
325
- }
326
- this.tracer._exporter.addMetadataTags(metadataTags)
327
+ this.tracer._exporter.addMetadataTags({
328
+ '*': { [TEST_COMMAND]: testCommand, [TEST_SESSION_NAME]: testSessionName },
329
+ test: getLibraryCapabilitiesTags(this.constructor.id),
330
+ })
327
331
  }
328
332
 
329
333
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
@@ -1,8 +1,38 @@
1
1
  'use strict'
2
2
 
3
+ const { buildLogHolder, messageProxy } = require('../../dd-trace/src/plugins/log_injection')
3
4
  const LogPlugin = require('../../dd-trace/src/plugins/log_plugin')
4
5
 
5
6
  class WinstonPlugin extends LogPlugin {
6
7
  static id = 'winston'
8
+
9
+ constructor (...args) {
10
+ super(...args)
11
+ this.addSub('apm:winston:log', (arg) => this.handleLog(arg))
12
+ }
13
+
14
+ /**
15
+ * The prototype + extensibility check is load-bearing. The Proxy
16
+ * fallback keeps `dd` off caller-owned objects (Error, Set, Map, any
17
+ * user class) and out of non-extensible records, where a strict-mode
18
+ * write would throw and `Plugin.addSub` would react by disabling the
19
+ * plugin for the rest of the process.
20
+ *
21
+ * @param {{ message: unknown }} arg
22
+ */
23
+ handleLog (arg) {
24
+ const info = arg.message
25
+ if (info === null || typeof info !== 'object' || Object.hasOwn(info, 'dd')) return
26
+
27
+ const logHolder = buildLogHolder(this.tracer)
28
+ if (!logHolder) return
29
+
30
+ if (Object.getPrototypeOf(info) === Object.prototype && Object.isExtensible(info)) {
31
+ info.dd = logHolder.dd
32
+ } else {
33
+ arg.message = messageProxy(info, logHolder)
34
+ }
35
+ }
7
36
  }
37
+
8
38
  module.exports = WinstonPlugin
@@ -13,11 +13,16 @@ const skipMethodSize = skipMethods.size
13
13
 
14
14
  const nonConfigurableModuleExports = new WeakMap()
15
15
 
16
+ // Reused descriptor scratch space for the `name` and `length` slots that
17
+ // `copyProperties` and `wrapCallback` rewrite per wrap. `Object.defineProperty`
18
+ // reads the descriptor's slots synchronously and does not retain the object,
19
+ // so mutating `value` between calls is safe.
20
+ const lengthDescriptor = { value: 0, configurable: true }
21
+ const nameDescriptor = { value: '', configurable: true }
22
+
16
23
  /**
17
- * Copies properties from the original function to the wrapped function.
18
- *
19
- * @param {Function} original - The original function.
20
- * @param {Function} wrapped - The wrapped function.
24
+ * @param {Function} original
25
+ * @param {Function} wrapped
21
26
  */
22
27
  function copyProperties (original, wrapped) {
23
28
  if (original.constructor !== wrapped.constructor) {
@@ -26,11 +31,15 @@ function copyProperties (original, wrapped) {
26
31
  }
27
32
 
28
33
  const ownKeys = Reflect.ownKeys(original)
29
- if (original.length !== wrapped.length) {
30
- Object.defineProperty(wrapped, 'length', { value: original.length, configurable: true })
34
+ const originalLength = original.length
35
+ if (originalLength !== wrapped.length) {
36
+ lengthDescriptor.value = originalLength
37
+ Object.defineProperty(wrapped, 'length', lengthDescriptor)
31
38
  }
32
- if (original.name !== wrapped.name) {
33
- Object.defineProperty(wrapped, 'name', { value: original.name, configurable: true })
39
+ const originalName = original.name
40
+ if (originalName !== wrapped.name) {
41
+ nameDescriptor.value = originalName
42
+ Object.defineProperty(wrapped, 'name', nameDescriptor)
34
43
  }
35
44
  if (ownKeys.length !== 2) {
36
45
  for (const key of ownKeys) {
@@ -46,11 +55,9 @@ function copyProperties (original, wrapped) {
46
55
  }
47
56
 
48
57
  /**
49
- * Copies properties from the original object to the wrapped object, skipping a specific key.
50
- *
51
- * @param {Record<string | symbol, unknown>} original - The original object.
52
- * @param {Record<string | symbol, unknown>} wrapped - The wrapped object.
53
- * @param {string | symbol} skipKey - The key to skip during copying.
58
+ * @param {Record<string | symbol, unknown>} original
59
+ * @param {Record<string | symbol, unknown>} wrapped
60
+ * @param {string | symbol} skipKey
54
61
  */
55
62
  function copyObjectProperties (original, wrapped, skipKey) {
56
63
  const ownKeys = Reflect.ownKeys(original)
@@ -66,11 +73,8 @@ function copyObjectProperties (original, wrapped, skipKey) {
66
73
  }
67
74
 
68
75
  /**
69
- * Wraps a function with a wrapper function.
70
- *
71
- * @param {Function} original - The original function to wrap.
72
- * @param {(original: Function) => Function} wrapper - The wrapper function.
73
- * @returns {Function} The wrapped function.
76
+ * @param {Function} original
77
+ * @param {(original: Function) => Function} wrapper
74
78
  */
75
79
  function wrapFunction (original, wrapper) {
76
80
  if (typeof original !== 'function') return original
@@ -82,6 +86,32 @@ function wrapFunction (original, wrapper) {
82
86
  return wrapped
83
87
  }
84
88
 
89
+ /**
90
+ * Lean variant of `wrapFunction` for tracer-owned closures wrapping a
91
+ * user-supplied callback. Preserves `name` and `length` only; skips the
92
+ * prototype copy, `assertNotClass`, and the `Reflect.ownKeys` descriptor
93
+ * walk. Use `wrapFunction` instead when the wrapped value needs its
94
+ * prototype, has own properties the caller may read, or is `new`-ed.
95
+ *
96
+ * @param {Function} original
97
+ * @param {(original: Function) => Function} wrapper
98
+ */
99
+ function wrapCallback (original, wrapper) {
100
+ if (typeof original !== 'function') {
101
+ return original
102
+ }
103
+ const wrapped = wrapper(original)
104
+ if (wrapped.name !== original.name) {
105
+ nameDescriptor.value = original.name
106
+ Object.defineProperty(wrapped, 'name', nameDescriptor)
107
+ }
108
+ if (wrapped.length !== original.length) {
109
+ lengthDescriptor.value = original.length
110
+ Object.defineProperty(wrapped, 'length', lengthDescriptor)
111
+ }
112
+ return wrapped
113
+ }
114
+
85
115
  /**
86
116
  * Wraps a method of an object with a wrapper function.
87
117
  *
@@ -140,7 +170,6 @@ function wrap (target, name, wrapper, options) {
140
170
  copyProperties(original, wrapped)
141
171
 
142
172
  if (descriptor.writable) {
143
- // Fast path for assigned properties.
144
173
  if (descriptor.configurable && descriptor.enumerable) {
145
174
  target[name] = wrapped
146
175
  return target
@@ -175,8 +204,6 @@ function wrap (target, name, wrapper, options) {
175
204
  // with this code. That way it would be possible to directly pass through
176
205
  // the entries.
177
206
 
178
- // In case more than a single property is not configurable and writable,
179
- // Just reuse the already created object.
180
207
  let moduleExports = nonConfigurableModuleExports.get(target)
181
208
  if (!moduleExports) {
182
209
  if (typeof target === 'function') {
@@ -280,6 +307,7 @@ function assertNotClass (target) {
280
307
 
281
308
  module.exports = {
282
309
  wrap,
310
+ wrapCallback,
283
311
  wrapFunction,
284
312
  massWrap,
285
313
  }
@@ -44,7 +44,7 @@ function disable () {
44
44
  /**
45
45
  * Handles channel messages with pre-converted messages.
46
46
  *
47
- * @param {{messages: Array<object>, resolve: Function, reject: Function}} ctx
47
+ * @param {{messages: Array<object>, integration?: string, resolve: Function, reject: Function}} ctx
48
48
  */
49
49
  function onEvaluate (ctx) {
50
50
  if (!ctx.messages?.length) {
@@ -148,7 +148,7 @@ class AIGuard extends NoopAIGuard {
148
148
  #setRootSpanClientIpTags (rootSpan) {
149
149
  if (!rootSpan) return
150
150
 
151
- const currentTags = rootSpan.context()._tags
151
+ const currentTags = rootSpan.context().getTags()
152
152
  const needsHttpClientIp = !Object.hasOwn(currentTags, HTTP_CLIENT_IP)
153
153
  const needsNetworkClientIp = !Object.hasOwn(currentTags, NETWORK_CLIENT_IP)
154
154
 
@@ -79,7 +79,7 @@ function getRouteOrEndpoint (context, statusCode) {
79
79
 
80
80
  // If route is not available, fallback to http.endpoint
81
81
  if (statusCode !== 404) {
82
- const endpoint = context?.span?.context()?._tags?.['http.endpoint']
82
+ const endpoint = context?.span?.context()?.getTag('http.endpoint')
83
83
  if (endpoint) {
84
84
  return endpoint
85
85
  }
@@ -104,8 +104,8 @@ function getBlockWithContentData (req, specificType, actionParameters) {
104
104
  const statusCode = actionParameters?.status_code || 403
105
105
 
106
106
  const headers = {
107
- 'Content-Type': type,
108
- 'Content-Length': Buffer.byteLength(body),
107
+ 'content-type': type,
108
+ 'content-length': Buffer.byteLength(body),
109
109
  }
110
110
 
111
111
  return { body, statusCode, headers }