dd-trace 5.110.0 → 5.111.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 (115) hide show
  1. package/README.md +13 -8
  2. package/ci/init.js +21 -2
  3. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +13 -0
  6. package/initialize.mjs +5 -6
  7. package/loader-hook.mjs +76 -55
  8. package/package.json +9 -9
  9. package/packages/datadog-instrumentations/src/fs.js +8 -6
  10. package/packages/datadog-instrumentations/src/graphql.js +26 -495
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  13. package/packages/datadog-instrumentations/src/mongoose.js +14 -2
  14. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1293 -0
  15. package/packages/datadog-instrumentations/src/vitest-main.js +1567 -0
  16. package/packages/datadog-instrumentations/src/vitest-util.js +249 -0
  17. package/packages/datadog-instrumentations/src/vitest-worker.js +753 -0
  18. package/packages/datadog-instrumentations/src/vitest.js +11 -1855
  19. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  20. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +18 -10
  21. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  22. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  23. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +10 -4
  24. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  25. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  26. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  27. package/packages/datadog-plugin-graphql/src/execute.js +627 -11
  28. package/packages/datadog-plugin-graphql/src/index.js +20 -8
  29. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  30. package/packages/datadog-plugin-graphql/src/validate.js +16 -5
  31. package/packages/datadog-plugin-http/src/index.js +6 -8
  32. package/packages/datadog-plugin-jest/src/index.js +1 -1
  33. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  34. package/packages/datadog-plugin-openai/src/services.js +2 -2
  35. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  36. package/packages/datadog-plugin-vitest/src/index.js +69 -63
  37. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  38. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  39. package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
  40. package/packages/dd-trace/src/appsec/channels.js +3 -1
  41. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  42. package/packages/dd-trace/src/appsec/graphql.js +5 -5
  43. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +26 -19
  44. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  45. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  46. package/packages/dd-trace/src/appsec/index.js +10 -1
  47. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  48. package/packages/dd-trace/src/appsec/reporter.js +49 -10
  49. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
  50. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  51. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  52. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  53. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -2
  54. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  55. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  56. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -2
  57. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  58. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -13
  59. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  60. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  61. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  62. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  63. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  64. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  65. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  66. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  67. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  68. package/packages/dd-trace/src/config/defaults.js +3 -2
  69. package/packages/dd-trace/src/config/generated-config-types.d.ts +61 -57
  70. package/packages/dd-trace/src/config/helper.js +1 -0
  71. package/packages/dd-trace/src/config/index.js +35 -22
  72. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  73. package/packages/dd-trace/src/config/remote_config.js +1 -1
  74. package/packages/dd-trace/src/config/supported-configurations.json +90 -56
  75. package/packages/dd-trace/src/constants.js +7 -0
  76. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  77. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  78. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  79. package/packages/dd-trace/src/llmobs/index.js +1 -1
  80. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  81. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  82. package/packages/dd-trace/src/llmobs/sdk.js +3 -3
  83. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  84. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  85. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  86. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  87. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  88. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  89. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
  90. package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
  91. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  92. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  93. package/packages/dd-trace/src/plugin_manager.js +7 -7
  94. package/packages/dd-trace/src/plugins/ci_plugin.js +12 -3
  95. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  96. package/packages/dd-trace/src/plugins/util/test.js +11 -9
  97. package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
  98. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
  99. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  100. package/packages/dd-trace/src/proxy.js +8 -8
  101. package/packages/dd-trace/src/span_processor.js +5 -6
  102. package/packages/dd-trace/src/span_stats.js +96 -78
  103. package/packages/dd-trace/src/startup-log.js +2 -1
  104. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  105. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  106. package/packages/dd-trace/src/telemetry/index.js +2 -2
  107. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  108. package/packages/dd-trace/src/telemetry/send-data.js +8 -8
  109. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  110. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  111. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  112. package/vendor/dist/protobufjs/index.js +1 -1
  113. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  114. package/vendor/dist/shell-quote/index.js +1 -1
  115. 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
 
@@ -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,
@@ -171,14 +171,14 @@ module.exports = class PluginManager {
171
171
  DD_TRACE_OTEL_SEMANTICS_ENABLED,
172
172
  DD_TEST_SESSION_NAME,
173
173
  DD_AGENTLESS_LOG_SUBMISSION_ENABLED,
174
- isTestDynamicInstrumentationEnabled,
174
+ testOptimization,
175
175
  isServiceUserProvided,
176
176
  middlewareTracingEnabled,
177
177
  traceWebsocketMessagesEnabled,
178
178
  traceWebsocketMessagesInheritSampling,
179
179
  traceWebsocketMessagesSeparateTraces,
180
180
  experimental,
181
- resourceRenamingEnabled,
181
+ DD_TRACE_RESOURCE_RENAMING_ENABLED,
182
182
  } = /** @type {import('./config/config-base')} */ (this._tracerConfig)
183
183
 
184
184
  const sharedConfig = {
@@ -193,21 +193,21 @@ module.exports = class PluginManager {
193
193
  clientIpHeader,
194
194
  DD_TEST_SESSION_NAME,
195
195
  DD_AGENTLESS_LOG_SUBMISSION_ENABLED,
196
- isTestDynamicInstrumentationEnabled,
196
+ isTestDynamicInstrumentationEnabled: testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED,
197
197
  isServiceUserProvided,
198
198
  traceWebsocketMessagesEnabled,
199
199
  traceWebsocketMessagesInheritSampling,
200
200
  traceWebsocketMessagesSeparateTraces,
201
201
  experimental,
202
- resourceRenamingEnabled,
202
+ resourceRenamingEnabled: DD_TRACE_RESOURCE_RENAMING_ENABLED,
203
203
  }
204
204
 
205
205
  if (logInjection !== undefined) {
206
206
  sharedConfig.logInjection = logInjection
207
207
  }
208
208
 
209
- if (queryStringObfuscation !== undefined) {
210
- sharedConfig.queryStringObfuscation = queryStringObfuscation
209
+ if (DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP !== undefined) {
210
+ sharedConfig.queryStringObfuscation = DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP
211
211
  }
212
212
 
213
213
  if (serviceMapping && serviceMapping[name]) {
@@ -61,7 +61,7 @@ const {
61
61
  DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX,
62
62
  DI_DEBUG_ERROR_FILE_SUFFIX,
63
63
  DI_DEBUG_ERROR_LINE_SUFFIX,
64
- getLibraryCapabilitiesTags,
64
+ getLibraryCapabilitiesTags: getDefaultLibraryCapabilitiesTags,
65
65
  getPullRequestDiff,
66
66
  getModifiedFilesFromDiff,
67
67
  getPullRequestBaseBranch,
@@ -109,7 +109,7 @@ const TEST_FRAMEWORKS_TO_SKIP_GIT_METADATA_EXTRACTION = new Set([
109
109
  ])
110
110
 
111
111
  function setItrSkippingEnabledTagFromLibraryConfig (plugin, frameworkVersion) {
112
- const libraryCapabilitiesTags = getLibraryCapabilitiesTags(plugin.constructor.id, frameworkVersion)
112
+ const libraryCapabilitiesTags = getDefaultLibraryCapabilitiesTags(plugin.constructor.id, frameworkVersion)
113
113
 
114
114
  if (!libraryCapabilitiesTags[DD_CAPABILITIES_TEST_IMPACT_ANALYSIS] ||
115
115
  !plugin.libraryConfig ||
@@ -168,7 +168,7 @@ module.exports = class CiPlugin extends Plugin {
168
168
  ? getSessionRequestErrorTags(this.testSessionSpan)
169
169
  : Object.fromEntries(this._pendingRequestErrorTags.map(({ tag, value }) => [tag, value]))
170
170
 
171
- const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, frameworkVersion)
171
+ const libraryCapabilitiesTags = this.getLibraryCapabilitiesTags(frameworkVersion, ctx)
172
172
  const metadataTags = {
173
173
  test: {
174
174
  ...libraryCapabilitiesTags,
@@ -431,6 +431,15 @@ module.exports = class CiPlugin extends Plugin {
431
431
  }
432
432
  }
433
433
 
434
+ /**
435
+ * Returns library capability metadata tags for this test framework.
436
+ * @param {string} frameworkVersion - The test framework version.
437
+ * @returns {Record<string, string|undefined>}
438
+ */
439
+ getLibraryCapabilitiesTags (frameworkVersion) {
440
+ return getDefaultLibraryCapabilitiesTags(this.constructor.id, frameworkVersion)
441
+ }
442
+
434
443
  /**
435
444
  * Adds a hidden _dd tag to the test session span when a test-optimization request fails.
436
445
  * If the session span does not exist yet (e.g. library-configuration failed before session:start),
@@ -37,7 +37,12 @@ function isPromptCompletionSampled (sampler, span) {
37
37
 
38
38
  module.exports = function makeUtilities (integrationName, tracerConfig) {
39
39
  const integrationConfig = tracerConfig[integrationName] || {}
40
- const { spanCharLimit, spanPromptCompletionSampleRate } = integrationConfig
40
+ // The per-integration config is keyed by the canonical environment variable
41
+ // names (e.g. `DD_LANGCHAIN_SPAN_CHAR_LIMIT`), nested under the integration's
42
+ // namespace. Derive those leaf names from the integration name.
43
+ const prefix = `DD_${integrationName.toUpperCase()}_`
44
+ const spanCharLimit = integrationConfig[`${prefix}SPAN_CHAR_LIMIT`]
45
+ const spanPromptCompletionSampleRate = integrationConfig[`${prefix}SPAN_PROMPT_COMPLETION_SAMPLE_RATE`]
41
46
 
42
47
  const sampler = new Sampler(spanPromptCompletionSampleRate ?? 1)
43
48