dd-trace 5.97.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 (175) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/ext/tags.js +1 -0
  3. package/index.d.ts +35 -3
  4. package/package.json +48 -46
  5. package/packages/datadog-instrumentations/src/crypto.js +45 -0
  6. package/packages/datadog-instrumentations/src/cucumber.js +65 -3
  7. package/packages/datadog-instrumentations/src/cypress-config.js +153 -53
  8. package/packages/datadog-instrumentations/src/dns.js +24 -56
  9. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  10. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +74 -0
  11. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +4 -1
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +10 -3
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +59 -0
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +11 -2
  17. package/packages/datadog-instrumentations/src/jest.js +104 -12
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +8 -0
  19. package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +7 -0
  20. package/packages/datadog-instrumentations/src/pino.js +4 -28
  21. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +27 -0
  22. package/packages/datadog-instrumentations/src/playwright.js +5 -17
  23. package/packages/datadog-instrumentations/src/redis.js +12 -6
  24. package/packages/datadog-instrumentations/src/stripe.js +38 -24
  25. package/packages/datadog-instrumentations/src/vitest.js +32 -4
  26. package/packages/datadog-instrumentations/src/zlib.js +29 -0
  27. package/packages/datadog-plugin-aws-sdk/src/base.js +2 -3
  28. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -0
  29. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  30. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
  31. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -0
  32. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +1 -0
  33. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
  34. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +8 -15
  35. package/packages/datadog-plugin-azure-service-bus/src/producer.js +4 -9
  36. package/packages/datadog-plugin-cucumber/src/index.js +8 -2
  37. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +114 -6
  38. package/packages/datadog-plugin-cypress/src/index.js +59 -2
  39. package/packages/datadog-plugin-cypress/src/source-map-utils.js +48 -1
  40. package/packages/datadog-plugin-fs/src/index.js +1 -1
  41. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -1
  42. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -7
  43. package/packages/datadog-plugin-http/src/client.js +1 -1
  44. package/packages/datadog-plugin-http/src/server.js +21 -13
  45. package/packages/datadog-plugin-http2/src/client.js +1 -1
  46. package/packages/datadog-plugin-http2/src/server.js +10 -2
  47. package/packages/datadog-plugin-jest/src/index.js +2 -2
  48. package/packages/datadog-plugin-mocha/src/index.js +1 -2
  49. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/index.js +24 -0
  50. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +55 -0
  51. package/packages/datadog-plugin-mongodb-core/src/index.js +4 -9
  52. package/packages/datadog-plugin-mysql/src/index.js +1 -1
  53. package/packages/datadog-plugin-next/src/index.js +8 -2
  54. package/packages/datadog-plugin-pg/src/index.js +1 -1
  55. package/packages/datadog-plugin-playwright/src/index.js +2 -3
  56. package/packages/datadog-plugin-tedious/src/index.js +1 -1
  57. package/packages/datadog-plugin-vitest/src/index.js +14 -6
  58. package/packages/datadog-plugin-ws/src/close.js +3 -1
  59. package/packages/datadog-plugin-ws/src/producer.js +2 -0
  60. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  61. package/packages/dd-trace/src/aiguard/channels.js +8 -0
  62. package/packages/dd-trace/src/aiguard/index.js +7 -3
  63. package/packages/dd-trace/src/aiguard/sdk.js +66 -22
  64. package/packages/dd-trace/src/aiguard/tags.js +1 -0
  65. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -3
  66. package/packages/dd-trace/src/appsec/blocking.js +62 -34
  67. package/packages/dd-trace/src/appsec/graphql.js +6 -6
  68. package/packages/dd-trace/src/appsec/index.js +9 -11
  69. package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -5
  70. package/packages/dd-trace/src/appsec/rasp/lfi.js +8 -4
  71. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +5 -10
  72. package/packages/dd-trace/src/appsec/rasp/ssrf.js +5 -6
  73. package/packages/dd-trace/src/appsec/recommended.json +2438 -13
  74. package/packages/dd-trace/src/appsec/reporter.js +6 -5
  75. package/packages/dd-trace/src/appsec/sdk/set_user.js +1 -1
  76. package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -5
  77. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +6 -10
  78. package/packages/dd-trace/src/appsec/sdk/utils.js +4 -2
  79. package/packages/dd-trace/src/appsec/store.js +50 -0
  80. package/packages/dd-trace/src/appsec/waf/index.js +3 -5
  81. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -2
  82. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -2
  83. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +2 -2
  84. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -2
  85. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +3 -4
  86. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -2
  87. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +4 -5
  88. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +3 -4
  89. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +6 -6
  90. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +2 -2
  91. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +2 -2
  92. package/packages/dd-trace/src/config/config-types.d.ts +0 -4
  93. package/packages/dd-trace/src/config/defaults.js +10 -11
  94. package/packages/dd-trace/src/config/generated-config-types.d.ts +14 -8
  95. package/packages/dd-trace/src/config/index.js +49 -32
  96. package/packages/dd-trace/src/config/parsers.js +26 -9
  97. package/packages/dd-trace/src/config/supported-configurations.json +86 -33
  98. package/packages/dd-trace/src/constants.js +1 -0
  99. package/packages/dd-trace/src/debugger/config.js +2 -0
  100. package/packages/dd-trace/src/debugger/devtools_client/send.js +25 -5
  101. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +5 -2
  102. package/packages/dd-trace/src/encode/0.4.js +11 -11
  103. package/packages/dd-trace/src/encode/span-stats.js +4 -1
  104. package/packages/dd-trace/src/exporters/agent/index.js +0 -1
  105. package/packages/dd-trace/src/exporters/agent/writer.js +1 -2
  106. package/packages/dd-trace/src/exporters/agentless/writer.js +3 -3
  107. package/packages/dd-trace/src/exporters/common/util.js +2 -2
  108. package/packages/dd-trace/src/id.js +2 -0
  109. package/packages/dd-trace/src/index.js +2 -5
  110. package/packages/dd-trace/src/lambda/handler.js +1 -3
  111. package/packages/dd-trace/src/llmobs/plugins/{anthropic.js → anthropic/index.js} +5 -63
  112. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +106 -0
  113. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +3 -2
  114. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +3 -2
  115. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +2 -1
  116. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +0 -49
  117. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/vectorstore.js +2 -1
  118. package/packages/dd-trace/src/llmobs/plugins/langchain/messages.js +76 -0
  119. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -26
  120. package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/index.js +68 -0
  121. package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/utils.js +57 -0
  122. package/packages/dd-trace/src/llmobs/sdk.js +2 -2
  123. package/packages/dd-trace/src/log/index.js +0 -10
  124. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +103 -0
  125. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -0
  126. package/packages/dd-trace/src/openfeature/remote_config.js +6 -1
  127. package/packages/dd-trace/src/opentelemetry/context_manager.js +6 -4
  128. package/packages/dd-trace/src/opentelemetry/logs/index.js +1 -1
  129. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +3 -2
  130. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  131. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +3 -2
  132. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +19 -51
  133. package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +14 -2
  134. package/packages/dd-trace/src/opentelemetry/otlp/trace.proto +358 -0
  135. package/packages/dd-trace/src/opentelemetry/otlp/trace_service.proto +78 -0
  136. package/packages/dd-trace/src/opentelemetry/trace/index.js +70 -0
  137. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +74 -0
  138. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +342 -0
  139. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -11
  140. package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -10
  141. package/packages/dd-trace/src/opentracing/span.js +1 -1
  142. package/packages/dd-trace/src/opentracing/tracer.js +17 -5
  143. package/packages/dd-trace/src/plugins/index.js +1 -0
  144. package/packages/dd-trace/src/plugins/log_plugin.js +3 -0
  145. package/packages/dd-trace/src/plugins/plugin.js +6 -11
  146. package/packages/dd-trace/src/plugins/storage.js +2 -2
  147. package/packages/dd-trace/src/plugins/tracing.js +22 -5
  148. package/packages/dd-trace/src/plugins/util/test.js +128 -5
  149. package/packages/dd-trace/src/plugins/util/url.js +2 -1
  150. package/packages/dd-trace/src/plugins/util/web.js +6 -88
  151. package/packages/dd-trace/src/profiling/profiler.js +34 -77
  152. package/packages/dd-trace/src/profiling/profilers/event_plugins/crypto.js +32 -0
  153. package/packages/dd-trace/src/profiling/profilers/event_plugins/zlib.js +19 -0
  154. package/packages/dd-trace/src/profiling/profilers/events.js +35 -0
  155. package/packages/dd-trace/src/proxy.js +3 -4
  156. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -13
  157. package/packages/dd-trace/src/service-naming/index.js +1 -1
  158. package/packages/dd-trace/src/service-naming/schemas/definition.js +4 -1
  159. package/packages/dd-trace/src/service-naming/schemas/util.js +15 -1
  160. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +24 -1
  161. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +60 -0
  162. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +21 -1
  163. package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +5 -0
  164. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +17 -0
  165. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +15 -1
  166. package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +6 -0
  167. package/packages/dd-trace/src/span_processor.js +1 -2
  168. package/packages/dd-trace/src/span_stats.js +5 -1
  169. package/packages/dd-trace/src/tagger.js +2 -2
  170. package/packages/dd-trace/src/telemetry/send-data.js +5 -7
  171. package/vendor/dist/@apm-js-collab/code-transformer/index.js +28 -6
  172. package/vendor/dist/protobufjs/index.js +1 -1
  173. package/packages/dd-trace/src/log/utils.js +0 -16
  174. package/vendor/dist/ignore/LICENSE +0 -21
  175. package/vendor/dist/ignore/index.js +0 -1
@@ -0,0 +1,342 @@
1
+ 'use strict'
2
+
3
+ const OtlpTransformerBase = require('../otlp/otlp_transformer_base')
4
+ const { getProtobufTypes } = require('../otlp/protobuf_loader')
5
+ const { VERSION } = require('../../../../../version')
6
+ const id = require('../../id')
7
+
8
+ const { protoSpanKind } = getProtobufTypes()
9
+ const SPAN_KIND_UNSPECIFIED = protoSpanKind.values.SPAN_KIND_UNSPECIFIED
10
+ const SPAN_KIND_INTERNAL = protoSpanKind.values.SPAN_KIND_INTERNAL
11
+ const SPAN_KIND_SERVER = protoSpanKind.values.SPAN_KIND_SERVER
12
+ const SPAN_KIND_CLIENT = protoSpanKind.values.SPAN_KIND_CLIENT
13
+ const SPAN_KIND_PRODUCER = protoSpanKind.values.SPAN_KIND_PRODUCER
14
+ const SPAN_KIND_CONSUMER = protoSpanKind.values.SPAN_KIND_CONSUMER
15
+
16
+ // Cached zero Identifier used to detect zero IDs without re-allocating per span.
17
+ const ZERO_ID = id('0')
18
+
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
+ *
34
+ * @typedef {object} DDFormattedSpan
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
38
+ * @property {string} name - Span operation name
39
+ * @property {string} resource - Resource name
40
+ * @property {string} [service] - Service name
41
+ * @property {string} [type] - Span type
42
+ * @property {number} error - Error flag (0 or 1)
43
+ * @property {{[key: string]: string}} meta - String key-value tags
44
+ * @property {{[key: string]: number}} metrics - Numeric key-value tags
45
+ * @property {{[key: string]: object}} [meta_struct] - Structured tags (JSON-serialized, bytes in protobuf)
46
+ * @property {number} start - Start time in nanoseconds since epoch
47
+ * @property {number} duration - Duration in nanoseconds
48
+ * @property {DDSpanEvent[]} [span_events] - Span events
49
+ */
50
+
51
+ // Map DD span.kind string values to OTLP SpanKind numeric values
52
+ const SPAN_KIND_MAP = {
53
+ internal: SPAN_KIND_INTERNAL,
54
+ server: SPAN_KIND_SERVER,
55
+ client: SPAN_KIND_CLIENT,
56
+ producer: SPAN_KIND_PRODUCER,
57
+ consumer: SPAN_KIND_CONSUMER,
58
+ }
59
+
60
+ // OTLP StatusCode values (from trace.proto Status.StatusCode enum)
61
+ const STATUS_CODE_UNSET = 0
62
+ const STATUS_CODE_ERROR = 2
63
+
64
+ // DD meta keys that are mapped to dedicated OTLP span fields and should not appear as attributes
65
+ const EXCLUDED_META_KEYS = new Set([
66
+ '_dd.span_links',
67
+ 'span.kind',
68
+ ])
69
+
70
+ /**
71
+ * OtlpTraceTransformer transforms DD-formatted spans to OTLP trace JSON format.
72
+ *
73
+ * This implementation follows the OTLP trace data model:
74
+ * https://opentelemetry.io/docs/specs/otlp/#trace-data-model
75
+ *
76
+ * It receives DD-formatted spans (from span_format.js) and produces
77
+ * an ExportTraceServiceRequest serialized as JSON (http/json protocol only).
78
+ *
79
+ * @class OtlpTraceTransformer
80
+ * @augments OtlpTransformerBase
81
+ */
82
+ class OtlpTraceTransformer extends OtlpTransformerBase {
83
+ /**
84
+ * Creates a new OtlpTraceTransformer instance.
85
+ *
86
+ * @param {import('@opentelemetry/api').Attributes} resourceAttributes - Resource attributes
87
+ */
88
+ constructor (resourceAttributes) {
89
+ super(resourceAttributes, 'http/json', 'traces')
90
+ }
91
+
92
+ /**
93
+ * Transforms DD-formatted spans to OTLP JSON format.
94
+ *
95
+ * @param {DDFormattedSpan[]} spans - Array of DD-formatted spans to transform
96
+ * @returns {Buffer} JSON-encoded trace data
97
+ */
98
+ transformSpans (spans) {
99
+ const traceData = {
100
+ resourceSpans: [{
101
+ resource: this.transformResource(),
102
+ scopeSpans: this.#transformScopeSpans(spans),
103
+ }],
104
+ }
105
+ return this.serializeToJson(traceData)
106
+ }
107
+
108
+ /**
109
+ * Creates scope spans. DD spans do not carry instrumentation scope info,
110
+ * so all spans are placed under a single default scope.
111
+ *
112
+ * @param {DDFormattedSpan[]} spans - Array of DD-formatted spans
113
+ * @returns {object[]} Array of scope span objects
114
+ */
115
+ #transformScopeSpans (spans) {
116
+ return [{
117
+ scope: {
118
+ name: 'dd-trace-js',
119
+ version: VERSION,
120
+ attributes: [],
121
+ droppedAttributesCount: 0,
122
+ },
123
+ schemaUrl: '',
124
+ spans: spans.map(span => this.#transformSpan(span)),
125
+ }]
126
+ }
127
+
128
+ /**
129
+ * Transforms a single DD-formatted span to an OTLP Span object.
130
+ *
131
+ * @param {DDFormattedSpan} span - DD-formatted span to transform
132
+ * @returns {object} OTLP Span object
133
+ */
134
+ #transformSpan (span) {
135
+ const parentId = span.parent_id
136
+ const links = this.#extractLinks(span.meta?.['_dd.span_links'])
137
+
138
+ return {
139
+ traceId: this.#idToBytes(span.trace_id, 16),
140
+ spanId: this.#idToBytes(span.span_id, 8),
141
+ parentSpanId: (parentId && !parentId.equals(ZERO_ID)) ? this.#idToBytes(parentId, 8) : undefined,
142
+ name: span.resource,
143
+ kind: this.#mapSpanKind(span.meta?.['span.kind']),
144
+ startTimeUnixNano: span.start,
145
+ endTimeUnixNano: span.start + span.duration,
146
+ attributes: this.#buildAttributes(span),
147
+ droppedAttributesCount: 0,
148
+ events: span.span_events?.length ? span.span_events.map(event => this.#transformEvent(event)) : undefined,
149
+ droppedEventsCount: 0,
150
+ links: links.length ? links : undefined,
151
+ droppedLinksCount: 0,
152
+ status: this.#mapStatus(span),
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Builds OTLP attributes from DD span fields.
158
+ * Merges top-level DD fields (service, resource, type), meta (string tags),
159
+ * and metrics (numeric tags) into a single OTLP KeyValue array.
160
+ *
161
+ * @param {DDFormattedSpan} span - DD-formatted span
162
+ * @returns {object[]} Array of OTLP KeyValue objects
163
+ */
164
+ #buildAttributes (span) {
165
+ const attributes = []
166
+
167
+ // Add top-level DD span fields as OTLP attributes
168
+ if (span.service) {
169
+ attributes.push({ key: 'service.name', value: { stringValue: span.service } })
170
+ }
171
+ if (span.name) {
172
+ attributes.push({ key: 'operation.name', value: { stringValue: span.name } })
173
+ }
174
+ if (span.resource) {
175
+ attributes.push({ key: 'resource.name', value: { stringValue: span.resource } })
176
+ }
177
+ if (span.type) {
178
+ attributes.push({ key: 'span.type', value: { stringValue: span.type } })
179
+ }
180
+
181
+ // Add meta string tags, skipping keys that map to dedicated OTLP fields
182
+ if (span.meta) {
183
+ for (const [key, value] of Object.entries(span.meta)) {
184
+ if (EXCLUDED_META_KEYS.has(key)) continue
185
+ attributes.push({ key, value: { stringValue: value } })
186
+ }
187
+ }
188
+
189
+ // Add metrics as numeric attributes
190
+ if (span.metrics) {
191
+ for (const [key, value] of Object.entries(span.metrics)) {
192
+ if (Number.isInteger(value)) {
193
+ attributes.push({ key, value: { intValue: value } })
194
+ } else {
195
+ attributes.push({ key, value: { doubleValue: value } })
196
+ }
197
+ }
198
+ }
199
+
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.
204
+ if (span.meta_struct) {
205
+ for (const [key, value] of Object.entries(span.meta_struct)) {
206
+ const bytes = Buffer.from(JSON.stringify(value))
207
+ attributes.push({ key, value: { bytesValue: bytes.toString('base64') } })
208
+ }
209
+ }
210
+
211
+ return attributes
212
+ }
213
+
214
+ /**
215
+ * Maps a DD span.kind string to an OTLP SpanKind enum value.
216
+ *
217
+ * @param {string | undefined} kind - DD span kind string
218
+ * @returns {number} OTLP SpanKind enum value
219
+ */
220
+ #mapSpanKind (kind) {
221
+ if (!kind) return SPAN_KIND_UNSPECIFIED
222
+ return SPAN_KIND_MAP[kind] ?? SPAN_KIND_UNSPECIFIED
223
+ }
224
+
225
+ /**
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.
229
+ *
230
+ * @param {DDFormattedSpan} span - DD-formatted span
231
+ * @returns {object} OTLP Status object with code and message
232
+ */
233
+ #mapStatus (span) {
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
246
+ }
247
+ return { code: STATUS_CODE_ERROR, message }
248
+ }
249
+
250
+ /**
251
+ * Transforms a DD span event to an OTLP Event object.
252
+ *
253
+ * @param {DDSpanEvent} event - DD span event
254
+ * @returns {object} OTLP Event object
255
+ */
256
+ #transformEvent (event) {
257
+ return {
258
+ timeUnixNano: event.time_unix_nano,
259
+ name: event.name || '',
260
+ attributes: this.transformAttributes(event.attributes ?? {}),
261
+ droppedAttributesCount: 0,
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Extracts and transforms span links from the DD _dd.span_links meta JSON string.
267
+ *
268
+ * @param {string | undefined} spanLinksJson - JSON-encoded array of DD span links
269
+ * @returns {object[]} Array of OTLP Link objects
270
+ */
271
+ #extractLinks (spanLinksJson) {
272
+ if (!spanLinksJson) return []
273
+
274
+ let parsedLinks
275
+ try {
276
+ parsedLinks = JSON.parse(spanLinksJson)
277
+ } catch {
278
+ return []
279
+ }
280
+
281
+ if (!Array.isArray(parsedLinks)) return []
282
+
283
+ return parsedLinks.map(link => this.#transformLink(link))
284
+ }
285
+
286
+ /**
287
+ * Transforms a single DD span link to an OTLP Link object.
288
+ *
289
+ * @param {DDSpanLink} link - DD span link
290
+ * @returns {object} OTLP Link object
291
+ */
292
+ #transformLink (link) {
293
+ return {
294
+ traceId: this.#hexToBytes(link.trace_id, 16),
295
+ spanId: this.#hexToBytes(link.span_id, 8),
296
+ traceState: link.tracestate || '',
297
+ attributes: this.transformAttributes(link.attributes ?? {}),
298
+ droppedAttributesCount: 0,
299
+ flags: link.flags,
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Converts a DD Identifier object to a hex-encoded string of the specified byte length.
305
+ * Pads with leading zeros if the identifier buffer is shorter than the target.
306
+ * Per the OTLP http/json spec, trace-ids and span-ids must be hex-encoded strings.
307
+ *
308
+ * @param {Identifier} identifier - DD Identifier
309
+ * @param {number} targetLength - Target byte length (16 for trace ID, 8 for span ID)
310
+ * @returns {string} Hex-encoded string of the specified length
311
+ */
312
+ #idToBytes (identifier, targetLength) {
313
+ const buffer = identifier.toBuffer()
314
+ if (buffer.length === targetLength) {
315
+ return Buffer.from(buffer).toString('hex')
316
+ }
317
+ if (buffer.length > targetLength) {
318
+ return Buffer.from(buffer.slice(buffer.length - targetLength)).toString('hex')
319
+ }
320
+ // Pad with leading zeros to reach target length.
321
+ const result = Buffer.alloc(targetLength)
322
+ Buffer.from(buffer).copy(result, targetLength - buffer.length)
323
+ return result.toString('hex')
324
+ }
325
+
326
+ /**
327
+ * Normalizes a hex string to the specified byte length.
328
+ * Pads with leading zeros if the hex string is shorter than expected.
329
+ * Per the OTLP http/json spec, trace-ids and span-ids must be hex-encoded strings.
330
+ *
331
+ * @param {string | undefined} hexString - Hex string to normalize
332
+ * @param {number} targetLength - Target byte length
333
+ * @returns {string} Hex-encoded string of the specified length
334
+ */
335
+ #hexToBytes (hexString, targetLength) {
336
+ if (!hexString) return '0'.repeat(targetLength * 2)
337
+ const cleanHex = hexString.startsWith('0x') ? hexString.slice(2) : hexString
338
+ return cleanHex.padStart(targetLength * 2, '0')
339
+ }
340
+ }
341
+
342
+ module.exports = OtlpTraceTransformer
@@ -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
@@ -21,8 +21,6 @@ const REFERENCE_FOLLOWS_FROM = 'follows_from'
21
21
 
22
22
  class DatadogTracer {
23
23
  constructor (config, prioritySampler) {
24
- const Exporter = getExporter(config.experimental.exporter)
25
-
26
24
  this._config = config
27
25
  this._service = config.service
28
26
  this._version = config.version
@@ -30,7 +28,21 @@ class DatadogTracer {
30
28
  this._logInjection = config.logInjection
31
29
  this._debug = config.debug
32
30
  this._prioritySampler = prioritySampler ?? new PrioritySampler(config.env, config.sampler)
33
- this._exporter = new Exporter(config, this._prioritySampler)
31
+
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)
41
+ } else {
42
+ const Exporter = getExporter(config.experimental.exporter)
43
+ this._exporter = new Exporter(config, this._prioritySampler)
44
+ }
45
+
34
46
  this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config)
35
47
  this._url = this._exporter._url
36
48
  this._enableGetRumData = config.experimental.enableGetRumData
@@ -38,7 +50,7 @@ class DatadogTracer {
38
50
  this._propagators = {
39
51
  [formats.TEXT_MAP]: new TextMapPropagator(config),
40
52
  [formats.HTTP_HEADERS]: new HttpPropagator(config),
41
- [formats.BINARY]: new BinaryPropagator(config),
53
+ [formats.BINARY]: new BinaryPropagator(),
42
54
  [formats.LOG]: new LogPropagator(config),
43
55
  [formats.TEXT_MAP_DSM]: new DSMTextMapPropagator(config),
44
56
  }
@@ -111,7 +123,7 @@ class DatadogTracer {
111
123
  * Get the span context from a span or a span context.
112
124
  *
113
125
  * @param {Span|SpanContext} spanContext
114
- * @returns {SpanContext}
126
+ * @returns {SpanContext|null}
115
127
  */
116
128
  function getContext (spanContext) {
117
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') },
@@ -13,6 +13,9 @@ function messageProxy (message, holder) {
13
13
 
14
14
  return target[key]
15
15
  },
16
+ set (target, key, value) {
17
+ return Reflect.set(target, key, value)
18
+ },
16
19
  ownKeys (target) {
17
20
  const ownKeys = Reflect.ownKeys(target)
18
21
  if (!Object.hasOwn(target, 'dd') && Reflect.isExtensible(target)) {
@@ -106,12 +106,6 @@ module.exports = class Plugin {
106
106
  storage('legacy').enterWith({ ...store, span })
107
107
  }
108
108
 
109
- // TODO: Implement filters on resource name for all plugins.
110
- /** Prevents creation of spans here and for all async descendants. */
111
- skip () {
112
- storage('legacy').enterWith({ noop: true })
113
- }
114
-
115
109
  /**
116
110
  * Subscribe to a diagnostic channel with automatic error handling and enable/disable lifecycle.
117
111
  *
@@ -157,8 +151,9 @@ module.exports = class Plugin {
157
151
 
158
152
  if (!store || !store.span) return
159
153
 
160
- if (!store.span._spanContext._tags.error) {
161
- store.span.setTag('error', error || 1)
154
+ const span = /** @type {import('../opentracing/span')} */ (store.span)
155
+ if (!span._spanContext._tags.error) {
156
+ span.setTag('error', error || 1)
162
157
  }
163
158
  }
164
159
 
@@ -167,12 +162,12 @@ module.exports = class Plugin {
167
162
  *
168
163
  * TODO: Remove the overloading with `enabled` and use the config object directly.
169
164
  *
170
- * @param {boolean|import('../config/config-base')} config Either a boolean to enable/disable
171
- * or a configuration object containing at least `{ enabled: boolean }`.
165
+ * @param {boolean | import('../config/config-base') & {enabled: boolean}} config Either a boolean to
166
+ * enable/disable or a configuration object containing at least `{ enabled: boolean }`.
172
167
  */
173
168
  configure (config) {
174
169
  if (typeof config === 'boolean') {
175
- config = { enabled: config }
170
+ config = /** @type {import('../config/config-base') & {enabled: boolean}} */ ({ enabled: config })
176
171
  }
177
172
  this.config = config
178
173
  if (config.enabled) {
@@ -12,8 +12,8 @@ class StoragePlugin extends ClientPlugin {
12
12
  }
13
13
 
14
14
  startSpan (name, options, ctx) {
15
- if (!options.service && this.system) {
16
- options.service = `${this.tracer._service}-${this.system}`
15
+ if (!options.service?.name && this.system) {
16
+ options.service = { name: `${this.tracer._service}-${this.system}`, source: this.system }
17
17
  }
18
18
 
19
19
  return super.startSpan(name, options, ctx)
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { storage } = require('../../../datadog-core')
4
4
  const analyticsSampler = require('../analytics_sampler')
5
- const { COMPONENT } = require('../constants')
5
+ const { COMPONENT, SVC_SRC_KEY } = require('../constants')
6
6
  const Plugin = require('./plugin')
7
7
 
8
8
  class TracingPlugin extends Plugin {
@@ -26,7 +26,7 @@ class TracingPlugin extends Plugin {
26
26
  * @param {string} [opts.type]
27
27
  * @param {string} [opts.id]
28
28
  * @param {string} [opts.kind]
29
- * @returns {string}
29
+ * @returns {{ name: string, source: string | undefined }}
30
30
  */
31
31
  serviceName (opts = {}) {
32
32
  const {
@@ -157,7 +157,8 @@ class TracingPlugin extends Plugin {
157
157
  * @param {string} [options.kind] - The kind of the span.
158
158
  * @param {object} [options.meta] - The meta data for the span.
159
159
  * @param {object} [options.metrics] - The metrics for the span.
160
- * @param {string} [options.service] - The service name.
160
+ * @param {string | { name: string, source?: string }} [options.service] - The service name, or an object with
161
+ * name and source.
161
162
  * @param {number} [options.startTime] - The start time of the span.
162
163
  * @param {string} [options.resource] - The resource name.
163
164
  * @param {string} [options.type] - The type of the span.
@@ -180,24 +181,40 @@ class TracingPlugin extends Plugin {
180
181
  resource,
181
182
  type,
182
183
  } = options
183
-
184
+ let serviceSource
184
185
  const tracer = options.tracer || this.tracer
185
186
  const config = options.config || this.config
186
187
 
188
+ if (service && typeof service === 'object') {
189
+ serviceSource = service.source
190
+ service = service.name
191
+ } else if (service !== undefined) {
192
+ // service is a plain value returned by service naming/config logic
193
+ serviceSource = service ? 'opt.plugin' : undefined
194
+ }
195
+
187
196
  const store = storage('legacy').getStore()
188
197
  if (store && childOf === undefined) {
189
198
  childOf = /** @type {import('../opentracing/span') | undefined} */ (store.span)
190
199
  }
191
200
 
201
+ // clear service source if service is the same as tracer._service
202
+ const serviceName = service || meta?.service
203
+
204
+ if (!serviceName || serviceName === tracer._service) {
205
+ serviceSource = undefined
206
+ }
207
+
192
208
  const span = tracer.startSpan(name, {
193
209
  startTime,
194
210
  childOf,
195
211
  tags: {
196
212
  [COMPONENT]: component,
197
- 'service.name': service || meta?.service || tracer._service,
213
+ 'service.name': serviceName || tracer._service,
198
214
  'resource.name': resource,
199
215
  'span.kind': kind,
200
216
  'span.type': type,
217
+ ...(serviceSource === undefined ? undefined : { [SVC_SRC_KEY]: serviceSource }),
201
218
  ...meta,
202
219
  ...metrics,
203
220
  },