dd-trace 5.98.0 → 5.99.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 (123) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/ext/tags.js +1 -0
  3. package/index.d.ts +9 -1
  4. package/package.json +48 -46
  5. package/packages/datadog-instrumentations/src/crypto.js +45 -0
  6. package/packages/datadog-instrumentations/src/cypress-config.js +122 -16
  7. package/packages/datadog-instrumentations/src/dns.js +24 -56
  8. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  9. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +74 -0
  10. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +4 -1
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +10 -3
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +59 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +11 -2
  16. package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +7 -0
  17. package/packages/datadog-instrumentations/src/pino.js +4 -28
  18. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +27 -0
  19. package/packages/datadog-instrumentations/src/playwright.js +5 -17
  20. package/packages/datadog-instrumentations/src/stripe.js +38 -24
  21. package/packages/datadog-instrumentations/src/vitest.js +32 -4
  22. package/packages/datadog-instrumentations/src/zlib.js +29 -0
  23. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -2
  24. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +8 -15
  25. package/packages/datadog-plugin-azure-service-bus/src/producer.js +4 -9
  26. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  27. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -5
  28. package/packages/datadog-plugin-cypress/src/source-map-utils.js +48 -1
  29. package/packages/datadog-plugin-http/src/server.js +11 -11
  30. package/packages/datadog-plugin-jest/src/index.js +2 -2
  31. package/packages/datadog-plugin-mocha/src/index.js +1 -2
  32. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/index.js +24 -0
  33. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +55 -0
  34. package/packages/datadog-plugin-mongodb-core/src/index.js +1 -6
  35. package/packages/datadog-plugin-playwright/src/index.js +2 -3
  36. package/packages/datadog-plugin-vitest/src/index.js +14 -6
  37. package/packages/datadog-plugin-ws/src/close.js +2 -0
  38. package/packages/datadog-plugin-ws/src/producer.js +2 -0
  39. package/packages/datadog-plugin-ws/src/receiver.js +1 -0
  40. package/packages/dd-trace/src/aiguard/channels.js +8 -0
  41. package/packages/dd-trace/src/aiguard/index.js +7 -3
  42. package/packages/dd-trace/src/aiguard/sdk.js +44 -0
  43. package/packages/dd-trace/src/aiguard/tags.js +1 -0
  44. package/packages/dd-trace/src/appsec/graphql.js +6 -6
  45. package/packages/dd-trace/src/appsec/index.js +9 -11
  46. package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -5
  47. package/packages/dd-trace/src/appsec/rasp/lfi.js +8 -4
  48. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +5 -10
  49. package/packages/dd-trace/src/appsec/rasp/ssrf.js +5 -6
  50. package/packages/dd-trace/src/appsec/recommended.json +2438 -13
  51. package/packages/dd-trace/src/appsec/reporter.js +6 -5
  52. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +4 -8
  53. package/packages/dd-trace/src/appsec/store.js +50 -0
  54. package/packages/dd-trace/src/appsec/waf/index.js +3 -5
  55. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -2
  56. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -2
  57. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +2 -2
  58. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -2
  59. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +3 -4
  60. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -2
  61. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +4 -5
  62. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +3 -4
  63. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +6 -6
  64. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +2 -2
  65. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +2 -2
  66. package/packages/dd-trace/src/config/config-types.d.ts +0 -4
  67. package/packages/dd-trace/src/config/defaults.js +10 -10
  68. package/packages/dd-trace/src/config/generated-config-types.d.ts +13 -12
  69. package/packages/dd-trace/src/config/index.js +25 -35
  70. package/packages/dd-trace/src/config/parsers.js +26 -9
  71. package/packages/dd-trace/src/config/supported-configurations.json +32 -36
  72. package/packages/dd-trace/src/debugger/config.js +2 -0
  73. package/packages/dd-trace/src/debugger/devtools_client/send.js +25 -5
  74. package/packages/dd-trace/src/encode/0.4.js +4 -5
  75. package/packages/dd-trace/src/exporters/agent/index.js +0 -1
  76. package/packages/dd-trace/src/exporters/agent/writer.js +1 -2
  77. package/packages/dd-trace/src/exporters/agentless/writer.js +3 -3
  78. package/packages/dd-trace/src/exporters/common/util.js +2 -2
  79. package/packages/dd-trace/src/id.js +2 -0
  80. package/packages/dd-trace/src/index.js +2 -5
  81. package/packages/dd-trace/src/lambda/handler.js +1 -3
  82. package/packages/dd-trace/src/llmobs/plugins/{anthropic.js → anthropic/index.js} +5 -63
  83. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +106 -0
  84. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +3 -2
  85. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +3 -2
  86. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +2 -1
  87. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +0 -49
  88. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/vectorstore.js +2 -1
  89. package/packages/dd-trace/src/llmobs/plugins/langchain/messages.js +76 -0
  90. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -26
  91. package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/index.js +68 -0
  92. package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/utils.js +57 -0
  93. package/packages/dd-trace/src/llmobs/sdk.js +2 -2
  94. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +103 -0
  95. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -0
  96. package/packages/dd-trace/src/opentelemetry/logs/index.js +1 -1
  97. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +3 -2
  98. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  99. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +3 -2
  100. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +19 -66
  101. package/packages/dd-trace/src/opentelemetry/trace/index.js +11 -16
  102. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +11 -3
  103. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +51 -41
  104. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -11
  105. package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -10
  106. package/packages/dd-trace/src/opentracing/span.js +1 -1
  107. package/packages/dd-trace/src/opentracing/tracer.js +12 -5
  108. package/packages/dd-trace/src/plugins/index.js +1 -0
  109. package/packages/dd-trace/src/plugins/util/test.js +126 -5
  110. package/packages/dd-trace/src/plugins/util/url.js +2 -1
  111. package/packages/dd-trace/src/profiling/profilers/event_plugins/crypto.js +32 -0
  112. package/packages/dd-trace/src/profiling/profilers/event_plugins/zlib.js +19 -0
  113. package/packages/dd-trace/src/profiling/profilers/events.js +35 -0
  114. package/packages/dd-trace/src/proxy.js +2 -8
  115. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -2
  116. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
  117. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
  118. package/packages/dd-trace/src/span_processor.js +1 -2
  119. package/packages/dd-trace/src/tagger.js +2 -2
  120. package/packages/dd-trace/src/telemetry/send-data.js +5 -7
  121. package/packages/dd-trace/src/tracer.js +2 -2
  122. package/vendor/dist/ignore/LICENSE +0 -21
  123. package/vendor/dist/ignore/index.js +0 -1
@@ -18,13 +18,14 @@ class OtlpHttpMetricExporter extends OtlpHttpExporterBase {
18
18
  * Creates a new OtlpHttpMetricExporter instance.
19
19
  *
20
20
  * @param {string} url - OTLP endpoint URL
21
- * @param {string} headers - Additional HTTP headers as comma-separated key=value string
21
+ * @param {Record<string, string>|undefined} headers - Additional HTTP headers parsed from the
22
+ * corresponding `OTEL_EXPORTER_OTLP_*_HEADERS` env by the MAP parser.
22
23
  * @param {number} timeout - Request timeout in milliseconds
23
24
  * @param {string} protocol - OTLP protocol (http/protobuf or http/json)
24
25
  * @param {Resource} resource - Resource attributes
25
26
  */
26
27
  constructor (url, headers, timeout, protocol, resource) {
27
- super(url, headers, timeout, protocol, '/v1/metrics', 'metrics')
28
+ super(url, headers, timeout, protocol, 'metrics')
28
29
  this.transformer = new OtlpTransformer(resource, protocol)
29
30
  }
30
31
 
@@ -19,34 +19,31 @@ class OtlpHttpExporterBase {
19
19
  /**
20
20
  * Creates a new OtlpHttpExporterBase instance.
21
21
  *
22
- * @param {string} url - OTLP endpoint URL
23
- * @param {string|undefined} headers - Additional HTTP headers as comma-separated key=value string
22
+ * @param {string} url - OTLP endpoint URL (callers are expected to supply the full signal URL)
23
+ * @param {Record<string, string>|undefined} headers - Additional HTTP headers parsed from the
24
+ * corresponding `OTEL_EXPORTER_OTLP_*_HEADERS` env by the MAP parser.
24
25
  * @param {number} timeout - Request timeout in milliseconds
25
26
  * @param {string} protocol - OTLP protocol (http/protobuf or http/json)
26
- * @param {string} defaultPath - Default path to use if URL has no path
27
27
  * @param {string} signalType - Signal type for error messages (e.g., 'logs', 'metrics')
28
28
  */
29
- constructor (url, headers, timeout, protocol, defaultPath, signalType) {
30
- const parsedUrl = new URL(url)
31
-
29
+ constructor (url, headers, timeout, protocol, signalType) {
32
30
  this.protocol = protocol
33
31
  this.signalType = signalType
34
32
 
35
- // If no path is provided, use default path
36
- const path = parsedUrl.pathname === '/' ? defaultPath : parsedUrl.pathname
37
33
  const isJson = protocol === 'http/json'
38
34
 
35
+ // Initialize fields setUrl doesn't touch; it fills in hostname/port/path below.
39
36
  this.options = {
40
- hostname: parsedUrl.hostname,
41
- port: parsedUrl.port,
42
- path: path + parsedUrl.search,
43
37
  method: 'POST',
44
38
  timeout,
45
39
  headers: {
46
40
  'Content-Type': isJson ? 'application/json' : 'application/x-protobuf',
47
- ...this.#parseAdditionalHeaders(headers),
41
+ ...headers,
48
42
  },
49
43
  }
44
+
45
+ this.setUrl(url)
46
+
50
47
  this.telemetryTags = [
51
48
  'protocol:http',
52
49
  `encoding:${isJson ? 'json' : 'protobuf'}`,
@@ -61,6 +58,7 @@ class OtlpHttpExporterBase {
61
58
  * @protected
62
59
  */
63
60
  recordTelemetry (metricName, count, additionalTags) {
61
+ // @ts-expect-error - additionalTags is optional and can be undefined
64
62
  if (additionalTags?.length > 0) {
65
63
  tracerMetrics.count(metricName, [...this.telemetryTags, ...additionalTags || []]).inc(count)
66
64
  } else {
@@ -91,6 +89,7 @@ class OtlpHttpExporterBase {
91
89
  })
92
90
 
93
91
  res.once('end', () => {
92
+ // @ts-expect-error - res.statusCode can be undefined
94
93
  if (res.statusCode >= 200 && res.statusCode < 300) {
95
94
  resultCallback({ code: 0 })
96
95
  } else {
@@ -116,61 +115,15 @@ class OtlpHttpExporterBase {
116
115
  }
117
116
 
118
117
  /**
119
- * Parses additional HTTP headers from a comma-separated string or pre-parsed map.
120
- * @param {string|Record<string, string>} [headersString=''] - Comma-separated key=value pairs or map
121
- * @returns {Record<string, string>} Parsed headers object
118
+ * Updates the target URL used by this exporter. The URL is used as-is per the OTel spec: the
119
+ * caller is responsible for including the signal-specific path (`/v1/traces` etc.).
120
+ * @param {string} url - New OTLP endpoint URL
122
121
  */
123
- #parseAdditionalHeaders (headersString = '') {
124
- if (headersString !== null && typeof headersString === 'object') {
125
- // The config MAP parser uses tagger.add (which splits on ':'), so OTEL-format
126
- // headers ('key=value') arrive with the full 'key=value' string as the map key
127
- // and an empty string as the value. Re-split on '=' to get the correct pairs.
128
- const result = {}
129
- for (const [k, v] of Object.entries(headersString)) {
130
- if (v === '' && k.includes('=')) {
131
- const idx = k.indexOf('=')
132
- result[k.slice(0, idx).trim()] = k.slice(idx + 1).trim()
133
- } else {
134
- result[k] = v
135
- }
136
- }
137
- return result
138
- }
139
- const headers = {}
140
- let key = ''
141
- let value = ''
142
- let readingKey = true
143
-
144
- for (const char of headersString) {
145
- if (readingKey) {
146
- if (char === '=') {
147
- readingKey = false
148
- key = key.trim()
149
- } else {
150
- key += char
151
- }
152
- } else if (char === ',') {
153
- value = value.trim()
154
- if (key && value) {
155
- headers[key] = value
156
- }
157
- key = ''
158
- value = ''
159
- readingKey = true
160
- } else {
161
- value += char
162
- }
163
- }
164
-
165
- // Add the last pair if present
166
- if (!readingKey) {
167
- value = value.trim()
168
- if (value) {
169
- headers[key] = value
170
- }
171
- }
172
-
173
- return headers
122
+ setUrl (url) {
123
+ const parsedUrl = new URL(url)
124
+ this.options.hostname = parsedUrl.hostname
125
+ this.options.port = parsedUrl.port
126
+ this.options.path = parsedUrl.pathname + parsedUrl.search
174
127
  }
175
128
 
176
129
  /**
@@ -4,7 +4,7 @@ const { VERSION } = require('../../../../../version')
4
4
  const OtlpHttpTraceExporter = require('./otlp_http_trace_exporter')
5
5
 
6
6
  /**
7
- * @typedef {import('../../config')} Config
7
+ * @typedef {import('../../config/config-base')} Config
8
8
  * @typedef {import('../../opentracing/tracer')} DatadogTracer
9
9
  */
10
10
 
@@ -33,21 +33,17 @@ const OtlpHttpTraceExporter = require('./otlp_http_trace_exporter')
33
33
  */
34
34
  function buildResourceAttributes (config) {
35
35
  const resourceAttributes = {
36
- 'service.name': config.service || config.tags.service,
36
+ 'service.name': config.service,
37
37
  'telemetry.sdk.name': 'datadog',
38
38
  'telemetry.sdk.version': VERSION,
39
39
  'telemetry.sdk.language': 'nodejs',
40
40
  }
41
41
 
42
- const env = config.env || config.tags.env
43
- if (env) resourceAttributes['deployment.environment'] = env
44
- const version = config.version || config.tags.version
45
- if (version) resourceAttributes['service.version'] = version
42
+ if (config.env) resourceAttributes['deployment.environment.name'] = config.env
43
+ if (config.version) resourceAttributes['service.version'] = config.version
46
44
 
47
- if (config.tags) {
48
- const { service, version, env, ...filteredTags } = config.tags
49
- Object.assign(resourceAttributes, filteredTags)
50
- }
45
+ const { service, version, env, ...filteredTags } = config.tags
46
+ Object.assign(resourceAttributes, filteredTags)
51
47
 
52
48
  return resourceAttributes
53
49
  }
@@ -56,15 +52,14 @@ function buildResourceAttributes (config) {
56
52
  * Creates the OTLP HTTP/JSON trace exporter.
57
53
  *
58
54
  * @param {Config} config - Tracer configuration instance
59
- * @param {import('@opentelemetry/api').Attributes} resourceAttributes - Resource attributes
60
55
  * @returns {OtlpHttpTraceExporter} The OTLP HTTP/JSON exporter
61
56
  */
62
- function createOtlpTraceExporter (config, resourceAttributes) {
57
+ function createOtlpTraceExporter (config) {
63
58
  return new OtlpHttpTraceExporter(
64
- config.otelTracesUrl,
65
- config.otelTracesHeaders,
66
- config.otelTracesTimeout,
67
- resourceAttributes
59
+ config.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
60
+ config.OTEL_EXPORTER_OTLP_TRACES_HEADERS,
61
+ config.OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
62
+ buildResourceAttributes(config)
68
63
  )
69
64
  }
70
65
 
@@ -8,13 +8,20 @@ const OtlpTraceTransformer = require('./otlp_transformer')
8
8
  /**
9
9
  * OtlpHttpTraceExporter exports DD-formatted spans via OTLP over HTTP/JSON.
10
10
  *
11
- * This implementation follows the OTLP HTTP v1.7.0 specification:
11
+ * This implementation follows the OTLP HTTP specification:
12
12
  * https://opentelemetry.io/docs/specs/otlp/#otlphttp
13
13
  *
14
14
  * It receives DD-formatted spans (from span_format.js), transforms them
15
15
  * to OTLP ExportTraceServiceRequest JSON format, and sends them to the
16
16
  * configured OTLP endpoint via HTTP POST.
17
17
  *
18
+ * TODO: Add batch handling similar to the OpenTelemetry SDK Batch Processor
19
+ * (https://opentelemetry.io/docs/specs/otel/trace/sdk/#batching-processor).
20
+ * Currently each finished trace is sent as its own HTTP request, which is
21
+ * unsuitable for high-traffic production environments. The config values
22
+ * `otelBatchTimeout`, `otelMaxExportBatchSize`, and `otelMaxQueueSize`
23
+ * (OTEL_BSP_*) are already defined and should drive that implementation.
24
+ *
18
25
  * @class OtlpHttpTraceExporter
19
26
  * @augments OtlpHttpExporterBase
20
27
  */
@@ -25,12 +32,13 @@ class OtlpHttpTraceExporter extends OtlpHttpExporterBase {
25
32
  * Creates a new OtlpHttpTraceExporter instance.
26
33
  *
27
34
  * @param {string} url - OTLP endpoint URL
28
- * @param {string} headers - Additional HTTP headers as comma-separated key=value string
35
+ * @param {Record<string, string>|undefined} headers - Additional HTTP headers parsed from the
36
+ * corresponding `OTEL_EXPORTER_OTLP_*_HEADERS` env by the MAP parser.
29
37
  * @param {number} timeout - Request timeout in milliseconds
30
38
  * @param {import('@opentelemetry/api').Attributes} resourceAttributes - Resource attributes
31
39
  */
32
40
  constructor (url, headers, timeout, resourceAttributes) {
33
- super(url, headers, timeout, 'http/json', '/v1/traces', 'traces')
41
+ super(url, headers, timeout, 'http/json', 'traces')
34
42
  this.#transformer = new OtlpTraceTransformer(resourceAttributes)
35
43
  }
36
44
 
@@ -3,6 +3,7 @@
3
3
  const OtlpTransformerBase = require('../otlp/otlp_transformer_base')
4
4
  const { getProtobufTypes } = require('../otlp/protobuf_loader')
5
5
  const { VERSION } = require('../../../../../version')
6
+ const id = require('../../id')
6
7
 
7
8
  const { protoSpanKind } = getProtobufTypes()
8
9
  const SPAN_KIND_UNSPECIFIED = protoSpanKind.values.SPAN_KIND_UNSPECIFIED
@@ -12,11 +13,28 @@ const SPAN_KIND_CLIENT = protoSpanKind.values.SPAN_KIND_CLIENT
12
13
  const SPAN_KIND_PRODUCER = protoSpanKind.values.SPAN_KIND_PRODUCER
13
14
  const SPAN_KIND_CONSUMER = protoSpanKind.values.SPAN_KIND_CONSUMER
14
15
 
16
+ // Cached zero Identifier used to detect zero IDs without re-allocating per span.
17
+ const ZERO_ID = id('0')
18
+
15
19
  /**
20
+ * @typedef {import('../../id').Identifier} Identifier
21
+ *
22
+ * @typedef {object} DDSpanLink
23
+ * @property {string} trace_id - Hex-encoded trace ID
24
+ * @property {string} span_id - Hex-encoded span ID
25
+ * @property {Record<string, string | number | boolean>} [attributes] - Link attributes
26
+ * @property {number} [flags] - Trace flags
27
+ * @property {string} [tracestate] - W3C trace state
28
+ *
29
+ * @typedef {object} DDSpanEvent
30
+ * @property {string} name - Event name
31
+ * @property {number} time_unix_nano - Event time in nanoseconds since epoch
32
+ * @property {Record<string, string | number | boolean>} [attributes] - Event attributes
33
+ *
16
34
  * @typedef {object} DDFormattedSpan
17
- * @property {import('../../id')} trace_id - DD Identifier for trace ID
18
- * @property {import('../../id')} span_id - DD Identifier for span ID
19
- * @property {import('../../id')} parent_id - DD Identifier for parent span ID
35
+ * @property {Identifier} trace_id - DD Identifier for trace ID
36
+ * @property {Identifier} span_id - DD Identifier for span ID
37
+ * @property {Identifier} parent_id - DD Identifier for parent span ID
20
38
  * @property {string} name - Span operation name
21
39
  * @property {string} resource - Resource name
22
40
  * @property {string} [service] - Service name
@@ -24,9 +42,10 @@ const SPAN_KIND_CONSUMER = protoSpanKind.values.SPAN_KIND_CONSUMER
24
42
  * @property {number} error - Error flag (0 or 1)
25
43
  * @property {{[key: string]: string}} meta - String key-value tags
26
44
  * @property {{[key: string]: number}} metrics - Numeric key-value tags
45
+ * @property {{[key: string]: object}} [meta_struct] - Structured tags (JSON-serialized, bytes in protobuf)
27
46
  * @property {number} start - Start time in nanoseconds since epoch
28
47
  * @property {number} duration - Duration in nanoseconds
29
- * @property {object[]} [span_events] - Span events
48
+ * @property {DDSpanEvent[]} [span_events] - Span events
30
49
  */
31
50
 
32
51
  // Map DD span.kind string values to OTLP SpanKind numeric values
@@ -51,7 +70,7 @@ const EXCLUDED_META_KEYS = new Set([
51
70
  /**
52
71
  * OtlpTraceTransformer transforms DD-formatted spans to OTLP trace JSON format.
53
72
  *
54
- * This implementation follows the OTLP Trace v1.7.0 Data Model specification:
73
+ * This implementation follows the OTLP trace data model:
55
74
  * https://opentelemetry.io/docs/specs/otlp/#trace-data-model
56
75
  *
57
76
  * It receives DD-formatted spans (from span_format.js) and produces
@@ -119,7 +138,7 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
119
138
  return {
120
139
  traceId: this.#idToBytes(span.trace_id, 16),
121
140
  spanId: this.#idToBytes(span.span_id, 8),
122
- parentSpanId: (parentId && !this.#isZeroId(parentId)) ? this.#idToBytes(parentId, 8) : undefined,
141
+ parentSpanId: (parentId && !parentId.equals(ZERO_ID)) ? this.#idToBytes(parentId, 8) : undefined,
123
142
  name: span.resource,
124
143
  kind: this.#mapSpanKind(span.meta?.['span.kind']),
125
144
  startTimeUnixNano: span.start,
@@ -178,7 +197,10 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
178
197
  }
179
198
  }
180
199
 
181
- // Add meta_struct as bytesValue attributes (JSON-serialized, base64-encoded per proto JSON mapping)
200
+ // TODO: meta_struct values are logically raw bytes. The OTLP http/json spec encodes the bytesValue
201
+ // field as base64, but when http/protobuf or gRPC support is added the payload should be sent as
202
+ // raw bytes directly (no JSON.stringify + base64). The backend decoding side will need to be
203
+ // updated in parallel to accept the unencoded bytes.
182
204
  if (span.meta_struct) {
183
205
  for (const [key, value] of Object.entries(span.meta_struct)) {
184
206
  const bytes = Buffer.from(JSON.stringify(value))
@@ -202,33 +224,40 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
202
224
 
203
225
  /**
204
226
  * Maps DD span error state to an OTLP Status object.
227
+ * Combines error.type and error.message when both are present so error type
228
+ * information is preserved on the OTel side.
205
229
  *
206
230
  * @param {DDFormattedSpan} span - DD-formatted span
207
231
  * @returns {object} OTLP Status object with code and message
208
232
  */
209
233
  #mapStatus (span) {
210
- if (span.error === 1) {
211
- return {
212
- code: STATUS_CODE_ERROR,
213
- message: span.meta?.['error.message'] || '',
214
- }
234
+ if (span.error !== 1) {
235
+ return { code: STATUS_CODE_UNSET, message: '' }
236
+ }
237
+ const errorType = span.meta?.['error.type']
238
+ const errorMessage = span.meta?.['error.message']
239
+ let message = ''
240
+ if (errorType && errorMessage) {
241
+ message = `${errorType}: ${errorMessage}`
242
+ } else if (errorType) {
243
+ message = errorType
244
+ } else if (errorMessage) {
245
+ message = errorMessage
215
246
  }
216
- return { code: STATUS_CODE_UNSET, message: '' }
247
+ return { code: STATUS_CODE_ERROR, message }
217
248
  }
218
249
 
219
250
  /**
220
251
  * Transforms a DD span event to an OTLP Event object.
221
252
  *
222
- * @param {object} event - DD span event with name, time_unix_nano, and attributes
253
+ * @param {DDSpanEvent} event - DD span event
223
254
  * @returns {object} OTLP Event object
224
255
  */
225
256
  #transformEvent (event) {
226
257
  return {
227
258
  timeUnixNano: event.time_unix_nano,
228
259
  name: event.name || '',
229
- attributes: event.attributes && Object.keys(event.attributes).length > 0
230
- ? this.transformAttributes(event.attributes)
231
- : [],
260
+ attributes: this.transformAttributes(event.attributes ?? {}),
232
261
  droppedAttributesCount: 0,
233
262
  }
234
263
  }
@@ -257,7 +286,7 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
257
286
  /**
258
287
  * Transforms a single DD span link to an OTLP Link object.
259
288
  *
260
- * @param {object} link - DD span link with trace_id, span_id, attributes, flags, tracestate
289
+ * @param {DDSpanLink} link - DD span link
261
290
  * @returns {object} OTLP Link object
262
291
  */
263
292
  #transformLink (link) {
@@ -265,9 +294,7 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
265
294
  traceId: this.#hexToBytes(link.trace_id, 16),
266
295
  spanId: this.#hexToBytes(link.span_id, 8),
267
296
  traceState: link.tracestate || '',
268
- attributes: link.attributes && Object.keys(link.attributes).length > 0
269
- ? this.transformAttributes(link.attributes)
270
- : [],
297
+ attributes: this.transformAttributes(link.attributes ?? {}),
271
298
  droppedAttributesCount: 0,
272
299
  flags: link.flags,
273
300
  }
@@ -278,7 +305,7 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
278
305
  * Pads with leading zeros if the identifier buffer is shorter than the target.
279
306
  * Per the OTLP http/json spec, trace-ids and span-ids must be hex-encoded strings.
280
307
  *
281
- * @param {object} identifier - DD Identifier object with toBuffer() method
308
+ * @param {Identifier} identifier - DD Identifier
282
309
  * @param {number} targetLength - Target byte length (16 for trace ID, 8 for span ID)
283
310
  * @returns {string} Hex-encoded string of the specified length
284
311
  */
@@ -290,29 +317,12 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
290
317
  if (buffer.length > targetLength) {
291
318
  return Buffer.from(buffer.slice(buffer.length - targetLength)).toString('hex')
292
319
  }
293
- // Pad with leading zeros to reach target length
320
+ // Pad with leading zeros to reach target length.
294
321
  const result = Buffer.alloc(targetLength)
295
- const offset = targetLength - buffer.length
296
- for (let i = 0; i < buffer.length; i++) {
297
- result[offset + i] = buffer[i]
298
- }
322
+ Buffer.from(buffer).copy(result, targetLength - buffer.length)
299
323
  return result.toString('hex')
300
324
  }
301
325
 
302
- /**
303
- * Checks if a DD Identifier represents a zero ID (all bytes are 0).
304
- *
305
- * @param {object} identifier - DD Identifier object with toBuffer() method
306
- * @returns {boolean} True if the identifier is all zeros
307
- */
308
- #isZeroId (identifier) {
309
- const buffer = identifier.toBuffer()
310
- for (let i = 0; i < buffer.length; i++) {
311
- if (buffer[i] !== 0) return false
312
- }
313
- return true
314
- }
315
-
316
326
  /**
317
327
  * Normalizes a hex string to the specified byte length.
318
328
  * Pads with leading zeros if the hex string is shorter than expected.
@@ -81,10 +81,10 @@ class Tracer {
81
81
  }
82
82
 
83
83
  _convertOtelContextToDatadog (traceId, spanId, traceFlag, ts, meta = {}) {
84
- const origin = null
84
+ let origin = null
85
85
  let samplingPriority = traceFlag
86
86
 
87
- ts = ts?.traceparent || null
87
+ ts = ts?.traceparent
88
88
 
89
89
  if (ts) {
90
90
  // Use TraceState.fromString to parse the tracestate header
@@ -101,19 +101,17 @@ class Tracer {
101
101
  // Assuming ddTraceStateData is now a Map or similar structure containing Datadog trace state data
102
102
  // Extract values as needed, similar to the original logic
103
103
  const samplingPriorityTs = ddTraceStateData.get('s')
104
- const origin = ddTraceStateData.get('o')
104
+ origin = ddTraceStateData.get('o') ?? null
105
105
  // Convert Map to object for meta
106
106
  const otherPropagatedTags = Object.fromEntries(ddTraceStateData.entries())
107
107
 
108
108
  // Update meta and samplingPriority based on extracted values
109
109
  Object.assign(meta, otherPropagatedTags)
110
- samplingPriority = TextMapPropagator._getSamplingPriority(
111
- traceFlag,
112
- Number.parseInt(samplingPriorityTs, 10),
113
- origin
114
- )
110
+ // Guard against an undefined/empty `s:` field that would result in NaN.
111
+ const tracestateSamplingPriority = samplingPriorityTs ? Math.trunc(samplingPriorityTs) : undefined
112
+ samplingPriority = TextMapPropagator._getSamplingPriority(traceFlag, tracestateSamplingPriority, origin)
115
113
  } else {
116
- log.debug('no dd list member in tracestate from incoming request:', ts)
114
+ log.debug('No dd list member in tracestate from incoming request:', ts)
117
115
  }
118
116
  }
119
117
 
@@ -121,8 +119,8 @@ class Tracer {
121
119
  traceId: id(traceId, 16), spanId: id(), tags: meta, parentId: id(spanId, 16),
122
120
  })
123
121
 
124
- spanContext._sampling = { priority: samplingPriority }
125
- spanContext._trace = { origin }
122
+ spanContext._ddContext._sampling = { priority: samplingPriority }
123
+ spanContext._ddContext._trace = { ...spanContext._ddContext._trace, origin }
126
124
  return spanContext
127
125
  }
128
126
 
@@ -802,18 +802,25 @@ class TextMapPropagator {
802
802
  return spanContext._traceId.toString(16)
803
803
  }
804
804
 
805
- static _getSamplingPriority (traceparentSampled, tracestateSamplingPriority, origin = null) {
805
+ /**
806
+ * @param {number} traceparentSampled
807
+ * @param {number|undefined} tracestateSamplingPriority
808
+ * @param {string|null} origin
809
+ * @returns {import('../../priority_sampler').SamplingPriority}
810
+ */
811
+ static _getSamplingPriority (traceparentSampled, tracestateSamplingPriority, origin) {
806
812
  const fromRumWithoutPriority = !tracestateSamplingPriority && origin === 'rum'
807
813
 
808
- let samplingPriority
809
- if (!fromRumWithoutPriority && traceparentSampled === 0 &&
810
- (!tracestateSamplingPriority || tracestateSamplingPriority >= 0)) {
811
- samplingPriority = 0
812
- } else if (!fromRumWithoutPriority && traceparentSampled === 1 &&
813
- (!tracestateSamplingPriority || tracestateSamplingPriority < 0)) {
814
- samplingPriority = 1
815
- } else {
816
- samplingPriority = tracestateSamplingPriority
814
+ let samplingPriority =
815
+ /** @type {import('../../priority_sampler').SamplingPriority} */ (tracestateSamplingPriority ?? AUTO_KEEP)
816
+ if (!fromRumWithoutPriority) {
817
+ if (traceparentSampled === 0 &&
818
+ (!tracestateSamplingPriority || tracestateSamplingPriority >= 0)) {
819
+ samplingPriority = AUTO_REJECT
820
+ } else if (traceparentSampled === 1 &&
821
+ (!tracestateSamplingPriority || tracestateSamplingPriority < 0)) {
822
+ samplingPriority = AUTO_KEEP
823
+ }
817
824
  }
818
825
 
819
826
  return samplingPriority
@@ -149,7 +149,7 @@ class DatadogSpan {
149
149
  }
150
150
 
151
151
  /**
152
- * @returns {import('../priority_sampler').DatadogSpanContext}
152
+ * @returns {import('./span_context')}
153
153
  */
154
154
  context () {
155
155
  return this._spanContext
@@ -20,7 +20,7 @@ const REFERENCE_CHILD_OF = 'child_of'
20
20
  const REFERENCE_FOLLOWS_FROM = 'follows_from'
21
21
 
22
22
  class DatadogTracer {
23
- constructor (config, prioritySampler, exporter) {
23
+ constructor (config, prioritySampler) {
24
24
  this._config = config
25
25
  this._service = config.service
26
26
  this._version = config.version
@@ -29,8 +29,15 @@ class DatadogTracer {
29
29
  this._debug = config.debug
30
30
  this._prioritySampler = prioritySampler ?? new PrioritySampler(config.env, config.sampler)
31
31
 
32
- if (exporter) {
33
- this._exporter = exporter
32
+ // OTEL_TRACES_EXPORTER=otlp should not replace the Test Optimization
33
+ // exporter when the tracer is running in Test Optimization mode. Test spans
34
+ // (test_session/test_module/ test_suite/test) belong on the citestcycle
35
+ // endpoint, not on an OTLP traces endpoint — otherwise users with OTEL_*
36
+ // vars set in their environment (e.g. for a separate telemetry integration)
37
+ // silently lose all test spans.
38
+ if (config.OTEL_TRACES_EXPORTER === 'otlp' && !config.isCiVisibility) {
39
+ const { createOtlpTraceExporter } = require('../opentelemetry/trace')
40
+ this._exporter = createOtlpTraceExporter(config)
34
41
  } else {
35
42
  const Exporter = getExporter(config.experimental.exporter)
36
43
  this._exporter = new Exporter(config, this._prioritySampler)
@@ -43,7 +50,7 @@ class DatadogTracer {
43
50
  this._propagators = {
44
51
  [formats.TEXT_MAP]: new TextMapPropagator(config),
45
52
  [formats.HTTP_HEADERS]: new HttpPropagator(config),
46
- [formats.BINARY]: new BinaryPropagator(config),
53
+ [formats.BINARY]: new BinaryPropagator(),
47
54
  [formats.LOG]: new LogPropagator(config),
48
55
  [formats.TEXT_MAP_DSM]: new DSMTextMapPropagator(config),
49
56
  }
@@ -116,7 +123,7 @@ class DatadogTracer {
116
123
  * Get the span context from a span or a span context.
117
124
  *
118
125
  * @param {Span|SpanContext} spanContext
119
- * @returns {SpanContext}
126
+ * @returns {SpanContext|null}
120
127
  */
121
128
  function getContext (spanContext) {
122
129
  if (spanContext instanceof Span) {
@@ -6,6 +6,7 @@ const plugins = {
6
6
  get '@aws-sdk/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
7
7
  get '@azure/event-hubs' () { return require('../../../datadog-plugin-azure-event-hubs/src') },
8
8
  get '@azure/functions' () { return require('../../../datadog-plugin-azure-functions/src') },
9
+ get '@modelcontextprotocol/sdk' () { return require('../../../datadog-plugin-modelcontextprotocol-sdk/src') },
9
10
  get 'durable-functions' () { return require('../../../datadog-plugin-azure-durable-functions/src') },
10
11
  get '@azure/service-bus' () { return require('../../../datadog-plugin-azure-service-bus/src') },
11
12
  get '@cucumber/cucumber' () { return require('../../../datadog-plugin-cucumber/src') },