dd-trace 6.10.0 → 6.12.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 (173) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/README.md +0 -17
  3. package/index.d.ts +128 -6
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +27 -23
  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/cucumber.js +30 -15
  13. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  14. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  15. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  16. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  17. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  18. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  19. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
  20. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  21. package/packages/datadog-instrumentations/src/jest.js +25 -17
  22. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  23. package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +135 -19
  25. package/packages/datadog-instrumentations/src/playwright-reporter.js +247 -0
  26. package/packages/datadog-instrumentations/src/playwright.js +182 -35
  27. package/packages/datadog-instrumentations/src/router.js +2 -0
  28. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +6 -8
  29. package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
  30. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  31. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  32. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
  33. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  34. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  35. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  36. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  37. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  38. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  39. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
  40. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  41. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  42. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  43. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  44. package/packages/datadog-plugin-electron/src/index.js +2 -0
  45. package/packages/datadog-plugin-fs/src/index.js +1 -1
  46. package/packages/datadog-plugin-jest/src/index.js +6 -0
  47. package/packages/datadog-plugin-mocha/src/index.js +56 -1
  48. package/packages/datadog-plugin-nats/src/index.js +1 -1
  49. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  50. package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
  51. package/packages/datadog-plugin-playwright/src/index.js +46 -13
  52. package/packages/datadog-plugin-vitest/src/index.js +6 -0
  53. package/packages/datadog-webpack/index.js +0 -11
  54. package/packages/dd-trace/index.js +0 -1
  55. package/packages/dd-trace/src/agent/info.js +6 -5
  56. package/packages/dd-trace/src/aiguard/client.js +100 -1
  57. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  58. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  59. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  60. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  61. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  62. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  63. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  64. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  65. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  67. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  68. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  69. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  70. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  71. package/packages/dd-trace/src/carrier.js +107 -48
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  73. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  74. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +29 -3
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +27 -9
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  78. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +26 -8
  79. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  80. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  81. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  82. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  83. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  84. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -4
  85. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  86. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  87. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  88. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  89. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +10 -4
  90. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  91. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  92. package/packages/dd-trace/src/config/index.js +13 -2
  93. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  94. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  95. package/packages/dd-trace/src/datastreams/index.js +2 -0
  96. package/packages/dd-trace/src/datastreams/manager.js +9 -2
  97. package/packages/dd-trace/src/datastreams/processor.js +15 -13
  98. package/packages/dd-trace/src/datastreams/size.js +5 -0
  99. package/packages/dd-trace/src/debugger/constants.js +1 -0
  100. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  101. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -11
  102. package/packages/dd-trace/src/debugger/index.js +4 -2
  103. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  104. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  105. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  106. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  107. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  108. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  109. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  110. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  111. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  112. package/packages/dd-trace/src/llmobs/experiments/client.js +167 -5
  113. package/packages/dd-trace/src/llmobs/experiments/dataset.js +484 -92
  114. package/packages/dd-trace/src/llmobs/experiments/experiment.js +302 -29
  115. package/packages/dd-trace/src/llmobs/experiments/index.js +75 -64
  116. package/packages/dd-trace/src/llmobs/experiments/noop.js +135 -7
  117. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  118. package/packages/dd-trace/src/llmobs/experiments/util.js +101 -9
  119. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  120. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  121. package/packages/dd-trace/src/noop/proxy.js +3 -4
  122. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  123. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  124. package/packages/dd-trace/src/openfeature/index.js +7 -4
  125. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  126. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  127. package/packages/dd-trace/src/openfeature/writers/exposures.js +62 -14
  128. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  129. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  130. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  131. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  132. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  133. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  135. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  136. package/packages/dd-trace/src/opentracing/propagation/text_map.js +22 -13
  137. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  138. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  139. package/packages/dd-trace/src/plugin_manager.js +3 -3
  140. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  141. package/packages/dd-trace/src/plugins/index.js +1 -0
  142. package/packages/dd-trace/src/plugins/util/jest.js +4 -3
  143. package/packages/dd-trace/src/plugins/util/test.js +9 -9
  144. package/packages/dd-trace/src/plugins/util/web.js +3 -0
  145. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  146. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  148. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  149. package/packages/dd-trace/src/proxy.js +38 -29
  150. package/packages/dd-trace/src/ritm.js +9 -2
  151. package/packages/dd-trace/src/serverless.js +37 -0
  152. package/packages/dd-trace/src/span_format.js +1 -0
  153. package/packages/dd-trace/src/span_stats.js +30 -3
  154. package/packages/dd-trace/src/storage-channels.js +86 -0
  155. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  156. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  157. package/packages/dd-trace/src/tracer.js +9 -2
  158. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  159. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  160. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  161. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  162. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  163. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  164. package/vendor/dist/https-proxy-agent/index.js +4 -0
  165. package/vendor/dist/protobufjs/index.js +1 -1
  166. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  167. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  168. package/vendor/dist/proxy-from-env/index.js +1 -0
  169. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  170. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  171. package/packages/dd-trace/src/feature-registry.js +0 -29
  172. package/packages/dd-trace/src/openfeature/register.js +0 -35
  173. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -1,13 +1,18 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('dc-polyfill')
4
-
5
- const { storage } = require('../../../../datadog-core')
6
3
  const log = require('../../log')
7
4
  const runtimeMetrics = require('../../runtime_metrics')
8
5
  const telemetryMetrics = require('../../telemetry/metrics')
9
- const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require('../webspan-utils')
6
+ const { endpointNameFromTags, finalEndpoint, getStartedSpans } = require('../webspan-utils')
10
7
  const { SAMPLING_INTERVAL } = require('../constants')
8
+ const {
9
+ enterCh,
10
+ beforeCh,
11
+ spanFinishCh,
12
+ getActiveSpan,
13
+ ensureChannelsActivated,
14
+ } = require('../../storage-channels')
15
+ const webTagsCache = require('../../web-tags-cache')
11
16
 
12
17
  const {
13
18
  END_TIMESTAMP_LABEL,
@@ -21,21 +26,12 @@ const TRACE_ENDPOINT_LABEL = 'trace endpoint'
21
26
 
22
27
  /** @typedef {import('../../config/config-base')} TracerConfig */
23
28
 
24
- let beforeCh
25
- const enterCh = dc.channel('dd-trace:storage:enter')
26
- const spanFinishCh = dc.channel('dd-trace:span:finish')
27
- const tagsUpdateCh = dc.channel('dd-trace:span:tags:update')
28
29
  const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
29
30
 
30
31
  const ProfilingContext = Symbol('NativeWallProfiler.ProfilingContext')
31
32
 
32
33
  let kSampleCount
33
34
 
34
- function getActiveSpan () {
35
- const store = storage('legacy').getStore()
36
- return store && store.span
37
- }
38
-
39
35
  function toBigInt (spanId) {
40
36
  return spanId !== null && typeof spanId === 'object' ? spanId.toBigInt() : spanId
41
37
  }
@@ -53,58 +49,13 @@ function updateContext (context) {
53
49
  }
54
50
  if (context.webTags !== undefined && context.endpoint === undefined) {
55
51
  // endpoint may not be determined yet, but keep it as fallback
56
- // if tags are not available anymore during serialization
57
- context.endpoint = endpointNameFromTags(context.webTags)
58
- }
59
- }
60
-
61
- let channelsActivated = false
62
- function ensureChannelsActivated (asyncContextFrameEnabled) {
63
- if (channelsActivated) return
64
-
65
- const shimmer = require('../../../../datadog-shimmer')
66
-
67
- // We need to instrument enterWith() on the legacy storage — that's the storage
68
- // carrying span data and the only one the profiler cares about.
69
- const legacyStorage = storage('legacy')
70
- let inRun = false
71
- shimmer.wrap(legacyStorage, 'enterWith', function (original) {
72
- return function (store) {
73
- const retVal = original.call(this, store)
74
- if (!inRun) enterCh.publish()
75
- return retVal
76
- }
77
- })
78
-
79
- // When not using AsyncContextFrame, we need additional instrumentation.
80
- if (!asyncContextFrameEnabled) {
81
- // We need async_hooks.createHook to create a "before" callback.
82
- const { createHook } = require('async_hooks')
83
- beforeCh = dc.channel('dd-trace:storage:before')
84
- createHook({ before: () => beforeCh.publish() }).enable()
85
-
86
- // In ACF-based implementation run() delegates to enterWith() so it doesn't
87
- // need to be separately instrumented. in non-ACF implementation run()
88
- // doesn't delegate to enterWith(), so separate instrumentation is necessary.
89
- shimmer.wrap(legacyStorage, 'run', function (original) {
90
- return function (store, callback, ...args) {
91
- const wrappedCb = shimmer.wrapFunction(callback, cb => function (...args) {
92
- inRun = false
93
- enterCh.publish()
94
- const retVal = cb.apply(this, args)
95
- inRun = true
96
- return retVal
97
- })
98
- inRun = true
99
- const retVal = original.call(this, store, wrappedCb, ...args)
100
- enterCh.publish()
101
- inRun = false
102
- return retVal
103
- }
104
- })
52
+ // if tags are not available anymore during serialization. Only snapshot a
53
+ // settled value: routing tags land over the course of the request, and this
54
+ // field is never refreshed once set, so capturing an interim placeholder
55
+ // (a bare `GET`) would pin the fallback to it. Staying undefined until then
56
+ // just means the next sample rotation tries again.
57
+ context.endpoint = finalEndpoint(context.webTags)
105
58
  }
106
-
107
- channelsActivated = true
108
59
  }
109
60
 
110
61
  class NativeWallProfiler {
@@ -216,7 +167,13 @@ class NativeWallProfiler {
216
167
  enterCh.subscribe(this.#boundEnter)
217
168
  spanFinishCh.subscribe(this.#boundSpanFinished)
218
169
  if (this.#endpointCollectionEnabled) {
219
- tagsUpdateCh.subscribe(this.#boundSpanTagsUpdated)
170
+ // Web-tags cache publishes once per span at the moment its
171
+ // walk-result transitions from undefined to a real value —
172
+ // exactly when we need to refresh the ProfilingContext snapshot.
173
+ webTagsCache.resolvedCh.subscribe(this.#boundSpanTagsUpdated)
174
+ // Turn on the cache's own tagsUpdate subscription — it's
175
+ // ref-counted, so this composes with any other active consumer.
176
+ webTagsCache.activate()
220
177
  }
221
178
  }
222
179
  }
@@ -299,32 +256,20 @@ class NativeWallProfiler {
299
256
  let profilingContext = span[ProfilingContext]
300
257
  if (profilingContext === undefined) {
301
258
  const context = span.context()
302
- const startedSpans = getStartedSpans(context)
303
259
 
304
260
  let spanId
305
261
  let rootSpanId
306
262
  if (this.#codeHotspotsEnabled) {
263
+ const startedSpans = getStartedSpans(context)
307
264
  spanId = context._spanId
308
265
  rootSpanId = startedSpans.length ? startedSpans[0].context()._spanId : context._spanId
309
266
  }
310
267
 
311
- let webTags
312
- if (this.#endpointCollectionEnabled) {
313
- const tags = context.getTags()
314
- if (isWebServerSpan(tags)) {
315
- webTags = tags
316
- } else {
317
- // Get parent's context's web tags
318
- const parentId = context._parentId
319
- for (let i = startedSpans.length; --i >= 0;) {
320
- const ispan = startedSpans[i]
321
- if (ispan.context()._spanId === parentId) {
322
- webTags = this.#getProfilingContext(ispan).webTags
323
- break
324
- }
325
- }
326
- }
327
- }
268
+ // webTags is snapshotted into the sample context at getProfilingContext
269
+ // time; if the answer turns out to be undefined and the span later gets
270
+ // web-server tags, #spanTagsUpdated refreshes this field via the shared
271
+ // cache (see web-tags-cache.js).
272
+ const webTags = this.#endpointCollectionEnabled ? webTagsCache.getCachedWebTags(span) : undefined
328
273
 
329
274
  profilingContext = { spanId, rootSpanId, webTags }
330
275
  span[ProfilingContext] = profilingContext
@@ -345,14 +290,15 @@ class NativeWallProfiler {
345
290
  }
346
291
  }
347
292
 
293
+ // Invoked (via webTagsCache.resolvedCh) once per span at the moment the
294
+ // shared cache promotes a previously-undefined webTags answer into a
295
+ // real value. Refresh the ProfilingContext snapshot so future samples
296
+ // pick it up.
348
297
  #spanTagsUpdated (span) {
349
298
  if (!this.#started) return
350
299
  const profilingContext = span[ProfilingContext]
351
- if (profilingContext === undefined || profilingContext.webTags !== undefined) return
352
- const tags = span.context().getTags()
353
- if (isWebServerSpan(tags)) {
354
- profilingContext.webTags = tags
355
- }
300
+ if (profilingContext === undefined) return
301
+ profilingContext.webTags = webTagsCache.getCachedWebTags(span)
356
302
  }
357
303
 
358
304
  #reportV8bug (maybeBug) {
@@ -401,7 +347,8 @@ class NativeWallProfiler {
401
347
  enterCh.unsubscribe(this.#boundEnter)
402
348
  spanFinishCh.unsubscribe(this.#boundSpanFinished)
403
349
  if (this.#endpointCollectionEnabled) {
404
- tagsUpdateCh.unsubscribe(this.#boundSpanTagsUpdated)
350
+ webTagsCache.resolvedCh.unsubscribe(this.#boundSpanTagsUpdated)
351
+ webTagsCache.deactivate()
405
352
  }
406
353
  this.#profilerState = undefined
407
354
  }
@@ -470,7 +417,7 @@ class NativeWallProfiler {
470
417
  if (rootSpanId !== undefined) {
471
418
  labels[LOCAL_ROOT_SPAN_ID_LABEL] = toBigInt(rootSpanId)
472
419
  }
473
- if (webTags !== undefined && Object.keys(webTags).length !== 0) {
420
+ if (webTags !== undefined) {
474
421
  labels[TRACE_ENDPOINT_LABEL] = endpointNameFromTags(webTags)
475
422
  } else if (endpoint) {
476
423
  // fallback to endpoint computed when sample was taken
@@ -14,6 +14,50 @@ function endpointNameFromTags (tags) {
14
14
  ].filter(Boolean).join(' ')
15
15
  }
16
16
 
17
+ // The endpoint name a web-server span's tag bag yields changes over the span's
18
+ // lifetime: plugins set `http.method` when the request arrives and only add
19
+ // `http.route` (and often `resource.name`) once framework routing has resolved
20
+ // the URL. Returns the endpoint name once its value looks stable, or undefined
21
+ // while it is still an interim placeholder.
22
+ //
23
+ // Consumers that read the tag bag lazily (the wall profiler resolves endpoint
24
+ // labels at serialization time) see the settled value for free and only need
25
+ // this for fallbacks captured mid-request. Consumers that have to materialize
26
+ // the value early — the OTEP-4947 thread-context writer writes it into a record
27
+ // an out-of-process reader samples at any moment — need it to decide when
28
+ // publishing is safe.
29
+ //
30
+ // The test is on the computed value rather than on which tags are present,
31
+ // because endpointNameFromTags prefers `resource.name` over
32
+ // `http.method` + `http.route` and some plugins seed `resource.name` with the
33
+ // bare request method before routing resolves: datadog-plugin-next starts its
34
+ // request span with `resource.name: req.method` and only replaces it with
35
+ // `${req.method} ${page}` once the page is known. A value equal to the bare
36
+ // method is therefore a placeholder no matter which tags produced it.
37
+ function finalEndpoint (tags) {
38
+ if (tags == null) return
39
+ // Presence gate first: nothing but these two can contribute routing
40
+ // information, and checking them keeps the common not-yet-routed case free of
41
+ // the string building inside endpointNameFromTags.
42
+ if (tags[RESOURCE_NAME] == null && tags[HTTP_ROUTE] == null) return
43
+ const endpoint = endpointNameFromTags(tags)
44
+ if (!endpoint || endpoint === tags[HTTP_METHOD]) return
45
+ return endpoint
46
+ }
47
+
48
+ // The trace's started-spans list, whose first entry is the local root span by
49
+ // repo-wide convention — priority_sampler, span_format, the wall profiler's
50
+ // local-root-span-id label, the event plugins and the OTEP-4947 writer all read
51
+ // it that way, and web-tags-cache walks it to find a span's parent.
52
+ //
53
+ // Partial flush weakens that convention for every one of them alike:
54
+ // span_processor's _erase() replaces the list with the still-active spans only,
55
+ // so once a trace crosses DD_TRACE_PARTIAL_FLUSH_MIN_SPANS a local root that has
56
+ // already finished drops out, and the first entry becomes merely the oldest span
57
+ // still running — while a finished web-server ancestor stops being reachable for
58
+ // the parent-chain walk. Correcting it needs a local-root reference the tracer
59
+ // core keeps across flushes; until there is one, consumers share the same skew
60
+ // on large traces rather than each compensating differently.
17
61
  function getStartedSpans (context) {
18
62
  return context._trace.started
19
63
  }
@@ -21,5 +65,6 @@ function getStartedSpans (context) {
21
65
  module.exports = {
22
66
  isWebServerSpan,
23
67
  endpointNameFromTags,
68
+ finalEndpoint,
24
69
  getStartedSpans,
25
70
  }
@@ -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
+ }