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
@@ -11,6 +11,7 @@ const {
11
11
  TEST_PARAMETERS,
12
12
  finishAllTraceSpans,
13
13
  getTestSuitePath,
14
+ getRelativeCoverageFiles,
14
15
  getTestParametersString,
15
16
  getTestSuiteCommonTags,
16
17
  addIntelligentTestRunnerSpanTags,
@@ -73,8 +74,10 @@ class MochaPlugin extends CiPlugin {
73
74
  this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
74
75
  }
75
76
 
76
- const relativeCoverageFiles = [...coverageFiles, suiteFile]
77
- .map(filename => getTestSuitePath(filename, this.repositoryRoot || this.sourceRoot))
77
+ const relativeCoverageFiles = [
78
+ ...getRelativeCoverageFiles(coverageFiles, this.repositoryRoot || this.sourceRoot),
79
+ getTestSuitePath(suiteFile, this.repositoryRoot || this.sourceRoot),
80
+ ]
78
81
 
79
82
  const { _traceId, _spanId } = testSuiteSpan.context()
80
83
 
@@ -152,7 +155,7 @@ class MochaPlugin extends CiPlugin {
152
155
  this.addSub('ci:mocha:test-suite:finish', ({ testSuiteSpan, status }) => {
153
156
  if (testSuiteSpan) {
154
157
  // the test status of the suite may have been set in ci:mocha:test-suite:error already
155
- if (!testSuiteSpan.context()._tags[TEST_STATUS]) {
158
+ if (!testSuiteSpan.context().getTag(TEST_STATUS)) {
156
159
  testSuiteSpan.setTag(TEST_STATUS, status)
157
160
  }
158
161
  testSuiteSpan.finish()
@@ -352,6 +355,7 @@ class MochaPlugin extends CiPlugin {
352
355
  status,
353
356
  isSuitesSkipped,
354
357
  testCodeCoverageLinesTotal,
358
+ testSessionCoverageFiles,
355
359
  numSkippedSuites,
356
360
  hasForcedToRunSuites,
357
361
  hasUnskippableSuites,
@@ -362,7 +366,11 @@ class MochaPlugin extends CiPlugin {
362
366
  isParallel,
363
367
  }) => {
364
368
  if (this.testSessionSpan) {
365
- const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
369
+ const {
370
+ isSuitesSkippingEnabled,
371
+ isCodeCoverageEnabled,
372
+ isCoverageReportUploadEnabled,
373
+ } = this.libraryConfig || {}
366
374
  this.testSessionSpan.setTag(TEST_STATUS, status)
367
375
  this.testModuleSpan.setTag(TEST_STATUS, status)
368
376
 
@@ -394,6 +402,13 @@ class MochaPlugin extends CiPlugin {
394
402
  }
395
403
  )
396
404
 
405
+ if (testSessionCoverageFiles?.length && isCoverageReportUploadEnabled) {
406
+ this.tracer._exporter.exportCoverage({
407
+ sessionId: this.testSessionSpan.context()._traceId,
408
+ files: testSessionCoverageFiles,
409
+ })
410
+ }
411
+
397
412
  if (isEarlyFlakeDetectionEnabled) {
398
413
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
399
414
  }
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { isMap, isRegExp } = require('node:util').types
4
+
3
5
  const DatabasePlugin = require('../../dd-trace/src/plugins/database')
4
6
 
5
7
  class MongodbCorePlugin extends DatabasePlugin {
@@ -20,6 +22,9 @@ class MongodbCorePlugin extends DatabasePlugin {
20
22
 
21
23
  this.config.heartbeatEnabled = config.heartbeatEnabled ??
22
24
  this._tracerConfig.DD_TRACE_MONGODB_HEARTBEAT_ENABLED
25
+ this.config.obfuscateQuery = normaliseObfuscateQuery(
26
+ config.obfuscateQuery ?? this._tracerConfig.DD_TRACE_MONGODB_OBFUSCATE_QUERY
27
+ )
23
28
  }
24
29
 
25
30
  bindStart (ctx) {
@@ -28,7 +33,7 @@ class MongodbCorePlugin extends DatabasePlugin {
28
33
  if (!this.config.heartbeatEnabled && isHeartbeat(ops, this.config)) {
29
34
  return
30
35
  }
31
- const query = getQuery(ops)
36
+ const query = getQuery(ops, this.config.obfuscateQuery)
32
37
  const resource = truncate(getResource(this, ns, query, name))
33
38
  const serviceResult = this.serviceName({ pluginConfig: this.config })
34
39
  const span = this.startSpan(this.operationName(), {
@@ -106,15 +111,19 @@ function extractQuery (statements) {
106
111
  return extractedQueries
107
112
  }
108
113
 
109
- function getQuery (cmd) {
114
+ /**
115
+ * @param {Record<string, unknown> | unknown[] | undefined} cmd
116
+ * @param {'none' | 'types' | 'redact'} mode
117
+ */
118
+ function getQuery (cmd, mode) {
110
119
  if (!cmd || (typeof cmd !== 'object' && !Array.isArray(cmd))) return
111
120
 
112
- if (Array.isArray(cmd)) return sanitiseAndStringify(extractQuery(cmd))
113
- if (cmd.query) return sanitiseAndStringify(cmd.query)
114
- if (cmd.filter) return sanitiseAndStringify(cmd.filter)
115
- if (cmd.pipeline) return sanitiseAndStringify(cmd.pipeline)
116
- if (cmd.deletes) return sanitiseAndStringify(extractQuery(cmd.deletes))
117
- if (cmd.updates) return sanitiseAndStringify(extractQuery(cmd.updates))
121
+ if (Array.isArray(cmd)) return sanitiseAndStringify(extractQuery(cmd), mode)
122
+ if (cmd.query) return sanitiseAndStringify(cmd.query, mode)
123
+ if (cmd.filter) return sanitiseAndStringify(cmd.filter, mode)
124
+ if (cmd.pipeline) return sanitiseAndStringify(cmd.pipeline, mode)
125
+ if (cmd.deletes) return sanitiseAndStringify(extractQuery(cmd.deletes), mode)
126
+ if (cmd.updates) return sanitiseAndStringify(extractQuery(cmd.updates), mode)
118
127
  }
119
128
 
120
129
  function getResource (plugin, ns, query, operationName) {
@@ -131,37 +140,304 @@ function truncate (input) {
131
140
  return input.length > MAX_QUERY_LENGTH ? input.slice(0, MAX_QUERY_LENGTH) : input
132
141
  }
133
142
 
134
- // Single-pass sanitisation. The replacer:
135
- // - skips functions and coerces bigint to its decimal string,
136
- // - returns '?' for Buffer / BSON Binary on the *original* value (JSON.stringify already invoked
137
- // toJSON before calling us; Buffer / Binary do have toJSON outputs we want to suppress),
138
- // - lets JSON.stringify call toJSON on other BSON types (ObjectId, Long, Decimal128, Date, Timestamp, ...)
139
- // so the result lands here as a primitive or plain object,
140
- // - returns '?' for BSON types without toJSON (MinKey, MaxKey) where `value === original`,
141
- // - tracks depth via an ancestor stack so cycles and depth >= MAX_DEPTH collapse to '?'.
142
- function sanitiseAndStringify (input) {
143
- const ancestors = []
144
- return JSON.stringify(input, function (key, value) {
145
- if (typeof value === 'function') return
146
- if (typeof value === 'bigint') return value.toString()
147
-
148
- const original = key === '' ? value : this[key]
149
- if (typeof original === 'object' && original !== null) {
150
- if (Buffer.isBuffer(original)) return '?'
151
- const bsontype = original._bsontype
152
- if (bsontype !== undefined && (bsontype === 'Binary' || value === original)) {
153
- return '?'
154
- }
143
+ // Depth doubles as the cycle bound: a cycle pushes past MAX_DEPTH and bails,
144
+ // after which the slow path catches it via its ancestor stack.
145
+ /** @param {unknown} input */
146
+ function canStringifyDirect (input) {
147
+ if (input === null ||
148
+ typeof input !== 'object' ||
149
+ ArrayBuffer.isView(input) ||
150
+ input._bsontype !== undefined ||
151
+ isRegExp(input) ||
152
+ isMap(input) ||
153
+ typeof input.toJSON === 'function') {
154
+ return false
155
+ }
156
+ return canStringifyDirectWalk(input, 1)
157
+ }
158
+
159
+ /**
160
+ * @param {Record<string, unknown> | unknown[]} value
161
+ * @param {number} depth
162
+ */
163
+ function canStringifyDirectWalk (value, depth) {
164
+ if (depth > MAX_DEPTH) return false
165
+ const children = Array.isArray(value) ? value : Object.values(value)
166
+ for (const child of children) {
167
+ if (child === null ||
168
+ typeof child === 'string' ||
169
+ typeof child === 'number' ||
170
+ typeof child === 'boolean') {
171
+ continue
172
+ }
173
+ if (typeof child !== 'object' ||
174
+ ArrayBuffer.isView(child) ||
175
+ child._bsontype !== undefined ||
176
+ isRegExp(child) ||
177
+ isMap(child) ||
178
+ typeof child.toJSON === 'function') {
179
+ return false
180
+ }
181
+ if (!canStringifyDirectWalk(child, depth + 1)) return false
182
+ }
183
+ return true
184
+ }
185
+
186
+ /**
187
+ * @param {Record<string, unknown> | unknown[]} input
188
+ * @param {'none' | 'types' | 'redact'} mode
189
+ */
190
+ function sanitiseAndStringify (input, mode) {
191
+ if (mode === 'none') {
192
+ if (canStringifyDirect(input)) return JSON.stringify(input)
193
+ return buildNone(input, [])
194
+ }
195
+ if (mode === 'redact') return buildRedact(input, [])
196
+ return buildTypes(input, [])
197
+ }
198
+
199
+ const REDACT_LEAF = '"?"'
200
+
201
+ /**
202
+ * @param {RegExp} value
203
+ * @returns {string}
204
+ */
205
+ function stringifyRegExp (value) {
206
+ return `{"$regex":${JSON.stringify(value.source)},"$options":${JSON.stringify(value.flags)}}`
207
+ }
208
+
209
+ /**
210
+ * @param {Record<string, unknown> | unknown[]} value
211
+ * @param {object[]} ancestors
212
+ * @returns {string | undefined}
213
+ */
214
+ function buildNone (value, ancestors) {
215
+ // ArrayBuffer views (Buffer, every TypedArray, DataView) and Binary BSON
216
+ // wrappers redact at the leaf; the walker neither recurses into the bytes
217
+ // nor invokes any custom conversion.
218
+ const bsontype = value._bsontype
219
+ if (ArrayBuffer.isView(value) || bsontype === 'Binary' ||
220
+ ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
221
+ return REDACT_LEAF
222
+ }
223
+
224
+ if (isRegExp(value)) return stringifyRegExp(value)
225
+
226
+ // Mirror JSON.stringify's contract: when `toJSON` is present, walk its
227
+ // result (wrappers like Timestamp / Decimal128 expand to a small object,
228
+ // ObjectId / Date flatten to a primitive).
229
+ if (typeof value.toJSON === 'function') {
230
+ const json = value.toJSON()
231
+ if (json === value) return REDACT_LEAF
232
+ // JSON.stringify keeps a null result as null (an invalid Date's toJSON
233
+ // returns null); only function / symbol / undefined results drop the key.
234
+ if (json === null) return 'null'
235
+ if (typeof json !== 'object') return classifyLeafForNone(json)
236
+ // A wrapper that exposes binary state through toJSON (Buffer-backed
237
+ // class with WeakMap state, etc.) returns a TypedArray here. Re-screen
238
+ // before the per-key walk would expand it element by element.
239
+ if (ArrayBuffer.isView(json) || json._bsontype === 'Binary') return REDACT_LEAF
240
+ value = json
241
+ } else if (bsontype !== undefined) {
242
+ return REDACT_LEAF
243
+ }
244
+
245
+ // The driver serializes a Map via its entries; mirror that as a document so
246
+ // the tag matches the wire shape.
247
+ if (isMap(value)) value = Object.fromEntries(value)
248
+
249
+ ancestors.push(value)
250
+
251
+ let result
252
+ if (Array.isArray(value)) {
253
+ result = '['
254
+ let sep = ''
255
+ for (let i = 0; i < value.length; i++) {
256
+ // JSON.stringify renders unsupported leaves (function, symbol, undefined) as null in arrays.
257
+ result += sep + (classifyForNone(value[i], ancestors) ?? 'null')
258
+ sep = ','
259
+ }
260
+ result += ']'
261
+ } else {
262
+ result = '{'
263
+ let sep = ''
264
+ for (const key of Object.keys(value)) {
265
+ const childResult = classifyForNone(value[key], ancestors)
266
+ if (childResult === undefined) continue
267
+ result += sep + JSON.stringify(key) + ':' + childResult
268
+ sep = ','
269
+ }
270
+ result += '}'
271
+ }
272
+ ancestors.pop()
273
+ return result
274
+ }
275
+
276
+ /**
277
+ * @param {unknown} child
278
+ * @param {object[]} ancestors
279
+ * @returns {string | undefined}
280
+ */
281
+ function classifyForNone (child, ancestors) {
282
+ if (typeof child !== 'object') return classifyLeafForNone(child)
283
+ if (child === null) return 'null'
284
+ return buildNone(child, ancestors)
285
+ }
286
+
287
+ /**
288
+ * @param {unknown} leaf
289
+ * @returns {string | undefined}
290
+ */
291
+ function classifyLeafForNone (leaf) {
292
+ // Implicit `undefined` for function / symbol / undefined matches the
293
+ // contract callers rely on: JSON.stringify drops those property values
294
+ // inside objects and writes `null` in arrays.
295
+ switch (typeof leaf) {
296
+ case 'string': return JSON.stringify(leaf)
297
+ case 'number': return Number.isFinite(leaf) ? String(leaf) : 'null'
298
+ case 'boolean': return leaf ? 'true' : 'false'
299
+ case 'bigint': return `"${String(leaf)}"`
300
+ }
301
+ }
302
+
303
+ /**
304
+ * @param {Record<string, unknown> | unknown[]} value
305
+ * @param {object[]} ancestors
306
+ */
307
+ function buildRedact (value, ancestors) {
308
+ const bsontype = value._bsontype
309
+ if (ArrayBuffer.isView(value) || bsontype === 'Binary' || isRegExp(value) ||
310
+ ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
311
+ return REDACT_LEAF
312
+ }
313
+
314
+ // Mirror JSON.stringify: when `toJSON` is present, walk its result (which
315
+ // wrappers like Timestamp / Decimal128 expand to `{$timestamp: "..."}` etc).
316
+ // A primitive, null, or self-reference collapses to the sentinel — master's
317
+ // `value === original` short-circuit.
318
+ if (typeof value.toJSON === 'function') {
319
+ const json = value.toJSON()
320
+ if (typeof json !== 'object' || json === null || json === value) return REDACT_LEAF
321
+ // Re-screen: toJSON can return a TypedArray or Binary BSON wrapper.
322
+ if (ArrayBuffer.isView(json) || json._bsontype === 'Binary') return REDACT_LEAF
323
+ value = json
324
+ } else if (bsontype !== undefined) {
325
+ return REDACT_LEAF
326
+ }
327
+
328
+ if (isMap(value)) value = Object.fromEntries(value)
329
+
330
+ ancestors.push(value)
331
+
332
+ let result
333
+ if (Array.isArray(value)) {
334
+ result = '['
335
+ let sep = ''
336
+ for (let i = 0; i < value.length; i++) {
337
+ result += sep + classifyForRedact(value[i], ancestors)
338
+ sep = ','
155
339
  }
340
+ result += ']'
341
+ } else {
342
+ result = '{'
343
+ let sep = ''
344
+ for (const key of Object.keys(value)) {
345
+ result += sep + JSON.stringify(key) + ':' + classifyForRedact(value[key], ancestors)
346
+ sep = ','
347
+ }
348
+ result += '}'
349
+ }
350
+ ancestors.pop()
351
+ return result
352
+ }
156
353
 
157
- if (value === null || typeof value !== 'object') return value
354
+ /**
355
+ * @param {unknown} child
356
+ * @param {object[]} ancestors
357
+ */
358
+ function classifyForRedact (child, ancestors) {
359
+ if (typeof child !== 'object' || child === null) return REDACT_LEAF
360
+ return buildRedact(child, ancestors)
361
+ }
158
362
 
159
- while (ancestors.length > 0 && ancestors.at(-1) !== this) ancestors.pop()
160
- if (ancestors.length >= MAX_DEPTH || ancestors.includes(value)) return '?'
161
- ancestors.push(value)
363
+ const TYPE_OBJECT = '"object"'
364
+ const TYPE_NULL = '"null"'
365
+ const TYPE_BY_TYPEOF = {
366
+ string: '"string"',
367
+ number: '"number"',
368
+ boolean: '"boolean"',
369
+ bigint: '"bigint"',
370
+ undefined: '"undefined"',
371
+ }
372
+
373
+ /**
374
+ * @param {Record<string, unknown> | unknown[]} value
375
+ * @param {object[]} ancestors
376
+ */
377
+ function buildTypes (value, ancestors) {
378
+ const bsontype = value._bsontype
379
+ if (ArrayBuffer.isView(value) || bsontype === 'Binary' || isRegExp(value) ||
380
+ ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
381
+ return TYPE_OBJECT
382
+ }
383
+
384
+ if (typeof value.toJSON === 'function') {
385
+ const json = value.toJSON()
386
+ if (typeof json !== 'object' ||
387
+ json === null ||
388
+ json === value ||
389
+ ArrayBuffer.isView(json) ||
390
+ json._bsontype === 'Binary') {
391
+ return TYPE_OBJECT
392
+ }
393
+ value = json
394
+ } else if (bsontype !== undefined) {
395
+ return TYPE_OBJECT
396
+ }
397
+
398
+ if (isMap(value)) value = Object.fromEntries(value)
399
+
400
+ ancestors.push(value)
401
+
402
+ let result
403
+ if (Array.isArray(value)) {
404
+ result = '['
405
+ let sep = ''
406
+ for (let i = 0; i < value.length; i++) {
407
+ // JSON.stringify renders unsupported leaves (function, symbol) as null in arrays.
408
+ result += sep + (classifyForTypes(value[i], ancestors) ?? 'null')
409
+ sep = ','
410
+ }
411
+ result += ']'
412
+ } else {
413
+ result = '{'
414
+ let sep = ''
415
+ for (const key of Object.keys(value)) {
416
+ const childResult = classifyForTypes(value[key], ancestors)
417
+ if (childResult === undefined) continue
418
+ result += sep + JSON.stringify(key) + ':' + childResult
419
+ sep = ','
420
+ }
421
+ result += '}'
422
+ }
423
+ ancestors.pop()
424
+ return result
425
+ }
426
+
427
+ /**
428
+ * @param {unknown} child
429
+ * @param {object[]} ancestors
430
+ */
431
+ function classifyForTypes (child, ancestors) {
432
+ if (typeof child !== 'object') return TYPE_BY_TYPEOF[typeof child]
433
+ if (child === null) return TYPE_NULL
434
+ return buildTypes(child, ancestors)
435
+ }
162
436
 
163
- return value
164
- })
437
+ /** @param {unknown} value */
438
+ function normaliseObfuscateQuery (value) {
439
+ if (value === 'types' || value === 'redact') return value
440
+ return 'none'
165
441
  }
166
442
 
167
443
  function isHeartbeat (ops, config) {
@@ -0,0 +1,43 @@
1
+ 'use strict'
2
+
3
+ const { TEXT_MAP } = require('../../../ext/formats')
4
+ const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
5
+ const { headersToTextMap } = require('./util')
6
+
7
+ const MESSAGING_DESTINATION_KEY = 'messaging.destination.name'
8
+
9
+ class NatsConsumerPlugin extends ConsumerPlugin {
10
+ static id = 'nats'
11
+ static operation = 'consume'
12
+
13
+ bindStart (ctx) {
14
+ const { subject: filter, message } = ctx
15
+ // For wildcard subscriptions (e.g. `orders.*`), `filter` is the subscription
16
+ // pattern but `message.subject` is the actual delivered subject. Prefer the
17
+ // delivered one for resource/destination so spans aren't all collapsed under
18
+ // the wildcard pattern. Fall back to the filter if the message is missing it.
19
+ const subject = typeof message?.subject === 'string' ? message.subject : filter
20
+ const carrier = headersToTextMap(message?.headers)
21
+ const childOf = carrier ? this.tracer.extract(TEXT_MAP, carrier) : null
22
+
23
+ const meta = {
24
+ component: 'nats',
25
+ 'nats.subject': subject,
26
+ [MESSAGING_DESTINATION_KEY]: subject,
27
+ }
28
+ if (filter && filter !== subject) {
29
+ meta['nats.subscription.subject'] = filter
30
+ }
31
+
32
+ this.startSpan({
33
+ childOf,
34
+ resource: subject,
35
+ type: 'worker',
36
+ meta,
37
+ }, ctx)
38
+
39
+ return ctx.currentStore
40
+ }
41
+ }
42
+
43
+ module.exports = NatsConsumerPlugin
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
4
+ const ProducerPlugin = require('./producer')
5
+ const ConsumerPlugin = require('./consumer')
6
+
7
+ class NatsPlugin extends CompositePlugin {
8
+ static id = 'nats'
9
+ // Disabled by default — users must opt in via DD_TRACE_NATS_ENABLED=true
10
+ // or `tracer.use('nats')`. Matches the feature parity dashboard policy.
11
+ static experimental = true
12
+ static get plugins () {
13
+ return {
14
+ producer: ProducerPlugin,
15
+ consumer: ConsumerPlugin,
16
+ }
17
+ }
18
+ }
19
+
20
+ module.exports = NatsPlugin
@@ -0,0 +1,62 @@
1
+ 'use strict'
2
+
3
+ const { TEXT_MAP } = require('../../../ext/formats')
4
+ const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
5
+ const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
6
+ const { getOperationName } = require('./util')
7
+
8
+ const MESSAGING_DESTINATION_KEY = 'messaging.destination.name'
9
+
10
+ class NatsProducerPlugin extends ProducerPlugin {
11
+ static id = 'nats'
12
+ static operation = 'publish'
13
+ static peerServicePrecursors = [MESSAGING_DESTINATION_KEY]
14
+
15
+ bindStart (ctx) {
16
+ const { subject, options, connection, type, createHeaders } = ctx
17
+ const server = connection?.protocol?.servers?.getCurrent?.() ??
18
+ connection?.protocol?.servers?.getCurrentServer?.()
19
+ const operation = getOperationName(type)
20
+
21
+ const span = this.startSpan({
22
+ resource: subject,
23
+ meta: {
24
+ component: 'nats',
25
+ 'nats.subject': subject,
26
+ 'nats.operation': operation,
27
+ [MESSAGING_DESTINATION_KEY]: subject,
28
+ 'out.host': server?.hostname,
29
+ },
30
+ }, ctx)
31
+
32
+ if (server?.port) {
33
+ span.setTag(CLIENT_PORT_KEY, server.port)
34
+ }
35
+
36
+ if (this.serverSupportsHeaders(connection)) {
37
+ let headers = options.headers
38
+ if (!headers && typeof createHeaders === 'function') {
39
+ headers = createHeaders()
40
+ options.headers = headers
41
+ }
42
+ if (headers && typeof headers.set === 'function') {
43
+ const carrier = {}
44
+ this.tracer.inject(span, TEXT_MAP, carrier)
45
+ for (const key of Object.keys(carrier)) {
46
+ headers.set(key, carrier[key])
47
+ }
48
+ }
49
+ }
50
+
51
+ return ctx.currentStore
52
+ }
53
+
54
+ serverSupportsHeaders (connection) {
55
+ const info = connection?.protocol?.info
56
+ // If info isn't available yet (e.g. publish before INFO), assume supported — modern NATS does.
57
+ if (!info) return true
58
+ return info.headers !== false
59
+ }
60
+ }
61
+
62
+ module.exports = NatsProducerPlugin
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ function headersToTextMap (msgHdrs) {
4
+ if (!msgHdrs || typeof msgHdrs[Symbol.iterator] !== 'function') return null
5
+ const textMap = {}
6
+ for (const [key, values] of msgHdrs) {
7
+ if (!Array.isArray(values) || values.length === 0) continue
8
+ // Trace headers are single-valued (injected via `set`, not `append`), so
9
+ // the first element is always the authoritative value.
10
+ textMap[key] = values[0]
11
+ }
12
+ return textMap
13
+ }
14
+
15
+ function getOperationName (type) {
16
+ switch (type) {
17
+ case 'publish':
18
+ return 'publish'
19
+ case 'request':
20
+ case 'requestMany':
21
+ return 'request'
22
+ default:
23
+ // Surface unrecognized operations explicitly rather than silently
24
+ // collapsing them into 'publish' — if NATS adds a new outbound API,
25
+ // this lets us see it in traces and fix the mapping deliberately.
26
+ return 'unknown'
27
+ }
28
+ }
29
+
30
+ module.exports = {
31
+ headersToTextMap,
32
+ getOperationName,
33
+ }
@@ -33,11 +33,13 @@ class NextPlugin extends ServerPlugin {
33
33
  'span.type': 'web',
34
34
  'span.kind': 'server',
35
35
  'http.method': req.method,
36
- ...(serviceSource === undefined ? {} : { [SVC_SRC_KEY]: serviceSource }),
36
+ ...(serviceSource === undefined ? undefined : { [SVC_SRC_KEY]: serviceSource }),
37
37
  },
38
38
  integrationName: this.constructor.id,
39
39
  })
40
40
 
41
+ this.stampIntegrationService(span, serviceName)
42
+
41
43
  analyticsSampler.sample(span, this.config.measured, true)
42
44
 
43
45
  return { ...store, span, req }
@@ -60,7 +62,7 @@ class NextPlugin extends ServerPlugin {
60
62
  if (!store) return
61
63
 
62
64
  const span = store.span
63
- const error = span.context()._tags.error
65
+ const error = span.context().getTag('error')
64
66
  const requestError = req.error || nextRequest.error
65
67
 
66
68
  if (requestError) {
@@ -97,7 +99,7 @@ class NextPlugin extends ServerPlugin {
97
99
  if (!req) return
98
100
 
99
101
  // Only use error page names if there's not already a name
100
- const current = span.context()._tags['next.page']
102
+ const current = span.context().getTag('next.page')
101
103
  const isErrorPage = errorPages.has(page)
102
104
 
103
105
  if (current && isErrorPage) {
@@ -8,6 +8,7 @@ const Sampler = require('../../dd-trace/src/sampler')
8
8
  const { MEASURED } = require('../../../ext/tags')
9
9
 
10
10
  const { DD_MAJOR } = require('../../../version')
11
+ const { ERROR_TYPE } = require('../../dd-trace/src/constants')
11
12
  const {
12
13
  convertBuffersToObjects,
13
14
  constructCompletionResponseFromStreamedChunks,
@@ -157,7 +158,7 @@ class OpenAiTracingPlugin extends TracingPlugin {
157
158
  const span = store?.span
158
159
  if (!span) return
159
160
 
160
- const error = !!span.context()._tags.error
161
+ const error = !!span.context().getTag('error')
161
162
 
162
163
  let headers, body, method, path
163
164
  if (!error) {
@@ -171,7 +172,7 @@ class OpenAiTracingPlugin extends TracingPlugin {
171
172
  headers = Object.fromEntries(headers)
172
173
  }
173
174
 
174
- const resource = span._spanContext._tags['resource.name']
175
+ const resource = span.context().getTag('resource.name')
175
176
  const normalizedMethodName = store.normalizedMethodName
176
177
 
177
178
  body = coerceResponseBody(body, normalizedMethodName)
@@ -211,6 +212,18 @@ class OpenAiTracingPlugin extends TracingPlugin {
211
212
  this.sendMetrics(headers, body, endpoint, span._duration, error, tags)
212
213
  }
213
214
 
215
+ error (ctx) {
216
+ const span = ctx.currentStore?.span
217
+ if (!span) return
218
+
219
+ super.error(ctx) // add normal error tag
220
+
221
+ const errorType = ctx.error?.type
222
+ if (errorType) {
223
+ span.setTag(ERROR_TYPE, errorType)
224
+ }
225
+ }
226
+
214
227
  sendMetrics (headers, body, endpoint, duration, error, spanTags) {
215
228
  const tags = [`error:${Number(!!error)}`]
216
229
  if (error) {