dd-trace 6.9.0 → 6.11.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 (175) hide show
  1. package/README.md +0 -17
  2. package/ci/vitest-no-worker-init-setup.mjs +112 -10
  3. package/index.d.ts +150 -3
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +14 -10
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  13. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  14. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  15. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  16. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  17. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  20. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +634 -0
  21. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  26. package/packages/datadog-instrumentations/src/jest.js +26 -18
  27. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  28. package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
  29. package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
  30. package/packages/datadog-instrumentations/src/mysql.js +45 -15
  31. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  32. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  33. package/packages/datadog-instrumentations/src/pg.js +116 -92
  34. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  35. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  36. package/packages/datadog-instrumentations/src/router.js +2 -0
  37. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
  38. package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
  39. package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
  40. package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
  41. package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  45. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -13
  46. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  47. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  48. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  49. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  50. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  51. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  52. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  53. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +82 -30
  54. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  55. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  56. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  57. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  58. package/packages/datadog-plugin-electron/src/index.js +2 -0
  59. package/packages/datadog-plugin-fs/src/index.js +1 -1
  60. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  61. package/packages/datadog-plugin-jest/src/index.js +11 -4
  62. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  63. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  64. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
  65. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  66. package/packages/datadog-plugin-mocha/src/index.js +606 -40
  67. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  68. package/packages/datadog-plugin-nats/src/index.js +1 -1
  69. package/packages/datadog-plugin-next/src/index.js +1 -19
  70. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  71. package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
  72. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  73. package/packages/datadog-plugin-pg/src/index.js +1 -0
  74. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  75. package/packages/datadog-plugin-vitest/src/index.js +14 -20
  76. package/packages/datadog-plugin-ws/src/util.js +2 -1
  77. package/packages/datadog-webpack/index.js +0 -11
  78. package/packages/dd-trace/index.js +0 -1
  79. package/packages/dd-trace/src/agent/info.js +6 -5
  80. package/packages/dd-trace/src/aiguard/client.js +100 -1
  81. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  82. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  83. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  84. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  85. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  86. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  87. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  88. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  89. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  90. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  91. package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
  92. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  93. package/packages/dd-trace/src/carrier.js +356 -0
  94. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  101. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  102. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  103. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  104. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  105. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  106. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  107. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -0
  109. package/packages/dd-trace/src/config/index.js +13 -2
  110. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  111. package/packages/dd-trace/src/config/supported-configurations.json +24 -0
  112. package/packages/dd-trace/src/datastreams/context.js +6 -1
  113. package/packages/dd-trace/src/datastreams/index.js +2 -0
  114. package/packages/dd-trace/src/datastreams/manager.js +14 -3
  115. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  116. package/packages/dd-trace/src/datastreams/processor.js +16 -13
  117. package/packages/dd-trace/src/datastreams/size.js +5 -0
  118. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  119. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  120. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  121. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  122. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  123. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  124. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  125. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  126. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  127. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  128. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  129. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  130. package/packages/dd-trace/src/llmobs/index.js +34 -2
  131. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  132. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  133. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  134. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  135. package/packages/dd-trace/src/llmobs/util.js +78 -0
  136. package/packages/dd-trace/src/noop/proxy.js +3 -4
  137. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  138. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  139. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  140. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  141. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  142. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  143. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  145. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  146. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  147. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  148. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  149. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  150. package/packages/dd-trace/src/plugin_manager.js +5 -3
  151. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  152. package/packages/dd-trace/src/plugins/index.js +1 -0
  153. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
  154. package/packages/dd-trace/src/plugins/util/test.js +93 -14
  155. package/packages/dd-trace/src/plugins/util/web.js +56 -2
  156. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  157. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  158. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  159. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  160. package/packages/dd-trace/src/proxy.js +38 -29
  161. package/packages/dd-trace/src/ritm.js +9 -2
  162. package/packages/dd-trace/src/serverless.js +37 -0
  163. package/packages/dd-trace/src/span_format.js +1 -0
  164. package/packages/dd-trace/src/span_stats.js +30 -3
  165. package/packages/dd-trace/src/storage-channels.js +86 -0
  166. package/packages/dd-trace/src/tracer.js +9 -2
  167. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  168. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  169. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  170. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  171. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  172. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  173. package/packages/dd-trace/src/feature-registry.js +0 -29
  174. package/packages/dd-trace/src/openfeature/register.js +0 -35
  175. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const NoopProxy = require('./noop/proxy')
4
- const { features } = require('./feature-registry')
5
4
  const DatadogTracer = require('./tracer')
6
5
  const getConfig = require('./config')
7
6
  const { getEnvironmentVariable } = require('./config/helper')
@@ -38,9 +37,9 @@ const OFFLINE_VALIDATION_EXPORTERS = new Set([
38
37
  'playwright_worker',
39
38
  'vitest_worker',
40
39
  ])
41
- const FEATURE_STATE_NOOP = 0
42
- const FEATURE_STATE_LAZY = 1
43
- const FEATURE_STATE_ACTIVE = 2
40
+ const OPENFEATURE_STATE_NOOP = 0
41
+ const OPENFEATURE_STATE_LAZY = 1
42
+ const OPENFEATURE_STATE_ACTIVE = 2
44
43
 
45
44
  class LazyModule {
46
45
  constructor (provider) {
@@ -91,8 +90,7 @@ function defineLazily (obj, property, getClass, ...args) {
91
90
  }
92
91
 
93
92
  class Tracer extends NoopProxy {
94
- /** @type {Record<string, number> | undefined} */
95
- #featureStates
93
+ #openfeatureState = OPENFEATURE_STATE_NOOP
96
94
 
97
95
  constructor () {
98
96
  super()
@@ -115,12 +113,9 @@ class Tracer extends NoopProxy {
115
113
  aiguard: new LazyModule(() => require('./aiguard')),
116
114
  iast: new LazyModule(() => require('./appsec/iast')),
117
115
  llmobs: new LazyModule(() => require('./llmobs')),
116
+ openfeature: new LazyModule(() => require('./openfeature')),
118
117
  rewriter: new LazyModule(() => require('./appsec/iast/taint-tracking/rewriter')),
119
118
  }
120
-
121
- for (const feature of Object.values(features)) {
122
- this._modules[feature.name] = new LazyModule(feature.factory)
123
- }
124
119
  }
125
120
 
126
121
  /**
@@ -213,9 +208,10 @@ class Tracer extends NoopProxy {
213
208
  DynamicInstrumentation.start(config, rc)
214
209
  }
215
210
 
216
- for (const feature of Object.values(features)) {
217
- feature.remoteConfig?.(rc, config, this)
218
- }
211
+ const openfeatureRemoteConfig = require('./openfeature/remote_config')
212
+ const subscribeOpenfeatureToRemoteConfig = config.featureFlags.DD_FEATURE_FLAGS_ENABLED &&
213
+ config.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE === 'remote_config'
214
+ openfeatureRemoteConfig.enable(rc, () => this.openfeature, subscribeOpenfeatureToRemoteConfig)
219
215
  }
220
216
 
221
217
  if (config.profiling.DD_PROFILING_ENABLED === 'true') {
@@ -233,6 +229,17 @@ class Tracer extends NoopProxy {
233
229
  }
234
230
  }
235
231
 
232
+ // Experimental: mirror the active trace ID, span ID and endpoint into
233
+ // an OTEP-4947 thread-local context record an out-of-process eBPF
234
+ // reader can consume.
235
+ if (config.DD_TRACE_OTEL_CTX_ENABLED) {
236
+ try {
237
+ require('./otel-thread-ctx').start()
238
+ } catch (e) {
239
+ log.error('Error starting OTEP-4947 thread context writer', e)
240
+ }
241
+ }
242
+
236
243
  // OTel logs/metrics pipelines must be initialized BEFORE runtimeMetrics.start so that
237
244
  // when the OTLP runtime metrics module calls metrics.getMeterProvider(), it gets the
238
245
  // real provider, otherwise instruments register on the noop provider and never export.
@@ -305,28 +312,32 @@ class Tracer extends NoopProxy {
305
312
  }
306
313
 
307
314
  /**
308
- * @param {(typeof features)[string]} feature
315
+ * `tracer.openfeature` is only reachable through this proxy. SSI itself (auto-injecting
316
+ * the tracer, resolving the vendored provider regardless of the customer's own
317
+ * `node_modules` layout) is unaffected -- that is what this PR fixes. The remaining gap is
318
+ * narrower: there is no `dd-trace-api` handoff for `openfeature` yet (see
319
+ * `packages/datadog-plugin-dd-trace-api`), so an application that calls into the tracer
320
+ * exclusively through the `dd-trace-api` shim -- instead of `require('dd-trace')` -- cannot
321
+ * reach the flagging provider through that shim.
322
+ *
309
323
  * @param {import('./config/config-base')} config
310
324
  */
311
- #enableFeature (feature, config) {
312
- const states = this.#featureStates ??= {}
313
- const state = states[feature.name] ?? FEATURE_STATE_NOOP
325
+ #enableOpenfeature (config) {
326
+ if (this.#openfeatureState !== OPENFEATURE_STATE_NOOP) return
327
+ this.#openfeatureState = OPENFEATURE_STATE_LAZY
314
328
 
315
- if (state === FEATURE_STATE_ACTIVE || state === FEATURE_STATE_LAZY) return
316
- states[feature.name] = FEATURE_STATE_LAZY
317
-
318
- Reflect.defineProperty(this, feature.name, {
329
+ Reflect.defineProperty(this, 'openfeature', {
319
330
  get: () => {
320
- const Provider = feature.provider()
321
- const provider = new Provider(this._tracer, config)
331
+ const FlaggingProvider = require('./openfeature/flagging_provider')
332
+ const provider = new FlaggingProvider(this._tracer, config)
322
333
 
323
- this._modules[feature.name].enable(config)
324
- Reflect.defineProperty(this, feature.name, {
334
+ this._modules.openfeature.enable(config)
335
+ Reflect.defineProperty(this, 'openfeature', {
325
336
  value: provider,
326
337
  configurable: true,
327
338
  enumerable: true,
328
339
  })
329
- states[feature.name] = FEATURE_STATE_ACTIVE
340
+ this.#openfeatureState = OPENFEATURE_STATE_ACTIVE
330
341
  return provider
331
342
  },
332
343
  configurable: true,
@@ -373,9 +384,7 @@ class Tracer extends NoopProxy {
373
384
  this._modules.llmobs.disable()
374
385
  }
375
386
 
376
- for (const feature of Object.values(features)) {
377
- if (feature.isEnabled(config)) this.#enableFeature(feature, config)
378
- }
387
+ if (config.featureFlags.DD_FEATURE_FLAGS_ENABLED) this.#enableOpenfeature(config)
379
388
 
380
389
  if (this._tracingInitialized) {
381
390
  this._tracer.configure(config)
@@ -20,6 +20,7 @@ const origRequire = Module.prototype.require
20
20
  module.exports = Hook
21
21
 
22
22
  let moduleHooks = Object.create(null)
23
+ let hookedModuleCount = 0
23
24
  let cache = Object.create(null)
24
25
  let patching = Object.create(null)
25
26
  let patchedRequire = null
@@ -64,6 +65,7 @@ function Hook (modules, options, onrequire) {
64
65
  hooks.push(onrequire)
65
66
  } else {
66
67
  moduleHooks[mod] = [onrequire]
68
+ hookedModuleCount++
67
69
  }
68
70
  }
69
71
  }
@@ -205,6 +207,7 @@ Hook.reset = function () {
205
207
  patching = Object.create(null)
206
208
  cache = Object.create(null)
207
209
  moduleHooks = Object.create(null)
210
+ hookedModuleCount = 0
208
211
  }
209
212
 
210
213
  function findProjectRoot (startDir) {
@@ -221,16 +224,20 @@ function findProjectRoot (startDir) {
221
224
 
222
225
  Hook.prototype.unhook = function () {
223
226
  for (const mod of this.modules) {
224
- const hooks = (moduleHooks[mod] || []).filter(hook => hook !== this.onrequire)
227
+ const registeredHooks = moduleHooks[mod]
228
+ if (registeredHooks === undefined) continue
229
+
230
+ const hooks = registeredHooks.filter(hook => hook !== this.onrequire)
225
231
 
226
232
  if (hooks.length > 0) {
227
233
  moduleHooks[mod] = hooks
228
234
  } else {
229
235
  delete moduleHooks[mod]
236
+ hookedModuleCount--
230
237
  }
231
238
  }
232
239
 
233
- if (Object.keys(moduleHooks).length === 0) {
240
+ if (hookedModuleCount === 0) {
234
241
  Hook.reset()
235
242
  }
236
243
  }
@@ -42,7 +42,44 @@ function isInServerlessEnvironment () {
42
42
  return inAWSLambda || isGCPFunction || isAzureFunction
43
43
  }
44
44
 
45
+ /**
46
+ * Gets tags describing the serverless platform where the tracer is running.
47
+ *
48
+ * @returns {string[]|undefined}
49
+ */
50
+ function getServerlessPlatformTags () {
51
+ if (getEnvironmentVariable('VERCEL') === '1') {
52
+ return getVercelPlatformTags()
53
+ }
54
+ }
55
+
56
+ /**
57
+ * @returns {string[]|undefined}
58
+ */
59
+ function getVercelPlatformTags () {
60
+ let tags
61
+ const projectId = getEnvironmentVariable('VERCEL_PROJECT_ID')
62
+ if (projectId) {
63
+ tags = ['vercel.project_id', projectId]
64
+ }
65
+
66
+ const environment = getEnvironmentVariable('VERCEL_ENV')
67
+ if (environment) {
68
+ tags ??= []
69
+ tags.push('vercel.environment', environment)
70
+ }
71
+
72
+ const region = getEnvironmentVariable('VERCEL_REGION')
73
+ if (region) {
74
+ tags ??= []
75
+ tags.push('vercel.region', region)
76
+ }
77
+
78
+ return tags
79
+ }
80
+
45
81
  module.exports = {
82
+ getServerlessPlatformTags,
46
83
  getIsGCPFunction,
47
84
  getIsAzureFunction,
48
85
  enableGCPPubSubPushSubscription,
@@ -450,3 +450,4 @@ function isUrl (obj) {
450
450
  }
451
451
 
452
452
  module.exports = format
453
+ module.exports.addError = extractError
@@ -14,8 +14,10 @@ const {
14
14
  GRPC_STATUS_CODE,
15
15
  } = require('../../../ext/tags')
16
16
  const { ORIGIN_KEY, TOP_LEVEL_KEY, SVC_SRC_KEY, GRPC_STATUS_NAMES } = require('./constants')
17
+ const id = require('./id')
17
18
 
18
19
  const GRPC_STATUS_CODE_MAP = Object.fromEntries(GRPC_STATUS_NAMES.map((name, i) => [name, String(i)]))
20
+ const ZERO_ID = id('0')
19
21
  const { version } = require('./pkg')
20
22
  const processTags = require('./process-tags')
21
23
 
@@ -150,9 +152,24 @@ class SpanAggKey {
150
152
  }
151
153
 
152
154
  class SpanBuckets extends Map {
155
+ #includeTraceRoot
156
+
157
+ /**
158
+ * @param {boolean} [includeTraceRoot]
159
+ */
160
+ constructor (includeTraceRoot = false) {
161
+ super()
162
+ this.#includeTraceRoot = includeTraceRoot
163
+ }
164
+
153
165
  forSpan (span) {
154
166
  const aggKey = new SpanAggKey(span)
155
- const key = aggKey.toString()
167
+ const baseKey = aggKey.toString()
168
+ const parentId = span.parent_id
169
+ if (this.#includeTraceRoot && parentId !== undefined && parentId !== null) {
170
+ aggKey.isTraceRoot = parentId.equals(ZERO_ID)
171
+ }
172
+ const key = this.#includeTraceRoot ? `${baseKey},${aggKey.isTraceRoot}` : baseKey
156
173
 
157
174
  if (!this.has(key)) {
158
175
  this.set(key, new SpanAggStats(aggKey))
@@ -163,9 +180,19 @@ class SpanBuckets extends Map {
163
180
  }
164
181
 
165
182
  class TimeBuckets extends Map {
183
+ #includeTraceRoot
184
+
185
+ /**
186
+ * @param {boolean} [includeTraceRoot]
187
+ */
188
+ constructor (includeTraceRoot = false) {
189
+ super()
190
+ this.#includeTraceRoot = includeTraceRoot
191
+ }
192
+
166
193
  forTime (time) {
167
194
  if (!this.has(time)) {
168
- this.set(time, new SpanBuckets())
195
+ this.set(time, new SpanBuckets(this.#includeTraceRoot))
169
196
  }
170
197
 
171
198
  return this.get(time)
@@ -192,7 +219,7 @@ class SpanStatsProcessor {
192
219
  const intervalMs = otlpExporter ? (flushIntervalMs ?? 10_000) : interval * 1e3
193
220
  this.interval = intervalMs / 1e3
194
221
  this.bucketSizeNs = intervalMs * 1e6
195
- this.buckets = new TimeBuckets()
222
+ this.buckets = new TimeBuckets(Boolean(otlpExporter))
196
223
  this.hostname = os.hostname()
197
224
  this.enabled = enabled
198
225
  this.otlpExporter = otlpExporter || null
@@ -0,0 +1,86 @@
1
+ 'use strict'
2
+
3
+ const dc = require('dc-polyfill')
4
+ const { storage } = require('../../datadog-core')
5
+
6
+ // Channels that surface tracer-storage events to interested consumers
7
+ // (currently the wall profiler and the OTEP-4947 thread context writer).
8
+ //
9
+ // dd-trace:storage:enter — fires when the active legacy-storage store
10
+ // changes. Published by the shimmer installed in ensureChannelsActivated().
11
+ // dd-trace:storage:before — fires from an async_hooks "before" callback in
12
+ // non-AsyncContextFrame mode. Used by the wall profiler to refresh the
13
+ // sample context at the start of each async resource callback. Has no
14
+ // publisher in ACF mode.
15
+ // dd-trace:span:finish, dd-trace:span:tags:update — published by the
16
+ // tracer core (opentracing/span.js). Re-exported here as a convenience
17
+ // so consumers have a single import path for storage-related channels.
18
+ const enterCh = dc.channel('dd-trace:storage:enter')
19
+ const beforeCh = dc.channel('dd-trace:storage:before')
20
+ const spanFinishCh = dc.channel('dd-trace:span:finish')
21
+ const tagsUpdateCh = dc.channel('dd-trace:span:tags:update')
22
+
23
+ function getActiveSpan () {
24
+ const store = storage('legacy').getStore()
25
+ return store && store.span
26
+ }
27
+
28
+ let channelsActivated = false
29
+ // Idempotent. The asyncContextFrameEnabled argument from the first caller
30
+ // wins; subsequent calls are no-ops regardless of their argument. In
31
+ // practice all callers observe the same global ACF state.
32
+ function ensureChannelsActivated (asyncContextFrameEnabled) {
33
+ if (channelsActivated) return
34
+
35
+ const shimmer = require('../../datadog-shimmer')
36
+
37
+ // We need to instrument enterWith() on the legacy storage — that's the storage
38
+ // carrying span data and the only one consumers of these channels care about.
39
+ const legacyStorage = storage('legacy')
40
+ let inRun = false
41
+ shimmer.wrap(legacyStorage, 'enterWith', function (original) {
42
+ return function (store) {
43
+ const retVal = original.call(this, store)
44
+ if (!inRun) enterCh.publish()
45
+ return retVal
46
+ }
47
+ })
48
+
49
+ // When not using AsyncContextFrame, we need additional instrumentation.
50
+ if (!asyncContextFrameEnabled) {
51
+ // We need async_hooks.createHook to create a "before" callback.
52
+ const { createHook } = require('async_hooks')
53
+ createHook({ before: () => beforeCh.publish() }).enable()
54
+
55
+ // In ACF-based implementation run() delegates to enterWith() so it doesn't
56
+ // need to be separately instrumented. in non-ACF implementation run()
57
+ // doesn't delegate to enterWith(), so separate instrumentation is necessary.
58
+ shimmer.wrap(legacyStorage, 'run', function (original) {
59
+ return function (store, callback, ...args) {
60
+ const wrappedCb = shimmer.wrapFunction(callback, cb => function (...args) {
61
+ inRun = false
62
+ enterCh.publish()
63
+ const retVal = cb.apply(this, args)
64
+ inRun = true
65
+ return retVal
66
+ })
67
+ inRun = true
68
+ const retVal = original.call(this, store, wrappedCb, ...args)
69
+ enterCh.publish()
70
+ inRun = false
71
+ return retVal
72
+ }
73
+ })
74
+ }
75
+
76
+ channelsActivated = true
77
+ }
78
+
79
+ module.exports = {
80
+ enterCh,
81
+ beforeCh,
82
+ spanFinishCh,
83
+ tagsUpdateCh,
84
+ getActiveSpan,
85
+ ensureChannelsActivated,
86
+ }
@@ -59,8 +59,15 @@ class DatadogTracer extends Tracer {
59
59
 
60
60
  // todo[piochelepiotr] These two methods are not related to the tracer, but to data streams monitoring.
61
61
  // They should be moved outside of the tracer in the future.
62
- setCheckpoint (edgeTags, span, payloadSize = 0) {
63
- return this._dataStreamsManager.setCheckpoint(edgeTags, span, payloadSize)
62
+ /**
63
+ * @param {string[]} edgeTags
64
+ * @param {import('./opentracing/span')|null} span
65
+ * @param {number} [payloadSize]
66
+ * @param {number} [pathwayContextSize] See `DataStreamsProcessor#setCheckpoint`.
67
+ * @returns {object|undefined}
68
+ */
69
+ setCheckpoint (edgeTags, span, payloadSize = 0, pathwayContextSize) {
70
+ return this._dataStreamsManager.setCheckpoint(edgeTags, span, payloadSize, pathwayContextSize)
64
71
  }
65
72
 
66
73
  decodeDataStreamsContext (carrier) {
@@ -18,6 +18,18 @@ function storeConfig (config) {
18
18
  ? (processTags.serialized || null)
19
19
  : null
20
20
 
21
+ // OTEP-4947 thread-context writer metadata, published as part of the
22
+ // OTel process context so an out-of-process reader can decode the
23
+ // on-wire record: the attribute key map (libdatadog prepends the
24
+ // implicit `datadog.local_root_span_id` at wire index 0, so we only
25
+ // supply our own additional keys), the schema-version string, and
26
+ // the V8 layout constants the reader needs. Gated on the same config
27
+ // flag that activates the writer; undefined when off or when
28
+ // @datadog/pprof isn't installed to provide the values.
29
+ const threadlocalMetadata = config.DD_TRACE_OTEL_CTX_ENABLED
30
+ ? require('./otel-thread-ctx').getThreadLocalMetadata()
31
+ : undefined
32
+
21
33
  const metadata = new processDiscovery.TracerMetadata(
22
34
  config.tags['runtime-id'],
23
35
  tracerVersion,
@@ -26,7 +38,8 @@ function storeConfig (config) {
26
38
  config.env || null,
27
39
  config.version || null,
28
40
  processTagsSerialized,
29
- containerId || null
41
+ containerId || null,
42
+ threadlocalMetadata
30
43
  )
31
44
 
32
45
  return processDiscovery.storeMetadata(metadata)
@@ -0,0 +1,132 @@
1
+ 'use strict'
2
+
3
+ // Per-span cache of "which tag bag from the started-spans chain identifies
4
+ // this span (or its nearest web-server ancestor) as a web-server span?"
5
+ // Populated lazily on first `getCachedWebTags(span)`, refreshed
6
+ // automatically when a `dd-trace:span:tags:update` event promotes a
7
+ // previously-empty answer for that span into a real value.
8
+ //
9
+ // Used by the wall profiler (endpoint-collection label on samples) and by
10
+ // the OTEP-4947 thread-context writer (endpoint attribute in the record);
11
+ // having a single cache means the parent-chain walk happens once per span
12
+ // no matter how many consumers ask.
13
+ //
14
+ // Consumers that want to react to late web-server-span discovery
15
+ // subscribe to `resolvedCh` — a diagnostics channel we publish on once
16
+ // per span at the moment its cached webTags transitions from undefined
17
+ // to a real value. Doing it via a channel (rather than exposing a
18
+ // stateful "did the transition happen?" query) means each consumer sees
19
+ // every transition exactly once, regardless of subscription order or
20
+ // how many other consumers are attached.
21
+ //
22
+ // `endpointResolvedCh` is the same idea one field over: published once per
23
+ // web-server span at the moment its endpoint name settles (see finalEndpoint in
24
+ // profiling/webspan-utils.js), i.e. when routing tags stop being interim
25
+ // placeholders. Consumers that must materialize the endpoint eagerly rather
26
+ // than read the tag bag at leisure need that moment; owning it here means the
27
+ // one `dd-trace:span:tags:update` subscription this module already holds serves
28
+ // them too, instead of each consumer adding its own subscriber to that hot
29
+ // channel and re-deriving finality itself.
30
+ //
31
+ // The module-level `dd-trace:span:tags:update` subscription that drives
32
+ // the transition promotion is installed lazily via `activate()` /
33
+ // `deactivate()`, ref-counted across consumers. When no consumer has
34
+ // activated the cache, `dd-trace:span:tags:update` stays subscriber-free
35
+ // so `DatadogSpan#setTag` / `addTags` skip the publish path entirely.
36
+ // Consumers MUST balance every activate() call with a matching
37
+ // deactivate() when they stop caring.
38
+
39
+ const dc = require('dc-polyfill')
40
+ const { finalEndpoint, isWebServerSpan, getStartedSpans } = require('./profiling/webspan-utils')
41
+
42
+ // Fields on the cache entry:
43
+ // resolved: true once the parent-chain walk has run.
44
+ // webTags: the resolved tag bag, or undefined when the walk came up
45
+ // empty (no web-server span found in the started-spans chain).
46
+ // endpointFinal: true once this span is a web-server span whose endpoint name
47
+ // has settled and endpointResolvedCh has been published for it.
48
+ const CachedSym = Symbol('WebTagsCache')
49
+
50
+ const tagsUpdateCh = dc.channel('dd-trace:span:tags:update')
51
+ const resolvedCh = dc.channel('dd-trace:web-tags:resolved')
52
+ const endpointResolvedCh = dc.channel('dd-trace:web-tags:endpoint-resolved')
53
+
54
+ function getCache (span) {
55
+ let cached = span[CachedSym]
56
+ if (cached === undefined) {
57
+ cached = {}
58
+ span[CachedSym] = cached
59
+ }
60
+ return cached
61
+ }
62
+
63
+ // Returns the web-server tag bag for this span or its nearest web-server
64
+ // ancestor in the started-spans chain, or undefined if none is a
65
+ // web-server span. Lazy: walks the parent chain on the first call, caches
66
+ // the result on the span.
67
+ function getCachedWebTags (span) {
68
+ const cached = getCache(span)
69
+ if (cached.resolved) return cached.webTags
70
+ const spanContext = span.context()
71
+ const tags = spanContext.getTags()
72
+ let webTags
73
+ if (isWebServerSpan(tags)) {
74
+ webTags = tags
75
+ } else {
76
+ const parentId = spanContext._parentId
77
+ const startedSpans = getStartedSpans(spanContext)
78
+ for (let i = startedSpans.length; --i >= 0;) {
79
+ const ispan = startedSpans[i]
80
+ if (ispan.context()._spanId === parentId) {
81
+ webTags = getCachedWebTags(ispan)
82
+ break
83
+ }
84
+ }
85
+ }
86
+ cached.webTags = webTags
87
+ cached.resolved = true
88
+ return webTags
89
+ }
90
+
91
+ // Own the tagsUpdate → transition promotion here. The dc subscribe
92
+ // happens lazily via activate(); each active consumer bumps a refcount,
93
+ // the last deactivate() removes the subscription. When idle, this module
94
+ // contributes no subscribers to `dd-trace:span:tags:update`, so
95
+ // `DatadogSpan#setTag` / `addTags` never enter the publish path on our
96
+ // behalf.
97
+ function onTagsUpdate (span) {
98
+ const cached = span[CachedSym]
99
+ if (cached === undefined || !cached.resolved) return
100
+ const tags = span.context().getTags()
101
+ if (cached.webTags === undefined) {
102
+ if (!isWebServerSpan(tags)) return
103
+ cached.webTags = tags
104
+ resolvedCh.publish(span)
105
+ }
106
+ // Endpoint finality is a property of the web-server span itself: for a
107
+ // descendant, cached.webTags is an ancestor's bag rather than these tags, and
108
+ // the ancestor's own update is what settles the endpoint. Skipped entirely
109
+ // when nobody is listening, keeping this the same early return it used to be
110
+ // for consumers that only care about web-tag discovery.
111
+ if (!endpointResolvedCh.hasSubscribers || cached.endpointFinal || cached.webTags !== tags) return
112
+ if (finalEndpoint(tags) === undefined) return
113
+ cached.endpointFinal = true
114
+ endpointResolvedCh.publish(span)
115
+ }
116
+
117
+ let activeCount = 0
118
+
119
+ function activate () {
120
+ if (activeCount++ === 0) {
121
+ tagsUpdateCh.subscribe(onTagsUpdate)
122
+ }
123
+ }
124
+
125
+ function deactivate () {
126
+ if (activeCount === 0) return
127
+ if (--activeCount === 0) {
128
+ tagsUpdateCh.unsubscribe(onTagsUpdate)
129
+ }
130
+ }
131
+
132
+ module.exports = { getCachedWebTags, resolvedCh, endpointResolvedCh, activate, deactivate }