dd-trace 5.121.0 → 5.122.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 (137) hide show
  1. package/README.md +0 -17
  2. package/index.d.ts +108 -3
  3. package/initialize.mjs +28 -17
  4. package/openfeature.js +2 -2
  5. package/package.json +11 -8
  6. package/packages/datadog-esbuild/index.js +0 -34
  7. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  8. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  9. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  10. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  11. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  12. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  13. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  14. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  15. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  16. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  17. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  18. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
  19. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  20. package/packages/datadog-instrumentations/src/jest.js +25 -17
  21. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  22. package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
  23. package/packages/datadog-instrumentations/src/mocha/utils.js +130 -17
  24. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  25. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  26. package/packages/datadog-instrumentations/src/router.js +2 -0
  27. package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
  28. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  29. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  30. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
  31. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  32. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  33. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  34. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  35. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  36. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  37. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
  38. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  39. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  40. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  41. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  42. package/packages/datadog-plugin-electron/src/index.js +2 -0
  43. package/packages/datadog-plugin-fs/src/index.js +1 -1
  44. package/packages/datadog-plugin-jest/src/index.js +6 -0
  45. package/packages/datadog-plugin-mocha/src/index.js +56 -1
  46. package/packages/datadog-plugin-nats/src/index.js +1 -1
  47. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  48. package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
  49. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  50. package/packages/datadog-plugin-vitest/src/index.js +6 -0
  51. package/packages/datadog-webpack/index.js +0 -11
  52. package/packages/dd-trace/index.js +0 -1
  53. package/packages/dd-trace/src/agent/info.js +6 -5
  54. package/packages/dd-trace/src/aiguard/client.js +100 -1
  55. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  56. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  57. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  58. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  59. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  60. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  61. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  62. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  63. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  64. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  65. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  66. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  67. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  68. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  69. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  70. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  71. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  72. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  73. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  74. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  75. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  76. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  77. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  78. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  79. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  80. package/packages/dd-trace/src/config/index.js +13 -2
  81. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  82. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  83. package/packages/dd-trace/src/datastreams/index.js +2 -0
  84. package/packages/dd-trace/src/datastreams/manager.js +9 -2
  85. package/packages/dd-trace/src/datastreams/processor.js +15 -13
  86. package/packages/dd-trace/src/datastreams/size.js +5 -0
  87. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  88. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  89. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  90. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  91. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  92. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  93. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  94. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  95. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  96. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  97. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  98. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  99. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  100. package/packages/dd-trace/src/noop/proxy.js +3 -4
  101. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  102. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  103. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  104. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  105. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  106. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  107. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  108. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  109. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  110. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  111. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  112. package/packages/dd-trace/src/plugin_manager.js +3 -3
  113. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  114. package/packages/dd-trace/src/plugins/index.js +1 -0
  115. package/packages/dd-trace/src/plugins/util/jest.js +4 -3
  116. package/packages/dd-trace/src/plugins/util/test.js +9 -9
  117. package/packages/dd-trace/src/plugins/util/web.js +3 -0
  118. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  119. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  120. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  121. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  122. package/packages/dd-trace/src/proxy.js +38 -29
  123. package/packages/dd-trace/src/ritm.js +9 -2
  124. package/packages/dd-trace/src/serverless.js +37 -0
  125. package/packages/dd-trace/src/span_format.js +1 -0
  126. package/packages/dd-trace/src/span_stats.js +30 -3
  127. package/packages/dd-trace/src/storage-channels.js +86 -0
  128. package/packages/dd-trace/src/tracer.js +9 -2
  129. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  130. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  131. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  132. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  133. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  134. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  135. package/packages/dd-trace/src/feature-registry.js +0 -29
  136. package/packages/dd-trace/src/openfeature/register.js +0 -35
  137. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
package/README.md CHANGED
@@ -93,23 +93,6 @@ Regardless of where you open the issue, someone at Datadog will try to help.
93
93
 
94
94
  If you would like to trace your bundled application then please read this page on [bundling and dd-trace](https://docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/dd_libraries/nodejs/#bundling). It includes information on how to use our ESBuild plugin and includes caveats for other bundlers.
95
95
 
96
- When using the experimental OpenFeature provider, file-traced deployments can force the optional provider and
97
- its dependencies into the output with a side-effect import before accessing `tracer.openfeature`:
98
-
99
- CommonJS:
100
-
101
- ```js
102
- require('dd-trace/openfeature')
103
- ```
104
-
105
- ES modules:
106
-
107
- ```js
108
- import 'dd-trace/openfeature.js'
109
- ```
110
-
111
- This is a fallback for build tools that do not recognize the provider's optional-require wrapper.
112
-
113
96
 
114
97
  ## Security Vulnerabilities
115
98
 
package/index.d.ts CHANGED
@@ -243,6 +243,7 @@ interface Plugins {
243
243
  "azure-functions": tracer.plugins.azure_functions;
244
244
  "azure-service-bus": tracer.plugins.azure_service_bus;
245
245
  "azure-durable-functions": tracer.plugins.azure_durable_functions
246
+ "browser-bunyan": tracer.plugins.browser_bunyan;
246
247
  "bullmq": tracer.plugins.bullmq;
247
248
  "bunyan": tracer.plugins.bunyan;
248
249
  "cassandra-driver": tracer.plugins.cassandra_driver;
@@ -2052,6 +2053,18 @@ declare namespace tracer {
2052
2053
  /** @hidden */
2053
2054
  interface Instrumentation extends Integration, Analyzable {}
2054
2055
 
2056
+ /** @hidden */
2057
+ interface LLMObsIntegration extends Integration {
2058
+ /**
2059
+ * Whether to capture LLM Observability spans for this integration. When set to `false`,
2060
+ * the integration keeps emitting APM spans and propagating trace context, but no LLM
2061
+ * Observability spans are produced. Useful when another integration already captures the
2062
+ * same operation and the payloads would otherwise be stored twice.
2063
+ * @default true
2064
+ */
2065
+ llmobs?: boolean;
2066
+ }
2067
+
2055
2068
  /** @hidden */
2056
2069
  interface DatabaseInstrumentation extends Instrumentation {
2057
2070
  /**
@@ -2461,11 +2474,13 @@ declare namespace tracer {
2461
2474
  interface azure_durable_functions extends Integration {}
2462
2475
 
2463
2476
  /**
2464
- * This plugin patches the [bunyan](https://github.com/trentm/node-bunyan)
2477
+ * This plugin patches the [browser-bunyan](https://github.com/philmander/browser-bunyan)
2465
2478
  * to automatically inject trace identifiers in log records when the
2466
2479
  * [logInjection](interfaces/traceroptions.html#logInjection) option is enabled
2467
2480
  * on the tracer.
2468
2481
  */
2482
+ interface browser_bunyan extends Integration {}
2483
+
2469
2484
  /**
2470
2485
  * This plugin automatically instruments the
2471
2486
  * [bullmq](https://github.com/npmjs/package/bullmq) message queue library.
@@ -2486,6 +2501,12 @@ declare namespace tracer {
2486
2501
  producerFilter?: (job: { name?: string; data?: unknown; opts?: unknown; queueName?: string }) => boolean;
2487
2502
  }
2488
2503
 
2504
+ /**
2505
+ * This plugin patches the [bunyan](https://github.com/trentm/node-bunyan)
2506
+ * to automatically inject trace identifiers in log records when the
2507
+ * [logInjection](interfaces/traceroptions.html#logInjection) option is enabled
2508
+ * on the tracer.
2509
+ */
2489
2510
  interface bunyan extends Integration {}
2490
2511
 
2491
2512
  /**
@@ -2974,7 +2995,7 @@ declare namespace tracer {
2974
2995
  * This plugin automatically instruments the
2975
2996
  * [langchain](https://js.langchain.com/) module
2976
2997
  */
2977
- interface langchain extends Instrumentation {}
2998
+ interface langchain extends Instrumentation, LLMObsIntegration {}
2978
2999
 
2979
3000
  /**
2980
3001
  * This plugin automatically instruments the
@@ -3016,7 +3037,7 @@ declare namespace tracer {
3016
3037
  * This plugin automatically instruments the
3017
3038
  * [modelcontextprotocol-sdk](https://github.com/npmjs/package/@modelcontextprotocol/sdk) library.
3018
3039
  */
3019
- interface modelcontextprotocol_sdk extends Instrumentation {}
3040
+ interface modelcontextprotocol_sdk extends Instrumentation, LLMObsIntegration {}
3020
3041
 
3021
3042
  /**
3022
3043
  * This plugin automatically instruments the
@@ -4049,6 +4070,79 @@ declare namespace tracer {
4049
4070
  url: string
4050
4071
  }
4051
4072
 
4073
+ type ExternalExperimentTimestamp = number | string | Date
4074
+
4075
+ interface StartExperimentDatasetOptions {
4076
+ /** Existing dataset id. When omitted, a placeholder dataset is created. */
4077
+ id?: string
4078
+ /** Dataset version to associate with the experiment. */
4079
+ version?: number
4080
+ /** Placeholder dataset name. Defaults to `<experiment name> dataset`. */
4081
+ name?: string
4082
+ /** Placeholder dataset description. */
4083
+ description?: string
4084
+ }
4085
+
4086
+ interface StartExperimentOptions {
4087
+ name: string
4088
+ description?: string
4089
+ /** Override the configured project name for this external experiment. */
4090
+ projectName?: string
4091
+ dataset?: StartExperimentDatasetOptions
4092
+ config?: Record<string, JSONType>
4093
+ metadata?: Record<string, JSONType>
4094
+ tags?: Record<string, string>
4095
+ }
4096
+
4097
+ interface ExternalExperimentSpanInput {
4098
+ name?: string
4099
+ input?: JSONType
4100
+ output?: JSONType
4101
+ expectedOutput?: JSONType
4102
+ metadata?: Record<string, JSONType>
4103
+ tags?: Record<string, string>
4104
+ startedAt?: ExternalExperimentTimestamp
4105
+ completedAt?: ExternalExperimentTimestamp
4106
+ durationMs?: number
4107
+ error?: string | Error | { type?: string, name?: string, message?: string, stack?: string }
4108
+ datasetRecordId?: string
4109
+ runId?: string
4110
+ runIteration?: number
4111
+ }
4112
+
4113
+ interface ExternalExperimentSpan {
4114
+ experimentId: string
4115
+ spanId: string
4116
+ traceId: string
4117
+ url: string | null
4118
+ }
4119
+
4120
+ interface ExternalExperimentMetric {
4121
+ label: string
4122
+ value?: JSONType
4123
+ error?: string | Error
4124
+ timestamp?: ExternalExperimentTimestamp
4125
+ tags?: Record<string, string>
4126
+ source?: string
4127
+ }
4128
+
4129
+ interface ExternalExperimentCloseOptions {
4130
+ status?: string
4131
+ error?: string | Error
4132
+ }
4133
+
4134
+ interface ExternalExperiment {
4135
+ experimentId (): string
4136
+ name (): string
4137
+ url (): string | null
4138
+ submitSpan (input?: ExternalExperimentSpanInput): Promise<ExternalExperimentSpan>
4139
+ submitEvaluationMetrics (
4140
+ span: { experimentId?: string, spanId: string, traceId: string },
4141
+ metrics: ExternalExperimentMetric[]
4142
+ ): Promise<void>
4143
+ close (options?: ExternalExperimentCloseOptions): Promise<void>
4144
+ }
4145
+
4052
4146
  interface DatasetPushResult {
4053
4147
  /** Number of records from this push that were confirmed with a record id. */
4054
4148
  pushedCount: number
@@ -4058,9 +4152,18 @@ declare namespace tracer {
4058
4152
 
4059
4153
  interface Dataset {
4060
4154
  addRecord (input: JSONType, expectedOutput?: JSONType, metadata?: Record<string, JSONType>): Dataset
4155
+ /** Update fields on an existing dataset record. */
4156
+ update (index: number, fields: {
4157
+ input?: JSONType
4158
+ expectedOutput?: JSONType
4159
+ metadata?: Record<string, JSONType>
4160
+ }): Dataset
4161
+ /** Delete an existing dataset record. */
4162
+ delete (index: number): Dataset
4061
4163
  /** Creates the dataset remotely if needed and pushes any unpushed records. */
4062
4164
  push (): Promise<DatasetPushResult>
4063
4165
  name (): string
4166
+ description (): string
4064
4167
  id (): string | null
4065
4168
  projectId (): string | null
4066
4169
  version (): number | null
@@ -4090,6 +4193,8 @@ declare namespace tracer {
4090
4193
  pullDataset (name: string, options?: PullDatasetOptions): Promise<Dataset>
4091
4194
  /** Build an experiment to run over a dataset. */
4092
4195
  experiment (options: ExperimentOptions): Experiment
4196
+ /** Start an externally-driven experiment. */
4197
+ startExperiment (options: StartExperimentOptions): Promise<ExternalExperiment>
4093
4198
  }
4094
4199
 
4095
4200
  interface LLMObservabilitySpan {
package/initialize.mjs CHANGED
@@ -16,19 +16,28 @@ import * as Module from 'module'
16
16
  import { types } from 'util'
17
17
  import { isMainThread } from 'worker_threads'
18
18
 
19
- // This file must support Node.js 12.0.0 syntax
19
+ // This file must support Node.js 14.13.1 syntax
20
20
 
21
- import {
22
- iitmExclusionRegExp,
23
- initialize as hookInitialize,
24
- load as hookLoad,
25
- resolve as hookResolve,
26
- } from './loader-hook.mjs?initialize'
21
+ const NODE_VERSION = process.versions.node
22
+
23
+ const brokenLoaders = NODE_VERSION.startsWith('18.0')
24
+ const isNode20LoaderWorker = !isMainThread && NODE_VERSION.startsWith('20.0')
25
+ const useDefaultLoader = brokenLoaders || isNode20LoaderWorker
26
+ // Avoid CommonJS in the loader worker on Node 20.0: https://github.com/nodejs/node/issues/47566
27
+ const loaderHook = useDefaultLoader ? undefined : await import('./loader-hook.mjs?initialize')
27
28
 
28
29
  let hasInsertedInit = false
29
30
  const initJsUrl = new URL('init.js', import.meta.url).href
30
31
  // `--loader` only reliably influences ESM entrypoints; for CJS apps use `--import`/`--require`.
31
32
 
33
+ // `globalPreload` is deprecated in favor of the `initialize` hook, but `initialize` only exists
34
+ // from Node 20.6 on, so it is the only way to run code in the application realm on Node 20.0.
35
+ export const globalPreload = isNode20LoaderWorker
36
+ ? () => `if (getBuiltin('module').createRequire(${JSON.stringify(import.meta.url)})('./init.js')) {
37
+ process.emitWarning('dd-trace cannot instrument ES modules on Node.js 20.0.0. Upgrade to Node.js 20.1.0 or newer.')
38
+ }`
39
+ : undefined
40
+
32
41
  /**
33
42
  * @param {{ source?: string|Buffer|Uint8Array, format?: string }} result
34
43
  * @param {unknown} _url_
@@ -63,26 +72,28 @@ function insertInit (result, _url_, context) {
63
72
  return result
64
73
  }
65
74
 
66
- const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number)
67
-
68
- const brokenLoaders = NODE_MAJOR === 18 && NODE_MINOR === 0
69
-
70
75
  // Node calls `initialize` only through `module.register`, never for `--loader`; without it
71
76
  // import-in-the-middle keeps its default matcher and proxies every application module. Loaders
72
77
  // before Node 18.19 share the application thread, where `loader-hook.mjs` builds no matcher.
73
- let needsHookInitialize = !isMainThread && !brokenLoaders
78
+ let needsHookInitialize = !isMainThread && !useDefaultLoader
74
79
 
75
- export async function load (url, context, nextLoad) {
80
+ /**
81
+ * @param {string} url
82
+ * @param {import('node:module').LoadHookContext & { isMain?: boolean }} context
83
+ * @param {Parameters<import('node:module').LoadHook>[2]} nextLoad
84
+ */
85
+ async function loadWithInit (url, context, nextLoad) {
76
86
  if (needsHookInitialize) {
77
87
  needsHookInitialize = false
78
- hookInitialize()
88
+ loaderHook.initialize()
79
89
  }
80
90
 
81
- const loadHook = (brokenLoaders || iitmExclusionRegExp.test(url)) ? nextLoad : hookLoad
82
- return insertInit(await loadHook(url, context, nextLoad), url, context)
91
+ const load = (useDefaultLoader || loaderHook.iitmExclusionRegExp.test(url)) ? nextLoad : loaderHook.load
92
+ return insertInit(await load(url, context, nextLoad), url, context)
83
93
  }
84
94
 
85
- export const resolve = brokenLoaders ? undefined : hookResolve
95
+ export const load = isNode20LoaderWorker ? undefined : loadWithInit
96
+ export const resolve = useDefaultLoader ? undefined : loaderHook.resolve
86
97
 
87
98
  if (isMainThread) {
88
99
  const require = Module.createRequire(import.meta.url)
package/openfeature.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use strict'
2
2
 
3
- // Static fallback for file tracers that do not recognize the optional-peer wrapper.
4
- require('@datadog/openfeature-node-server')
3
+ // Static fallback for file tracers that do not recognize the vendored provider's lazy require.
4
+ require('./vendor/dist/@datadog/openfeature-node-server')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.121.0",
3
+ "version": "5.122.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -49,7 +49,7 @@
49
49
  "test:esbuild:ci": "node scripts/c8-ci.js test:esbuild",
50
50
  "test:webpack": "mocha \"packages/datadog-webpack/test/**/*.spec.js\"",
51
51
  "test:webpack:ci": "node scripts/c8-ci.js test:webpack",
52
- "test:instrumentations": "mocha \"packages/datadog-instrumentations/test/@(${PLUGINS}).spec.js\"",
52
+ "test:instrumentations": "mocha --fail-zero \"packages/datadog-instrumentations/test/@(${PLUGINS}).spec.js\"",
53
53
  "test:instrumentations:ci": "yarn services && node scripts/c8-ci.js test:instrumentations",
54
54
  "test:instrumentations:misc": "mocha \"packages/datadog-instrumentations/test/*/**/*.spec.{js,mjs}\"",
55
55
  "test:instrumentations:misc:ci": "node scripts/c8-ci.js test:instrumentations:misc",
@@ -65,12 +65,14 @@
65
65
  "test:llmobs:plugins:ci": "yarn services && node scripts/c8-ci.js test:llmobs:plugins",
66
66
  "test:openfeature": "mocha \"packages/dd-trace/test/openfeature/**/*.spec.js\"",
67
67
  "test:openfeature:ci": "node scripts/c8-ci.js test:openfeature",
68
- "test:plugins": "node --expose-gc ./node_modules/mocha/bin/mocha.js \"packages/datadog-plugin-@(${PLUGINS})/test/**/${SPEC:-*}*.spec.js\"",
68
+ "test:plugins": "node --expose-gc ./node_modules/mocha/bin/mocha.js --fail-zero \"packages/datadog-plugin-@(${PLUGINS})/test/**/${SPEC:-*}*.spec.js\"",
69
69
  "test:plugins:ci": "yarn services && node scripts/c8-ci.js test:plugins",
70
70
  "test:plugins:ci:flaky": "yarn services && node scripts/c8-ci.js test:plugins --bail --retries 2",
71
71
  "test:plugins:upstream": "node ./packages/dd-trace/test/plugins/suite.js",
72
72
  "test:profiler": "node scripts/mocha-parallel-files.js --expose-gc --timeout 30000 -- \"packages/dd-trace/test/profiling/**/*.spec.js\"",
73
73
  "test:profiler:ci": "node scripts/c8-ci.js test:profiler",
74
+ "test:otel-thread-ctx": "mocha --timeout 60000 packages/dd-trace/test/otel-thread-ctx.spec.js",
75
+ "test:docker:otel-thread-ctx": "./scripts/docker/run-otel-thread-ctx-spec.sh",
74
76
  "test:integration": "mocha --timeout 60000 \"integration-tests/*.spec.js\"",
75
77
  "test:integration:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/*.spec.js\"",
76
78
  "test:integration:aiguard": "mocha --timeout 60000 \"integration-tests/aiguard/*.spec.js\"",
@@ -112,7 +114,7 @@
112
114
  "test:integration:plugins:coverage": "yarn services && node ./integration-tests/coverage/run-suite.js \"packages/datadog-plugin-@(${PLUGINS})/test/integration-test/**/${SPEC:-*}*.spec.js\"",
113
115
  "test:unit:plugins": "mocha \"packages/datadog-instrumentations/test/@(${PLUGINS}).spec.js\" \"packages/datadog-plugin-@(${PLUGINS})/test/**/*.spec.js\" --exclude \"packages/datadog-plugin-@(${PLUGINS})/test/integration-test/**/*.spec.js\"",
114
116
  "test:release": "mocha \"scripts/release/**/*.spec.js\"",
115
- "test:scripts": "mocha \"scripts/helpers/**/*.spec.js\" \"scripts/*.spec.mjs\"",
117
+ "test:scripts": "mocha \"benchmark/sirun/*.spec.js\" \"scripts/helpers/**/*.spec.js\" \"scripts/*.spec.mjs\"",
116
118
  "test:shimmer": "mocha \"packages/datadog-shimmer/test/**/*.spec.js\"",
117
119
  "test:shimmer:ci": "node scripts/c8-ci.js test:shimmer",
118
120
  "verify:workflow-job-names": "node scripts/verify-workflow-job-names.js",
@@ -180,13 +182,12 @@
180
182
  "opentracing": ">=0.14.7"
181
183
  },
182
184
  "optionalDependencies": {
183
- "@datadog/libdatadog": "0.9.4",
185
+ "@datadog/libdatadog": "0.12.1",
184
186
  "@datadog/native-appsec": "11.0.1",
185
187
  "@datadog/native-iast-taint-tracking": "4.2.0",
186
188
  "@datadog/native-metrics": "3.1.2",
187
- "@datadog/openfeature-node-server": "2.0.2",
188
- "@datadog/pprof": "5.17.0",
189
- "@datadog/wasm-js-rewriter": "5.0.1",
189
+ "@datadog/pprof": "5.18.0",
190
+ "@datadog/wasm-js-rewriter": "5.0.3",
190
191
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
191
192
  "@opentelemetry/api-logs": "<1.0.0",
192
193
  "oxc-parser": "^0.132.0"
@@ -197,6 +198,8 @@
197
198
  "@babel/helpers": "^8.0.0",
198
199
  "@eslint/eslintrc": "^3.3.5",
199
200
  "@eslint/js": "^10.0.1",
201
+ "@eslint/plugin-kit": "^0.7.2",
202
+ "@datadog/openfeature-node-server": "2.1.0",
200
203
  "@msgpack/msgpack": "^3.1.3",
201
204
  "@openfeature/core": "^1.11.0",
202
205
  "@openfeature/server-sdk": "~1.22.0",
@@ -8,11 +8,6 @@ const { pathToFileURL, fileURLToPath } = require('node:url')
8
8
  const instrumentations = require('../datadog-instrumentations/src/helpers/instrumentations')
9
9
  const extractPackageAndModulePath = require('../datadog-instrumentations/src/helpers/extract-package-and-module-path')
10
10
  const hooks = require('../datadog-instrumentations/src/helpers/hooks')
11
- const {
12
- OPTIONAL_PEER_FILTER,
13
- matchesOptionalPeerFile,
14
- rewriteOptionalPeerLoads,
15
- } = require('../datadog-instrumentations/src/helpers/optional-peer-bundler')
16
11
  const { processModule, isESMFile } = require('./src/utils')
17
12
  const log = require('./src/log')
18
13
 
@@ -133,19 +128,6 @@ module.exports.setup = function (build) {
133
128
  ${build.initialOptions.banner.js}`
134
129
  }
135
130
 
136
- // Keep the build from failing on the optional `@openfeature/core` peer of
137
- // `@openfeature/server-sdk` when it is not installed (#8635). esbuild follows the chain
138
- // whenever `@openfeature/server-sdk` is reachable -- the app importing it directly, or the
139
- // bundled provider when the optional peer is present -- so mark `@openfeature/core` external
140
- // when it is absent rather than erroring at bundle time.
141
- try {
142
- // eslint-disable-next-line n/no-unpublished-require
143
- require.resolve('@openfeature/core')
144
- } catch {
145
- build.initialOptions.external ??= []
146
- build.initialOptions.external.push('@openfeature/core')
147
- }
148
-
149
131
  const esmBuild = isESMBuild(build)
150
132
  if (
151
133
  esmBuild &&
@@ -182,22 +164,6 @@ ${build.initialOptions.banner.js}`
182
164
  log.warn('No git metadata available - skipping injection')
183
165
  }
184
166
 
185
- // Rewrite optional-peer loads so installed peers get bundled and survive the bundle being
186
- // relocated without them on disk (#8980). Registered before the generic onLoad so it wins for
187
- // these files. Absent peers stay opaque, so a build that does not opt into the feature does not
188
- // follow their dependency chain (#8635).
189
- build.onLoad({ filter: OPTIONAL_PEER_FILTER }, args => {
190
- const normalizedPath = args.path.replaceAll('\\', '/')
191
- if (!matchesOptionalPeerFile(normalizedPath)) return
192
-
193
- log.debug('INLINE: optional-peer loader applied to %s', normalizedPath)
194
- return {
195
- contents: rewriteOptionalPeerLoads(fs.readFileSync(args.path, 'utf8'), path.dirname(args.path)),
196
- loader: 'js',
197
- resolveDir: path.dirname(args.path),
198
- }
199
- })
200
-
201
167
  // first time is intercepted, proxy should be created, next time the original should be loaded
202
168
  const interceptedESMModules = new Set()
203
169
 
@@ -6,6 +6,144 @@ const { addHook } = require('./helpers/instrument')
6
6
 
7
7
  const anthropicTracingChannel = tracingChannel('apm:anthropic:request')
8
8
  const onStreamedChunkCh = channel('apm:anthropic:request:chunk')
9
+ const messagesBeforeChannel = channel('dd-trace:anthropic:messages:before')
10
+ const messagesAfterChannel = channel('dd-trace:anthropic:messages:after')
11
+
12
+ /**
13
+ * Publishes a provider-native lifecycle payload to a cancelable lifecycle channel.
14
+ *
15
+ * Subscribers push async work into `pending` synchronously during publication and
16
+ * abort `abortController` with an error before the pushed promise resolves to block.
17
+ *
18
+ * @param {object} channel
19
+ * @param {object} payload
20
+ * @returns {Promise<void>}
21
+ */
22
+ function publishLifecycle (channel, payload) {
23
+ const abortController = new AbortController()
24
+ const ctx = { ...payload, abortController, pending: [] }
25
+
26
+ channel.publish(ctx)
27
+
28
+ return Promise.all(ctx.pending).then(() => {
29
+ if (abortController.signal.aborted) {
30
+ throw abortController.signal.reason
31
+ }
32
+ })
33
+ }
34
+
35
+ /**
36
+ * @template T
37
+ * @param {Promise<T>} promise
38
+ * @param {Promise<void>|undefined} verdict
39
+ * @returns {Promise<T>}
40
+ */
41
+ function waitForVerdict (promise, verdict) {
42
+ if (!verdict) return promise
43
+
44
+ // The lifecycle verdict takes precedence over an earlier SDK rejection.
45
+ promise.catch(() => {})
46
+ return verdict.then(() => promise)
47
+ }
48
+
49
+ /**
50
+ * @param {Array<unknown>} args
51
+ * @returns {Array<unknown>|undefined}
52
+ */
53
+ function snapshotLifecycleArgs (args) {
54
+ const options = args[0]
55
+ if (!options || typeof options !== 'object') return
56
+
57
+ const input = { messages: options.messages }
58
+ if (options.system !== undefined) input.system = options.system
59
+
60
+ // Snapshot via JSON so AI Guard evaluates exactly what the SDK serializes and sends, immune to
61
+ // later caller mutation. If it can't serialize (e.g. circular), the SDK's own serialization would
62
+ // fail too, so skipping evaluation is safe — nothing reaches the model.
63
+ try {
64
+ const snapshot = [...args]
65
+ // eslint-disable-next-line unicorn/prefer-structured-clone
66
+ snapshot[0] = { ...options, ...JSON.parse(JSON.stringify(input)) }
67
+ return snapshot
68
+ } catch {
69
+ // Unserializable input — leave AI Guard inactive for this call (the SDK send would fail too).
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Finishes after output evaluation so a block propagates to the caller and span.
75
+ *
76
+ * @param {object} ctx
77
+ * @param {object} result
78
+ * @param {(body: object) => Promise<void>|undefined} getVerdict
79
+ * @param {object|string} [returnedResult]
80
+ * @returns {object|string|Promise<object|string>}
81
+ */
82
+ function finishResult (ctx, result, getVerdict, returnedResult = result) {
83
+ const verdict = getVerdict(result)
84
+ if (!verdict) {
85
+ finish(ctx, result)
86
+ return returnedResult
87
+ }
88
+
89
+ return verdict.then(() => {
90
+ finish(ctx, result)
91
+ return returnedResult
92
+ })
93
+ }
94
+
95
+ /**
96
+ * @param {object} ctx
97
+ * @param {Error} error
98
+ * @throws {Error} Always rethrows the supplied error.
99
+ */
100
+ function finishAndThrow (ctx, error) {
101
+ finish(ctx, null, error)
102
+ throw error
103
+ }
104
+
105
+ /**
106
+ * @param {object} response
107
+ * @param {'json'|'text'} method
108
+ * @param {object} ctx
109
+ * @param {(body: object) => Promise<void>|undefined} getVerdict
110
+ */
111
+ function wrapResponseReader (response, method, ctx, getVerdict) {
112
+ if (typeof response[method] !== 'function') return
113
+
114
+ shimmer.wrap(response, method, original => function (...args) {
115
+ return original.apply(this, args)
116
+ .then(body => {
117
+ if (method === 'json') return finishResult(ctx, body, getVerdict)
118
+
119
+ try {
120
+ return finishResult(ctx, JSON.parse(body), getVerdict, body)
121
+ } catch {
122
+ finish(ctx)
123
+ return body
124
+ }
125
+ })
126
+ .catch(error => finishAndThrow(ctx, error))
127
+ })
128
+ }
129
+
130
+ /**
131
+ * @param {object} response
132
+ * @param {object} ctx
133
+ * @param {(body: object) => Promise<void>|undefined} getVerdict
134
+ */
135
+ function wrapRawResponse (response, ctx, getVerdict) {
136
+ wrapResponseReader(response, 'json', ctx, getVerdict)
137
+ wrapResponseReader(response, 'text', ctx, getVerdict)
138
+
139
+ if (typeof response.clone !== 'function') return
140
+
141
+ shimmer.wrap(response, 'clone', clone => function (...args) {
142
+ const clonedResponse = clone.apply(this, args)
143
+ wrapRawResponse(clonedResponse, ctx, getVerdict)
144
+ return clonedResponse
145
+ })
146
+ }
9
147
 
10
148
  function wrapStreamIterator (iterator, ctx) {
11
149
  return function (...args) {
@@ -34,40 +172,94 @@ function wrapStreamIterator (iterator, ctx) {
34
172
 
35
173
  function wrapCreate (create) {
36
174
  return function (...args) {
37
- if (!anthropicTracingChannel.start.hasSubscribers) {
175
+ const options = args[0]
176
+ const stream = options?.stream
177
+
178
+ // Streaming is out of scope for lifecycle evaluation.
179
+ const lifecycleArgs = !stream &&
180
+ (messagesBeforeChannel.hasSubscribers || messagesAfterChannel.hasSubscribers)
181
+ ? snapshotLifecycleArgs(args)
182
+ : undefined
183
+
184
+ if (!anthropicTracingChannel.start.hasSubscribers && !lifecycleArgs) {
38
185
  return create.apply(this, args)
39
186
  }
40
187
 
41
- const options = args[0]
42
- const stream = options.stream
43
-
44
- const ctx = { options, resource: 'create', baseUrl: this._client?.baseURL }
188
+ const ctx = { options: lifecycleArgs?.[0] ?? options, resource: 'create', baseUrl: this._client?.baseURL }
45
189
 
46
190
  return anthropicTracingChannel.start.runStores(ctx, () => {
191
+ const parentSpan = lifecycleArgs ? ctx.currentStore?.span : undefined
192
+
47
193
  let apiPromise
48
194
  try {
49
- apiPromise = create.apply(this, args)
195
+ // Anthropic starts the request eagerly; the input verdict only gates result delivery.
196
+ apiPromise = create.apply(this, lifecycleArgs ?? args)
50
197
  } catch (error) {
51
198
  finish(ctx, null, error)
52
199
  throw error
53
200
  }
54
201
 
55
- shimmer.wrap(apiPromise, 'parse', parse => function (...args) {
56
- return parse.apply(this, args)
202
+ let beforeVerdict
203
+ let afterVerdict
204
+ let parseResult
205
+ let wrappedResponse
206
+
207
+ function getBeforeVerdict () {
208
+ if (!lifecycleArgs || beforeVerdict) return beforeVerdict
209
+ if (!messagesBeforeChannel.hasSubscribers) return
210
+
211
+ beforeVerdict = publishLifecycle(messagesBeforeChannel, { args: lifecycleArgs, parentSpan })
212
+ return beforeVerdict
213
+ }
214
+
215
+ /**
216
+ * @param {object|string} body
217
+ */
218
+ function getAfterVerdict (body) {
219
+ if (!lifecycleArgs || afterVerdict) return afterVerdict
220
+ if (!messagesAfterChannel.hasSubscribers) return
221
+
222
+ afterVerdict = publishLifecycle(messagesAfterChannel, { args: lifecycleArgs, body, parentSpan })
223
+ return afterVerdict
224
+ }
225
+
226
+ shimmer.wrap(apiPromise, 'parse', parse => function (...parseArgs) {
227
+ if (parseResult) return parseResult
228
+
229
+ const parsed = parse.apply(this, parseArgs)
230
+ parseResult = waitForVerdict(parsed, getBeforeVerdict())
57
231
  .then(response => {
58
232
  if (stream) {
59
233
  shimmer.wrap(response, Symbol.asyncIterator, iterator => wrapStreamIterator(iterator, ctx))
60
- } else {
61
- finish(ctx, response, null)
234
+ return response
62
235
  }
63
-
64
- return response
65
- }).catch(error => {
66
- finish(ctx, null, error)
67
- throw error
236
+ return finishResult(ctx, response, getAfterVerdict)
68
237
  })
238
+ .catch(error => finishAndThrow(ctx, error))
239
+
240
+ return parseResult
69
241
  })
70
242
 
243
+ if (typeof apiPromise.asResponse === 'function') {
244
+ shimmer.wrap(apiPromise, 'asResponse', origAsResponse => function (...asResponseArgs) {
245
+ return waitForVerdict(origAsResponse.apply(this, asResponseArgs), getBeforeVerdict())
246
+ .then(response => {
247
+ // Wrap the raw response's json()/text()/clone() readers to evaluate output on read,
248
+ // but only while someone still consumes the result and not twice for the same response.
249
+ const outputObserved = anthropicTracingChannel.start.hasSubscribers ||
250
+ afterVerdict || messagesAfterChannel.hasSubscribers
251
+ if (!stream && outputObserved && wrappedResponse !== response) {
252
+ wrappedResponse = response
253
+ wrapRawResponse(response, ctx, getAfterVerdict)
254
+ }
255
+
256
+ if (afterVerdict) return afterVerdict.then(() => response)
257
+ return response
258
+ })
259
+ .catch(error => finishAndThrow(ctx, error))
260
+ })
261
+ }
262
+
71
263
  anthropicTracingChannel.end.publish(ctx)
72
264
 
73
265
  return apiPromise
@@ -76,6 +268,8 @@ function wrapCreate (create) {
76
268
  }
77
269
 
78
270
  function finish (ctx, result, error) {
271
+ if (ctx.finished) return
272
+
79
273
  if (error) {
80
274
  ctx.error = error
81
275
  anthropicTracingChannel.error.publish(ctx)
@@ -83,6 +277,7 @@ function finish (ctx, result, error) {
83
277
 
84
278
  // streamed responses are handled and set separately
85
279
  ctx.result ??= result
280
+ ctx.finished = true
86
281
 
87
282
  anthropicTracingChannel.asyncEnd.publish(ctx)
88
283
  }