dd-trace 5.98.0 → 5.99.1

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 (139) 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 +68 -47
  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/jest.js +5 -5
  17. package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +7 -0
  18. package/packages/datadog-instrumentations/src/pino.js +4 -28
  19. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +27 -0
  20. package/packages/datadog-instrumentations/src/playwright.js +5 -17
  21. package/packages/datadog-instrumentations/src/stripe.js +38 -24
  22. package/packages/datadog-instrumentations/src/vitest.js +32 -4
  23. package/packages/datadog-instrumentations/src/zlib.js +29 -0
  24. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -2
  25. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +8 -15
  26. package/packages/datadog-plugin-azure-service-bus/src/producer.js +4 -9
  27. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  28. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -5
  29. package/packages/datadog-plugin-cypress/src/source-map-utils.js +48 -1
  30. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -1
  31. package/packages/datadog-plugin-graphql/src/utils.js +2 -2
  32. package/packages/datadog-plugin-http/src/server.js +11 -11
  33. package/packages/datadog-plugin-jest/src/index.js +2 -2
  34. package/packages/datadog-plugin-memcached/src/index.js +1 -1
  35. package/packages/datadog-plugin-mocha/src/index.js +1 -2
  36. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/index.js +24 -0
  37. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +55 -0
  38. package/packages/datadog-plugin-mongodb-core/src/index.js +1 -6
  39. package/packages/datadog-plugin-playwright/src/index.js +2 -3
  40. package/packages/datadog-plugin-vitest/src/index.js +14 -6
  41. package/packages/datadog-plugin-ws/src/close.js +2 -0
  42. package/packages/datadog-plugin-ws/src/producer.js +2 -0
  43. package/packages/datadog-plugin-ws/src/receiver.js +1 -0
  44. package/packages/dd-trace/src/aiguard/channels.js +8 -0
  45. package/packages/dd-trace/src/aiguard/index.js +7 -3
  46. package/packages/dd-trace/src/aiguard/sdk.js +44 -0
  47. package/packages/dd-trace/src/aiguard/tags.js +1 -0
  48. package/packages/dd-trace/src/appsec/blocking.js +18 -6
  49. package/packages/dd-trace/src/appsec/graphql.js +7 -7
  50. package/packages/dd-trace/src/appsec/index.js +9 -11
  51. package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -5
  52. package/packages/dd-trace/src/appsec/rasp/lfi.js +8 -4
  53. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +5 -10
  54. package/packages/dd-trace/src/appsec/rasp/ssrf.js +5 -6
  55. package/packages/dd-trace/src/appsec/recommended.json +2438 -13
  56. package/packages/dd-trace/src/appsec/reporter.js +6 -5
  57. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +4 -8
  58. package/packages/dd-trace/src/appsec/store.js +50 -0
  59. package/packages/dd-trace/src/appsec/waf/index.js +3 -5
  60. package/packages/dd-trace/src/baggage.js +16 -13
  61. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -2
  62. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -2
  63. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +2 -2
  64. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -2
  65. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +1 -1
  66. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +3 -4
  67. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -2
  68. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +4 -5
  69. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +3 -4
  70. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +6 -6
  71. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +2 -2
  72. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +2 -2
  73. package/packages/dd-trace/src/config/config-types.d.ts +0 -4
  74. package/packages/dd-trace/src/config/defaults.js +10 -10
  75. package/packages/dd-trace/src/config/generated-config-types.d.ts +39 -38
  76. package/packages/dd-trace/src/config/index.js +29 -39
  77. package/packages/dd-trace/src/config/parsers.js +26 -9
  78. package/packages/dd-trace/src/config/supported-configurations.json +46 -78
  79. package/packages/dd-trace/src/debugger/config.js +2 -0
  80. package/packages/dd-trace/src/debugger/devtools_client/send.js +25 -5
  81. package/packages/dd-trace/src/dogstatsd.js +5 -8
  82. package/packages/dd-trace/src/encode/0.4.js +4 -5
  83. package/packages/dd-trace/src/exporter.js +1 -1
  84. package/packages/dd-trace/src/exporters/agent/index.js +0 -1
  85. package/packages/dd-trace/src/exporters/agent/writer.js +1 -2
  86. package/packages/dd-trace/src/exporters/agentless/writer.js +3 -3
  87. package/packages/dd-trace/src/exporters/common/util.js +2 -2
  88. package/packages/dd-trace/src/git_metadata_tagger.js +1 -1
  89. package/packages/dd-trace/src/id.js +2 -0
  90. package/packages/dd-trace/src/index.js +2 -5
  91. package/packages/dd-trace/src/lambda/handler.js +1 -3
  92. package/packages/dd-trace/src/llmobs/constants/tags.js +3 -0
  93. package/packages/dd-trace/src/llmobs/plugins/{anthropic.js → anthropic/index.js} +5 -63
  94. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +106 -0
  95. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +3 -2
  96. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +3 -2
  97. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +2 -1
  98. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +0 -49
  99. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/vectorstore.js +2 -1
  100. package/packages/dd-trace/src/llmobs/plugins/langchain/messages.js +76 -0
  101. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -26
  102. package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/index.js +68 -0
  103. package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/utils.js +57 -0
  104. package/packages/dd-trace/src/llmobs/sdk.js +23 -3
  105. package/packages/dd-trace/src/llmobs/span_processor.js +14 -1
  106. package/packages/dd-trace/src/llmobs/writers/base.js +7 -1
  107. package/packages/dd-trace/src/llmobs/writers/spans.js +1 -1
  108. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +103 -0
  109. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -0
  110. package/packages/dd-trace/src/opentelemetry/logs/index.js +6 -6
  111. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +3 -2
  112. package/packages/dd-trace/src/opentelemetry/metrics/index.js +7 -7
  113. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +3 -2
  114. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +19 -66
  115. package/packages/dd-trace/src/opentelemetry/trace/index.js +11 -16
  116. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +11 -3
  117. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +51 -41
  118. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -11
  119. package/packages/dd-trace/src/opentracing/propagation/text_map.js +30 -23
  120. package/packages/dd-trace/src/opentracing/span.js +2 -2
  121. package/packages/dd-trace/src/opentracing/tracer.js +12 -5
  122. package/packages/dd-trace/src/plugin_manager.js +6 -6
  123. package/packages/dd-trace/src/plugins/index.js +1 -0
  124. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  125. package/packages/dd-trace/src/plugins/util/test.js +128 -7
  126. package/packages/dd-trace/src/plugins/util/url.js +2 -1
  127. package/packages/dd-trace/src/profiling/profilers/event_plugins/crypto.js +32 -0
  128. package/packages/dd-trace/src/profiling/profilers/event_plugins/zlib.js +19 -0
  129. package/packages/dd-trace/src/profiling/profilers/events.js +35 -0
  130. package/packages/dd-trace/src/proxy.js +8 -14
  131. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -2
  132. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
  133. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
  134. package/packages/dd-trace/src/span_processor.js +1 -2
  135. package/packages/dd-trace/src/tagger.js +2 -2
  136. package/packages/dd-trace/src/telemetry/send-data.js +5 -7
  137. package/packages/dd-trace/src/tracer.js +2 -2
  138. package/vendor/dist/ignore/LICENSE +0 -21
  139. package/vendor/dist/ignore/index.js +0 -1
@@ -45,7 +45,7 @@ class LLMObsSpanWriter extends BaseWriter {
45
45
  this.flush()
46
46
  }
47
47
 
48
- super.append(event, routing, processedEventSizeBytes)
48
+ return super.append(event, routing, processedEventSizeBytes)
49
49
  }
50
50
 
51
51
  makePayload (events) {
@@ -0,0 +1,103 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+
5
+ const METER_NAME = 'dd-trace-js/openfeature'
6
+ const COUNTER_NAME = 'feature_flag.evaluations'
7
+ const COUNTER_DESCRIPTION = 'Number of feature flag evaluations'
8
+ const COUNTER_UNIT = '{evaluation}'
9
+
10
+ /**
11
+ * OpenFeature hook that tracks feature flag evaluation metrics using an
12
+ * OpenTelemetry counter.
13
+ *
14
+ * Implements the OpenFeature `finally` hook interface so it can be pushed
15
+ * directly onto a provider's `hooks` array. We use the `finally` stage
16
+ * (not diagnostic channels inside the provider's `resolve*` methods) because
17
+ * the OpenFeature SDK short-circuits before calling the provider when it is in
18
+ * NOT_READY state; the `finally` hook still fires, ensuring all evaluations are
19
+ * captured. It also catches type-mismatch errors detected by the SDK client
20
+ * after the provider returns.
21
+ *
22
+ * The counter is created lazily on the first successful `finally()` call rather
23
+ * than in the constructor. This is necessary because `FlaggingProvider` is
24
+ * constructed eagerly by `proxy.js#updateTracing()`, which runs *before*
25
+ * `initializeOpenTelemetryMetrics()` sets the global OTel meter provider.
26
+ * Calling `getMeter()` in the constructor would return the noop meter and
27
+ * produce a noop counter that silently discards all measurements. By deferring
28
+ * to `finally()` time we give the meter provider a chance to be set up first.
29
+ *
30
+ * If counter creation fails (e.g. the OTel API is not yet available), the call
31
+ * is silently skipped and retried on the next `finally()` invocation.
32
+ *
33
+ * When `config.otelMetricsEnabled` is false, `finally()` is always a no-op.
34
+ */
35
+ class EvalMetricsHook {
36
+ #enabled = false
37
+ #counter = null
38
+
39
+ /**
40
+ * @param {import('../config')} config - Tracer configuration object
41
+ */
42
+ constructor (config) {
43
+ this.#enabled = config.otelMetricsEnabled === true
44
+ }
45
+
46
+ /**
47
+ * Returns the OTel counter, creating it on first successful call.
48
+ * Returns `null` if counter creation fails; will retry on next call.
49
+ *
50
+ * @returns {import('@opentelemetry/api').Counter | null}
51
+ */
52
+ #getCounter () {
53
+ if (this.#counter) return this.#counter
54
+
55
+ try {
56
+ const { metrics } = require('@opentelemetry/api')
57
+ const meter = metrics.getMeter(METER_NAME)
58
+ this.#counter = meter.createCounter(COUNTER_NAME, {
59
+ description: COUNTER_DESCRIPTION,
60
+ unit: COUNTER_UNIT,
61
+ })
62
+ } catch (e) {
63
+ log.warn('EvalMetricsHook: failed to create counter: %s', e.message)
64
+ }
65
+
66
+ return this.#counter
67
+ }
68
+
69
+ /**
70
+ * Called by the OpenFeature SDK after every flag evaluation (success or error).
71
+ *
72
+ * @param {{ flagKey: string }} hookContext - Hook context containing the flag key
73
+ * @param {{ variant?: string, reason?: string, errorCode?: string, flagMetadata?: object }} evaluationDetails
74
+ * - Full evaluation details
75
+ * @returns {void}
76
+ */
77
+ finally (hookContext, evaluationDetails) {
78
+ if (!this.#enabled) return
79
+
80
+ const counter = this.#getCounter()
81
+ if (!counter) return
82
+
83
+ const attributes = {
84
+ 'feature_flag.key': hookContext?.flagKey ?? '',
85
+ 'feature_flag.result.variant': evaluationDetails?.variant ?? '',
86
+ 'feature_flag.result.reason': evaluationDetails?.reason?.toLowerCase() ?? 'unknown',
87
+ }
88
+
89
+ const errorCode = evaluationDetails?.errorCode
90
+ if (errorCode) {
91
+ attributes['error.type'] = errorCode.toLowerCase()
92
+ }
93
+
94
+ const allocationKey = evaluationDetails?.flagMetadata?.allocationKey
95
+ if (allocationKey) {
96
+ attributes['feature_flag.result.allocation_key'] = allocationKey
97
+ }
98
+
99
+ counter.add(1, attributes)
100
+ }
101
+ }
102
+
103
+ module.exports = EvalMetricsHook
@@ -4,6 +4,7 @@ const { DatadogNodeServerProvider } = require('@datadog/openfeature-node-server'
4
4
  const { channel } = require('dc-polyfill')
5
5
  const log = require('../log')
6
6
  const { EXPOSURE_CHANNEL } = require('./constants/constants')
7
+ const EvalMetricsHook = require('./eval-metrics-hook')
7
8
 
8
9
  /**
9
10
  * OpenFeature provider that integrates with Datadog's feature flagging system.
@@ -24,6 +25,8 @@ class FlaggingProvider extends DatadogNodeServerProvider {
24
25
  this._tracer = tracer
25
26
  this._config = config
26
27
 
28
+ this.hooks.push(new EvalMetricsHook(config))
29
+
27
30
  log.debug('%s created with timeout: %dms', this.constructor.name,
28
31
  config.experimental.flaggingProvider.initializationTimeoutMs)
29
32
  }
@@ -60,18 +60,18 @@ function initializeOpenTelemetryLogs (config) {
60
60
 
61
61
  // Create OTLP exporter using resolved config values
62
62
  const exporter = new OtlpHttpLogExporter(
63
- config.otelLogsUrl,
64
- config.otelLogsHeaders,
65
- config.otelLogsTimeout,
66
- config.otelLogsProtocol,
63
+ config.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
64
+ config.OTEL_EXPORTER_OTLP_LOGS_HEADERS,
65
+ config.OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
66
+ config.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
67
67
  resourceAttributes
68
68
  )
69
69
 
70
70
  // Create batch processor for exporting logs to Datadog Agent
71
71
  const processor = new BatchLogRecordProcessor(
72
72
  exporter,
73
- config.otelBatchTimeout,
74
- config.otelMaxExportBatchSize
73
+ config.OTEL_BSP_SCHEDULE_DELAY,
74
+ config.OTEL_BSP_MAX_EXPORT_BATCH_SIZE
75
75
  )
76
76
 
77
77
  // Create logger provider with processor for Datadog Agent export
@@ -22,13 +22,14 @@ class OtlpHttpLogExporter extends OtlpHttpExporterBase {
22
22
  * Creates a new OtlpHttpLogExporter instance.
23
23
  *
24
24
  * @param {string} url - OTLP endpoint URL
25
- * @param {string} headers - Additional HTTP headers as comma-separated key=value string
25
+ * @param {Record<string, string>|undefined} headers - Additional HTTP headers parsed from the
26
+ * corresponding `OTEL_EXPORTER_OTLP_*_HEADERS` env by the MAP parser.
26
27
  * @param {number} timeout - Request timeout in milliseconds
27
28
  * @param {string} protocol - OTLP protocol (http/protobuf or http/json)
28
29
  * @param {Resource} resource - Resource attributes
29
30
  */
30
31
  constructor (url, headers, timeout, protocol, resource) {
31
- super(url, headers, timeout, protocol, '/v1/logs', 'logs')
32
+ super(url, headers, timeout, protocol, 'logs')
32
33
  this.transformer = new OtlpTransformer(resource, protocol)
33
34
  }
34
35
 
@@ -57,18 +57,18 @@ function initializeOpenTelemetryMetrics (config) {
57
57
  }
58
58
 
59
59
  const exporter = new OtlpHttpMetricExporter(
60
- config.otelMetricsUrl,
61
- config.otelMetricsHeaders,
62
- config.otelMetricsTimeout,
63
- config.otelMetricsProtocol,
60
+ config.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
61
+ config.OTEL_EXPORTER_OTLP_METRICS_HEADERS,
62
+ config.OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
63
+ config.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
64
64
  resourceAttributes
65
65
  )
66
66
 
67
67
  const reader = new PeriodicMetricReader(
68
68
  exporter,
69
- config.otelMetricsExportInterval,
70
- config.otelMetricsTemporalityPreference,
71
- config.otelMaxQueueSize
69
+ config.OTEL_METRIC_EXPORT_INTERVAL,
70
+ config.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE,
71
+ config.OTEL_BSP_MAX_QUEUE_SIZE
72
72
  )
73
73
 
74
74
  const meterProvider = new MeterProvider({ reader })
@@ -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
+ * `OTEL_BSP_SCHEDULE_DELAY`, `OTEL_BSP_MAX_EXPORT_BATCH_SIZE`, and `OTEL_BSP_MAX_QUEUE_SIZE`
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.