dd-trace 5.110.0 → 5.112.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 (164) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/README.md +13 -8
  3. package/ci/init.js +21 -2
  4. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  5. package/ext/tags.js +2 -0
  6. package/index.d.ts +34 -1
  7. package/initialize.mjs +5 -6
  8. package/loader-hook.mjs +76 -55
  9. package/package.json +37 -34
  10. package/packages/datadog-instrumentations/src/ai.js +45 -0
  11. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  12. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  13. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  14. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  15. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  16. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  17. package/packages/datadog-instrumentations/src/fs.js +8 -6
  18. package/packages/datadog-instrumentations/src/graphql.js +26 -495
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  25. package/packages/datadog-instrumentations/src/jest.js +713 -63
  26. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  27. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  28. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  29. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  30. package/packages/datadog-instrumentations/src/mongoose.js +13 -2
  31. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  32. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1297 -0
  33. package/packages/datadog-instrumentations/src/vitest-main.js +1594 -0
  34. package/packages/datadog-instrumentations/src/vitest-util.js +254 -0
  35. package/packages/datadog-instrumentations/src/vitest-worker.js +823 -0
  36. package/packages/datadog-instrumentations/src/vitest.js +11 -1855
  37. package/packages/datadog-plugin-ai/src/index.js +1 -1
  38. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  39. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  40. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +19 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  43. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  44. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  45. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  46. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  47. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  48. package/packages/datadog-plugin-cucumber/src/index.js +15 -24
  49. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +360 -14
  50. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  51. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  52. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  53. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  54. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  55. package/packages/datadog-plugin-graphql/src/execute.js +639 -12
  56. package/packages/datadog-plugin-graphql/src/index.js +37 -9
  57. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  58. package/packages/datadog-plugin-graphql/src/utils.js +9 -2
  59. package/packages/datadog-plugin-graphql/src/validate.js +17 -6
  60. package/packages/datadog-plugin-http/src/index.js +6 -8
  61. package/packages/datadog-plugin-jest/src/index.js +31 -15
  62. package/packages/datadog-plugin-mocha/src/index.js +40 -15
  63. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  64. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  65. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  66. package/packages/datadog-plugin-openai/src/services.js +2 -2
  67. package/packages/datadog-plugin-playwright/src/index.js +4 -3
  68. package/packages/datadog-plugin-vitest/src/index.js +120 -72
  69. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  70. package/packages/dd-trace/src/aiguard/index.js +9 -14
  71. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  72. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  73. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  74. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  75. package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
  76. package/packages/dd-trace/src/appsec/channels.js +3 -1
  77. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  78. package/packages/dd-trace/src/appsec/graphql.js +14 -11
  79. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +23 -23
  80. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  81. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  82. package/packages/dd-trace/src/appsec/index.js +10 -1
  83. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  84. package/packages/dd-trace/src/appsec/reporter.js +49 -10
  85. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
  86. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  87. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  88. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  89. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  90. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  91. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +5 -2
  92. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  93. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  94. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +6 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  96. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +130 -20
  97. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  98. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  99. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  100. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  101. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  102. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  103. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  104. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  105. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  106. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  107. package/packages/dd-trace/src/config/defaults.js +3 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +71 -57
  109. package/packages/dd-trace/src/config/helper.js +1 -0
  110. package/packages/dd-trace/src/config/index.js +35 -22
  111. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  112. package/packages/dd-trace/src/config/remote_config.js +1 -1
  113. package/packages/dd-trace/src/config/supported-configurations.json +127 -56
  114. package/packages/dd-trace/src/constants.js +7 -0
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  116. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  117. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  118. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  119. package/packages/dd-trace/src/llmobs/index.js +1 -1
  120. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  121. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  122. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  123. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  124. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  125. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  126. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  127. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  128. package/packages/dd-trace/src/llmobs/sdk.js +3 -3
  129. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  130. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  131. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  132. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  133. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  135. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
  136. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  137. package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
  138. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  139. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  140. package/packages/dd-trace/src/plugin_manager.js +23 -7
  141. package/packages/dd-trace/src/plugins/ci_plugin.js +159 -10
  142. package/packages/dd-trace/src/plugins/index.js +2 -0
  143. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  144. package/packages/dd-trace/src/plugins/util/test.js +15 -9
  145. package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
  146. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  147. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
  148. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  149. package/packages/dd-trace/src/proxy.js +8 -8
  150. package/packages/dd-trace/src/span_processor.js +5 -6
  151. package/packages/dd-trace/src/span_stats.js +96 -78
  152. package/packages/dd-trace/src/startup-log.js +2 -1
  153. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  154. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  155. package/packages/dd-trace/src/telemetry/index.js +2 -2
  156. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  157. package/packages/dd-trace/src/telemetry/send-data.js +8 -8
  158. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  159. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  160. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  161. package/vendor/dist/protobufjs/index.js +1 -1
  162. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  163. package/vendor/dist/shell-quote/index.js +1 -1
  164. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
@@ -49,7 +49,7 @@ class LLMObs extends NoopLLMObs {
49
49
  }
50
50
 
51
51
  get enabled () {
52
- return this._config.llmobs.enabled ?? false
52
+ return this._config.llmobs.DD_LLMOBS_ENABLED ?? false
53
53
  }
54
54
 
55
55
  enable (options = {}) {
@@ -75,7 +75,7 @@ class LLMObs extends NoopLLMObs {
75
75
  }
76
76
 
77
77
  // TODO: These configs should be passed through directly at construction time instead.
78
- this._config.llmobs.enabled = true
78
+ this._config.llmobs.DD_LLMOBS_ENABLED = true
79
79
  this._config.llmobs.mlApp = options.mlApp
80
80
  this._config.llmobs.agentlessEnabled = options.agentlessEnabled
81
81
 
@@ -96,7 +96,7 @@ class LLMObs extends NoopLLMObs {
96
96
 
97
97
  logger.debug('Disabling LLMObs')
98
98
 
99
- this._config.llmobs.enabled = false
99
+ this._config.llmobs.DD_LLMOBS_ENABLED = false
100
100
 
101
101
  // disable writers and channel subscribers
102
102
  this._llmobsModule.disable()
@@ -83,7 +83,7 @@ class LLMObsSpanProcessor {
83
83
 
84
84
  // TODO: instead of relying on the tagger's weakmap registry, can we use some namespaced storage correlation?
85
85
  process (span) {
86
- if (!this.#config.llmobs.enabled) return
86
+ if (!this.#config.llmobs.DD_LLMOBS_ENABLED) return
87
87
  // if the span is not in our private tagger map, it is not an llmobs span
88
88
  if (!LLMObsTagger.tagMap.has(span)) return
89
89
 
@@ -105,7 +105,7 @@ class LLMObsTagger {
105
105
  integration,
106
106
  _decorator,
107
107
  } = {}) {
108
- if (!this.#config.llmobs.enabled) return
108
+ if (!this.#config.llmobs.DD_LLMOBS_ENABLED) return
109
109
  if (!kind) return // do not register it in the map if it doesn't have an llmobs span kind
110
110
 
111
111
  const spanMlApp =
@@ -734,7 +734,7 @@ class LLMObsTagger {
734
734
  }
735
735
 
736
736
  _register (span) {
737
- if (!this.#config.llmobs.enabled) return
737
+ if (!this.#config.llmobs.DD_LLMOBS_ENABLED) return
738
738
  if (registry.has(span)) {
739
739
  this.#handleFailure(`LLMObs Span "${span._name}" already registered.`)
740
740
  return
@@ -744,7 +744,7 @@ class LLMObsTagger {
744
744
  }
745
745
 
746
746
  _setTag (span, key, value) {
747
- if (!this.#config.llmobs.enabled) return
747
+ if (!this.#config.llmobs.DD_LLMOBS_ENABLED) return
748
748
  if (!registry.has(span)) {
749
749
  this.#handleFailure(`Span "${span._name}" must be an LLMObs generated span.`)
750
750
  return
@@ -229,7 +229,7 @@ class BaseLLMObsWriter {
229
229
  }
230
230
 
231
231
  if (this._agentless) {
232
- options.headers['DD-API-KEY'] = this._config.apiKey || ''
232
+ options.headers['DD-API-KEY'] = this._config.DD_API_KEY || ''
233
233
  } else {
234
234
  options.headers[EVP_SUBDOMAIN_HEADER_NAME] = this._intake
235
235
  }
@@ -2,6 +2,14 @@
2
2
 
3
3
  const os = require('os')
4
4
 
5
+ const { metrics } = require('@opentelemetry/api')
6
+
7
+ const { VERSION } = require('../../../../../version')
8
+ const processTags = require('../../process-tags')
9
+ const MeterProvider = require('./meter_provider')
10
+ const PeriodicMetricReader = require('./periodic_metric_reader')
11
+ const OtlpHttpMetricExporter = require('./otlp_http_metric_exporter')
12
+
5
13
  /**
6
14
  * @typedef {import('../../config')} Config
7
15
  */
@@ -28,11 +36,6 @@ const os = require('os')
28
36
  * @package
29
37
  */
30
38
 
31
- const { metrics } = require('@opentelemetry/api')
32
- const MeterProvider = require('./meter_provider')
33
- const PeriodicMetricReader = require('./periodic_metric_reader')
34
- const OtlpHttpMetricExporter = require('./otlp_http_metric_exporter')
35
-
36
39
  /**
37
40
  * Initializes OpenTelemetry Metrics support
38
41
  * @param {import('../../config/config-base')} config - Tracer configuration instance
@@ -75,7 +78,53 @@ function initializeOpenTelemetryMetrics (config) {
75
78
  metrics.setGlobalMeterProvider(meterProvider)
76
79
  }
77
80
 
81
+ function buildResourceAttributes (tags, { reportHostname, otelSemanticsEnabled, service, env, serviceVersion } = {}) {
82
+ const attrs = {
83
+ 'telemetry.sdk.name': 'datadog',
84
+ 'telemetry.sdk.language': 'nodejs',
85
+ 'telemetry.sdk.version': VERSION,
86
+ }
87
+ if (service) attrs['service.name'] = service
88
+ if (serviceVersion) attrs['service.version'] = serviceVersion
89
+ if (env) attrs['deployment.environment.name'] = env
90
+ if (reportHostname) attrs['host.name'] = os.hostname()
91
+
92
+ if (!otelSemanticsEnabled) {
93
+ if (tags?.['runtime-id']) attrs['datadog.runtime_id'] = tags['runtime-id']
94
+ const processTagsObject = processTags.tagsObject
95
+ if (processTagsObject) {
96
+ for (const key of Object.keys(processTagsObject)) {
97
+ attrs[`datadog.${key}`] = processTagsObject[key]
98
+ }
99
+ }
100
+ }
101
+ return attrs
102
+ }
103
+
104
+ function createOtlpSpanStatsExporter (config) {
105
+ const { OtlpStatsExporter } = require('./otlp_span_stats_exporter')
106
+ const protocol = config.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL || 'http/json'
107
+ const resourceAttributes = buildResourceAttributes(config.tags, {
108
+ reportHostname: config.reportHostname,
109
+ otelSemanticsEnabled: config.DD_TRACE_OTEL_SEMANTICS_ENABLED,
110
+ service: config.service,
111
+ env: config.env,
112
+ serviceVersion: config.version,
113
+ })
114
+ return new OtlpStatsExporter(
115
+ config.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
116
+ protocol,
117
+ resourceAttributes,
118
+ config.DD_TRACE_OTEL_SEMANTICS_ENABLED,
119
+ config.service,
120
+ config.OTEL_EXPORTER_OTLP_METRICS_HEADERS,
121
+ config.OTEL_EXPORTER_OTLP_METRICS_TIMEOUT
122
+ )
123
+ }
124
+
78
125
  module.exports = {
79
126
  MeterProvider,
80
127
  initializeOpenTelemetryMetrics,
128
+ buildResourceAttributes,
129
+ createOtlpSpanStatsExporter,
81
130
  }
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../log')
4
+ const OtlpHttpExporterBase = require('../otlp/otlp_http_exporter_base')
5
+ const OtlpStatsTransformer = require('./otlp_span_stats_transformer')
6
+
7
+ class OtlpStatsExporter extends OtlpHttpExporterBase {
8
+ #transformer
9
+
10
+ /**
11
+ * @param {string} url
12
+ * @param {string} protocol
13
+ * @param {import('@opentelemetry/api').Attributes} resourceAttributes
14
+ * @param {boolean} [otelSemanticsEnabled]
15
+ * @param {string} [defaultService]
16
+ * @param {Record<string, string>} [headers]
17
+ * @param {number} [timeout]
18
+ */
19
+ constructor (url, protocol, resourceAttributes, otelSemanticsEnabled = false, defaultService = '',
20
+ headers, timeout = 10_000) {
21
+ super(url, headers, timeout, protocol, 'span-stats')
22
+ this.#transformer = new OtlpStatsTransformer(resourceAttributes, protocol, otelSemanticsEnabled, defaultService)
23
+ }
24
+
25
+ /**
26
+ * @param {Array<{timeNs: number, bucket: import('../../span_stats').SpanBuckets}>} drained
27
+ * @param {number} bucketSizeNs
28
+ */
29
+ export (drained, bucketSizeNs) {
30
+ if (drained.length === 0) return
31
+ const payload = this.#transformer.transform(drained, bucketSizeNs)
32
+ this.sendPayload(payload, (result) => {
33
+ if (result.code !== 0) {
34
+ log.error('Failed to export span stats: %s', result.error?.message)
35
+ }
36
+ })
37
+ }
38
+ }
39
+
40
+ module.exports = { OtlpStatsExporter }
@@ -0,0 +1,169 @@
1
+ 'use strict'
2
+
3
+ const { LogCollapsingLowestDenseDDSketch } = require('../../../../../vendor/dist/@datadog/sketches-js')
4
+ const OtlpTransformerBase = require('../otlp/otlp_transformer_base')
5
+ const { getProtobufTypes } = require('../otlp/protobuf_loader')
6
+
7
+ const NS_PER_S = 1e9
8
+
9
+ // Must match libdatadog's EXPLICIT_BOUNDS_SECONDS and OTel spanmetrics connector defaults.
10
+ const EXPLICIT_BOUNDS_SECONDS = [
11
+ 0.002, 0.004, 0.006, 0.008, 0.01, 0.05, 0.1, 0.2, 0.4, 0.8, 1, 1.4, 2, 5, 10, 15,
12
+ ]
13
+
14
+ /**
15
+ * @param {object} sketch
16
+ * @returns {number[]}
17
+ */
18
+ function sketchToFixedHistogram (sketch) {
19
+ const bucketCounts = new Array(EXPLICIT_BOUNDS_SECONDS.length + 1).fill(0)
20
+ if (sketch.zeroCount > 0) bucketCounts[0] += sketch.zeroCount
21
+ const { store, mapping } = sketch
22
+ for (let key = store.minKey; key <= store.maxKey; key++) {
23
+ const weight = store.bins[key - store.offset]
24
+ if (!weight) continue
25
+ const seconds = mapping.value(key) / NS_PER_S
26
+ let idx = EXPLICIT_BOUNDS_SECONDS.findIndex((bound) => seconds <= bound)
27
+ if (idx === -1) idx = EXPLICIT_BOUNDS_SECONDS.length
28
+ bucketCounts[idx] += weight
29
+ }
30
+ return bucketCounts.map((weight) => Math.round(weight))
31
+ }
32
+
33
+ let _deltaTemporality
34
+
35
+ function getDeltaTemporality () {
36
+ if (_deltaTemporality === undefined) {
37
+ const { protoAggregationTemporality } = getProtobufTypes()
38
+ _deltaTemporality = protoAggregationTemporality.values.AGGREGATION_TEMPORALITY_DELTA
39
+ }
40
+ return _deltaTemporality
41
+ }
42
+
43
+ const ERROR_STATUS_ATTR = { key: 'status.code', value: { intValue: 2 } }
44
+
45
+ class OtlpStatsTransformer extends OtlpTransformerBase {
46
+ #otelSemanticsEnabled
47
+ #defaultService
48
+
49
+ /**
50
+ * @param {import('@opentelemetry/api').Attributes} resourceAttributes
51
+ * @param {string} protocol
52
+ * @param {boolean} [otelSemanticsEnabled]
53
+ * @param {string} [defaultService]
54
+ */
55
+ constructor (resourceAttributes, protocol, otelSemanticsEnabled = false, defaultService = '') {
56
+ super(resourceAttributes, protocol, 'span-stats')
57
+ this.#otelSemanticsEnabled = otelSemanticsEnabled
58
+ this.#defaultService = defaultService
59
+ }
60
+
61
+ /**
62
+ * @param {Array<{timeNs: number, bucket: import('../../span_stats').SpanBuckets}>} drained
63
+ * @param {number} bucketSizeNs
64
+ */
65
+ transform (drained, bucketSizeNs) {
66
+ const isJson = this.protocol === 'http/json'
67
+ const data = {
68
+ resourceMetrics: [{
69
+ resource: this.transformResource(),
70
+ scopeMetrics: this.#buildScopeMetrics(drained, bucketSizeNs, isJson),
71
+ }],
72
+ }
73
+ return isJson
74
+ ? this.serializeToJson(data)
75
+ : this.serializeToProtobuf(getProtobufTypes().protoMetricsService, data)
76
+ }
77
+
78
+ #buildScopeMetrics (drained, bucketSizeNs, isJson) {
79
+ const temporality = isJson ? 'AGGREGATION_TEMPORALITY_DELTA' : getDeltaTemporality()
80
+
81
+ const dataPoints = []
82
+
83
+ for (const { timeNs, bucket } of drained) {
84
+ const endTimeNs = timeNs + bucketSizeNs
85
+ const startNano = isJson ? String(timeNs) : timeNs
86
+ const endNano = isJson ? String(endTimeNs) : endTimeNs
87
+
88
+ for (const aggStats of bucket.values()) {
89
+ const baseAttrs = this.#buildAttributes(aggStats.aggKey)
90
+
91
+ if (this.#otelSemanticsEnabled) {
92
+ const okDist = new LogCollapsingLowestDenseDDSketch()
93
+ okDist.merge(aggStats.topLevelOkDistribution)
94
+ okDist.merge(aggStats.nonTopLevelOkDistribution)
95
+ const errDist = new LogCollapsingLowestDenseDDSketch()
96
+ errDist.merge(aggStats.topLevelErrorDistribution)
97
+ errDist.merge(aggStats.nonTopLevelErrorDistribution)
98
+ this.#pushPoint(dataPoints, okDist, startNano, endNano, baseAttrs)
99
+ this.#pushPoint(dataPoints, errDist, startNano, endNano, [...baseAttrs, ERROR_STATUS_ATTR])
100
+ } else {
101
+ const tlAttrs = [...baseAttrs, { key: 'datadog.span.top_level', value: { boolValue: true } }]
102
+ const ntlAttrs = [...baseAttrs, { key: 'datadog.span.top_level', value: { boolValue: false } }]
103
+ this.#pushPoint(dataPoints, aggStats.topLevelOkDistribution, startNano, endNano, tlAttrs)
104
+ this.#pushPoint(dataPoints, aggStats.topLevelErrorDistribution, startNano, endNano,
105
+ [...tlAttrs, ERROR_STATUS_ATTR])
106
+ this.#pushPoint(dataPoints, aggStats.nonTopLevelOkDistribution, startNano, endNano, ntlAttrs)
107
+ this.#pushPoint(dataPoints, aggStats.nonTopLevelErrorDistribution, startNano, endNano,
108
+ [...ntlAttrs, ERROR_STATUS_ATTR])
109
+ }
110
+ }
111
+ }
112
+
113
+ if (dataPoints.length === 0) return []
114
+ return [{
115
+ metrics: [
116
+ {
117
+ name: 'traces.span.sdk.metrics.duration',
118
+ unit: 's',
119
+ histogram: { dataPoints, aggregationTemporality: temporality },
120
+ },
121
+ ],
122
+ }]
123
+ }
124
+
125
+ #pushPoint (points, sketch, startNano, endNano, attributes) {
126
+ if (!sketch || sketch.count === 0) return
127
+ points.push({
128
+ attributes,
129
+ startTimeUnixNano: startNano,
130
+ timeUnixNano: endNano,
131
+ count: sketch.count,
132
+ sum: sketch.sum / NS_PER_S,
133
+ min: sketch.min / NS_PER_S,
134
+ max: sketch.max / NS_PER_S,
135
+ bucketCounts: sketchToFixedHistogram(sketch),
136
+ explicitBounds: EXPLICIT_BOUNDS_SECONDS,
137
+ })
138
+ }
139
+
140
+ /**
141
+ * @param {import('../../span_stats').SpanAggKey} aggKey
142
+ */
143
+ #buildAttributes (aggKey) {
144
+ const raw = { 'span.name': aggKey.resource }
145
+
146
+ if (aggKey.service && aggKey.service !== this.#defaultService) {
147
+ raw['service.name'] = aggKey.service
148
+ }
149
+
150
+ if (aggKey.spanKind) raw['span.kind'] = aggKey.spanKind
151
+ if (aggKey.statusCode) raw['http.response.status_code'] = Number(aggKey.statusCode)
152
+ if (aggKey.method) raw['http.request.method'] = aggKey.method
153
+ if (aggKey.endpoint) raw['http.route'] = aggKey.endpoint
154
+ if (aggKey.rpcStatusCode !== '') {
155
+ raw['rpc.response.status_code'] = String(aggKey.rpcStatusCode).toUpperCase()
156
+ }
157
+
158
+ if (!this.#otelSemanticsEnabled) {
159
+ raw['datadog.operation.name'] = aggKey.name
160
+ if (aggKey.type) raw['datadog.span.type'] = aggKey.type
161
+ if (aggKey.synthetics) raw['datadog.origin'] = 'synthetics'
162
+ }
163
+
164
+ return this.transformAttributes(raw)
165
+ }
166
+ }
167
+
168
+ module.exports = OtlpStatsTransformer
169
+ module.exports.EXPLICIT_BOUNDS_SECONDS = EXPLICIT_BOUNDS_SECONDS
@@ -3,10 +3,12 @@
3
3
  const http = require('node:http')
4
4
  const https = require('node:https')
5
5
  const { URL } = require('node:url')
6
+ const { storage } = require('../../../../datadog-core')
6
7
  const log = require('../../log')
7
8
  const telemetryMetrics = require('../../telemetry/metrics')
8
9
 
9
10
  const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
11
+ const legacyStorage = storage('legacy')
10
12
 
11
13
  /**
12
14
  * Base class for OTLP HTTP exporters.
@@ -35,18 +37,20 @@ class OtlpHttpExporterBase {
35
37
 
36
38
  const isJson = protocol === 'http/json'
37
39
 
38
- // Initialize fields setUrl doesn't touch; it fills in hostname/port/path below.
40
+ const parsedUrl = new URL(url)
41
+ this.#transport = parsedUrl.protocol === 'http:' ? http : https
39
42
  this.options = {
40
43
  method: 'POST',
41
44
  timeout,
45
+ hostname: parsedUrl.hostname,
46
+ port: parsedUrl.port,
47
+ path: parsedUrl.pathname + parsedUrl.search,
42
48
  headers: {
43
49
  'Content-Type': isJson ? 'application/json' : 'application/x-protobuf',
44
50
  ...headers,
45
51
  },
46
52
  }
47
53
 
48
- this.setUrl(url)
49
-
50
54
  this.telemetryTags = [
51
55
  `protocol:${this.#transport === https ? 'https' : 'http'}`,
52
56
  `encoding:${isJson ? 'json' : 'protobuf'}`,
@@ -84,59 +88,54 @@ class OtlpHttpExporterBase {
84
88
  },
85
89
  }
86
90
 
87
- const req = this.#transport.request(options, (res) => {
88
- let data = ''
89
-
90
- res.on('data', (chunk) => {
91
- data += chunk
91
+ legacyStorage.run({ noop: true }, () => {
92
+ const req = this.#transport.request(options, (res) => {
93
+ let data = ''
94
+
95
+ res.on('data', (chunk) => {
96
+ data += chunk
97
+ })
98
+
99
+ res.once('end', () => {
100
+ // @ts-expect-error - res.statusCode can be undefined
101
+ if (res.statusCode >= 200 && res.statusCode < 300) {
102
+ resultCallback({ code: 0 })
103
+ } else {
104
+ const error = new Error(`HTTP ${res.statusCode}: ${data}`)
105
+ resultCallback({ code: 1, error })
106
+ }
107
+ })
92
108
  })
93
109
 
94
- res.once('end', () => {
95
- // @ts-expect-error - res.statusCode can be undefined
96
- if (res.statusCode >= 200 && res.statusCode < 300) {
97
- resultCallback({ code: 0 })
98
- } else {
99
- const error = new Error(`HTTP ${res.statusCode}: ${data}`)
100
- resultCallback({ code: 1, error })
101
- }
110
+ req.on('error', (error) => {
111
+ log.error('Error sending OTLP %s:', this.signalType, error)
112
+ resultCallback({ code: 1, error })
102
113
  })
103
- })
104
114
 
105
- req.on('error', (error) => {
106
- log.error('Error sending OTLP %s:', this.signalType, error)
107
- resultCallback({ code: 1, error })
108
- })
115
+ req.once('timeout', () => {
116
+ req.destroy()
117
+ const error = new Error('Request timeout')
118
+ resultCallback({ code: 1, error })
119
+ })
109
120
 
110
- req.once('timeout', () => {
111
- req.destroy()
112
- const error = new Error('Request timeout')
113
- resultCallback({ code: 1, error })
121
+ req.write(payload)
122
+ req.end()
114
123
  })
115
-
116
- req.write(payload)
117
- req.end()
118
124
  }
119
125
 
120
126
  /**
121
- * Updates the target URL used by this exporter. The URL is used as-is per the OTel spec: the
122
- * caller is responsible for including the signal-specific path (`/v1/traces` etc.).
123
- * @param {string} url - New OTLP endpoint URL
127
+ * Re-targets the exporter to a different URL, updating transport, hostname, port, and path.
128
+ * @param {string} url
124
129
  */
125
130
  setUrl (url) {
126
131
  const parsedUrl = new URL(url)
132
+ this.#transport = parsedUrl.protocol === 'http:' ? http : https
127
133
  this.options.hostname = parsedUrl.hostname
128
134
  this.options.port = parsedUrl.port
129
135
  this.options.path = parsedUrl.pathname + parsedUrl.search
130
- this.#transport = parsedUrl.protocol === 'http:' ? http : https
131
- if (this.telemetryTags !== undefined) {
132
- this.telemetryTags[0] = `protocol:${this.#transport === https ? 'https' : 'http'}`
133
- }
136
+ this.telemetryTags[0] = `protocol:${this.#transport === https ? 'https' : 'http'}`
134
137
  }
135
138
 
136
- /**
137
- * Shuts down the exporter.
138
- * Subclasses can override to add cleanup logic.
139
- */
140
139
  shutdown () {}
141
140
  }
142
141
 
@@ -152,6 +152,11 @@ class Span extends BridgeSpanBase {
152
152
  hostname: _tracer._hostname,
153
153
  integrationName: parentTracer?._isOtelLibrary ? 'otel.library' : 'otel',
154
154
  tags: {
155
+ // Apply global/resource tags (DD_TAGS, OTEL_RESOURCE_ATTRIBUTES) as
156
+ // defaults, mirroring the native path in opentracing/tracer.js. The
157
+ // explicit service/resource/span.kind below take precedence, and any
158
+ // user-set OTel attributes applied via setAttributes() still win.
159
+ ..._tracer._config.tags,
155
160
  [SERVICE_NAME]: _tracer._service,
156
161
  [RESOURCE_NAME]: spanName,
157
162
  [SPAN_KIND]: spanKindNames[kind],
@@ -45,6 +45,10 @@ function buildResourceAttributes (config) {
45
45
  const { service, version, env, ...filteredTags } = config.tags
46
46
  Object.assign(resourceAttributes, filteredTags)
47
47
 
48
+ if (config.OTEL_TRACES_SPAN_METRICS_ENABLED) {
49
+ resourceAttributes['_dd.stats_computed'] = 'true'
50
+ }
51
+
48
52
  return resourceAttributes
49
53
  }
50
54
 
@@ -68,7 +68,7 @@ class TracerProvider {
68
68
  return Promise.reject(new Error('Not started'))
69
69
  }
70
70
 
71
- exporter._writer.flush()
71
+ exporter._writer?.flush()
72
72
  return this._activeProcessor.forceFlush()
73
73
  }
74
74
 
@@ -45,7 +45,12 @@ class DatadogTracer {
45
45
  this._exporter = new Exporter(config, this._prioritySampler)
46
46
  }
47
47
 
48
- this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config)
48
+ let otlpStatsExporter
49
+ if (config.OTEL_TRACES_SPAN_METRICS_ENABLED) {
50
+ const { createOtlpSpanStatsExporter } = require('../opentelemetry/metrics')
51
+ otlpStatsExporter = createOtlpSpanStatsExporter(config)
52
+ }
53
+ this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config, otlpStatsExporter)
49
54
  this._url = this._exporter._url
50
55
  this._enableGetRumData = config.experimental.enableGetRumData
51
56
  this._traceId128BitGenerationEnabled = config.traceId128BitGenerationEnabled
@@ -158,7 +158,7 @@ module.exports = class PluginManager {
158
158
  const {
159
159
  logInjection,
160
160
  serviceMapping,
161
- queryStringObfuscation,
161
+ DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP,
162
162
  site,
163
163
  url,
164
164
  headerTags,
@@ -169,16 +169,20 @@ module.exports = class PluginManager {
169
169
  clientIpHeader,
170
170
  DD_TRACE_MEMCACHED_COMMAND_ENABLED,
171
171
  DD_TRACE_OTEL_SEMANTICS_ENABLED,
172
+ DD_TRACE_GRAPHQL_COLLAPSE,
173
+ DD_TRACE_GRAPHQL_DEPTH,
174
+ DD_TRACE_GRAPHQL_VARIABLES,
175
+ DD_TRACE_GRAPHQL_ERROR_EXTENSIONS,
172
176
  DD_TEST_SESSION_NAME,
173
177
  DD_AGENTLESS_LOG_SUBMISSION_ENABLED,
174
- isTestDynamicInstrumentationEnabled,
178
+ testOptimization,
175
179
  isServiceUserProvided,
176
180
  middlewareTracingEnabled,
177
181
  traceWebsocketMessagesEnabled,
178
182
  traceWebsocketMessagesInheritSampling,
179
183
  traceWebsocketMessagesSeparateTraces,
180
184
  experimental,
181
- resourceRenamingEnabled,
185
+ DD_TRACE_RESOURCE_RENAMING_ENABLED,
182
186
  } = /** @type {import('./config/config-base')} */ (this._tracerConfig)
183
187
 
184
188
  const sharedConfig = {
@@ -193,21 +197,21 @@ module.exports = class PluginManager {
193
197
  clientIpHeader,
194
198
  DD_TEST_SESSION_NAME,
195
199
  DD_AGENTLESS_LOG_SUBMISSION_ENABLED,
196
- isTestDynamicInstrumentationEnabled,
200
+ isTestDynamicInstrumentationEnabled: testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED,
197
201
  isServiceUserProvided,
198
202
  traceWebsocketMessagesEnabled,
199
203
  traceWebsocketMessagesInheritSampling,
200
204
  traceWebsocketMessagesSeparateTraces,
201
205
  experimental,
202
- resourceRenamingEnabled,
206
+ resourceRenamingEnabled: DD_TRACE_RESOURCE_RENAMING_ENABLED,
203
207
  }
204
208
 
205
209
  if (logInjection !== undefined) {
206
210
  sharedConfig.logInjection = logInjection
207
211
  }
208
212
 
209
- if (queryStringObfuscation !== undefined) {
210
- sharedConfig.queryStringObfuscation = queryStringObfuscation
213
+ if (DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP !== undefined) {
214
+ sharedConfig.queryStringObfuscation = DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP
211
215
  }
212
216
 
213
217
  if (serviceMapping && serviceMapping[name]) {
@@ -225,6 +229,18 @@ module.exports = class PluginManager {
225
229
  sharedConfig.middleware = middlewareTracingEnabled
226
230
  }
227
231
 
232
+ // The graphql `DD_TRACE_GRAPHQL_*` options are global on purpose: they feed
233
+ // the plugin config as a base that a programmatic `tracer.use('graphql', …)`
234
+ // overrides, and stay on the Config singleton so remote config and config
235
+ // telemetry observe them. Forwarded only for graphql so other plugins do not
236
+ // carry keys they ignore. The plugin-facing names drop the prefix.
237
+ if (name === 'graphql') {
238
+ sharedConfig.collapse = DD_TRACE_GRAPHQL_COLLAPSE
239
+ sharedConfig.depth = DD_TRACE_GRAPHQL_DEPTH
240
+ sharedConfig.variables = DD_TRACE_GRAPHQL_VARIABLES
241
+ sharedConfig.errorExtensions = DD_TRACE_GRAPHQL_ERROR_EXTENSIONS
242
+ }
243
+
228
244
  return sharedConfig
229
245
  }
230
246
  }