dd-trace 6.9.0 → 6.11.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/README.md +0 -17
  2. package/ci/vitest-no-worker-init-setup.mjs +112 -10
  3. package/index.d.ts +150 -3
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +14 -10
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  13. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  14. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  15. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  16. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  17. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  20. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +634 -0
  21. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  26. package/packages/datadog-instrumentations/src/jest.js +26 -18
  27. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  28. package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
  29. package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
  30. package/packages/datadog-instrumentations/src/mysql.js +45 -15
  31. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  32. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  33. package/packages/datadog-instrumentations/src/pg.js +116 -92
  34. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  35. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  36. package/packages/datadog-instrumentations/src/router.js +2 -0
  37. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
  38. package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
  39. package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
  40. package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
  41. package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  45. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -13
  46. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  47. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  48. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  49. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  50. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  51. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  52. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  53. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +82 -30
  54. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  55. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  56. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  57. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  58. package/packages/datadog-plugin-electron/src/index.js +2 -0
  59. package/packages/datadog-plugin-fs/src/index.js +1 -1
  60. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  61. package/packages/datadog-plugin-jest/src/index.js +11 -4
  62. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  63. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  64. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
  65. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  66. package/packages/datadog-plugin-mocha/src/index.js +606 -40
  67. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  68. package/packages/datadog-plugin-nats/src/index.js +1 -1
  69. package/packages/datadog-plugin-next/src/index.js +1 -19
  70. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  71. package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
  72. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  73. package/packages/datadog-plugin-pg/src/index.js +1 -0
  74. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  75. package/packages/datadog-plugin-vitest/src/index.js +14 -20
  76. package/packages/datadog-plugin-ws/src/util.js +2 -1
  77. package/packages/datadog-webpack/index.js +0 -11
  78. package/packages/dd-trace/index.js +0 -1
  79. package/packages/dd-trace/src/agent/info.js +6 -5
  80. package/packages/dd-trace/src/aiguard/client.js +100 -1
  81. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  82. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  83. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  84. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  85. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  86. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  87. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  88. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  89. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  90. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  91. package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
  92. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  93. package/packages/dd-trace/src/carrier.js +356 -0
  94. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  101. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  102. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  103. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  104. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  105. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  106. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  107. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -0
  109. package/packages/dd-trace/src/config/index.js +13 -2
  110. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  111. package/packages/dd-trace/src/config/supported-configurations.json +24 -0
  112. package/packages/dd-trace/src/datastreams/context.js +6 -1
  113. package/packages/dd-trace/src/datastreams/index.js +2 -0
  114. package/packages/dd-trace/src/datastreams/manager.js +14 -3
  115. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  116. package/packages/dd-trace/src/datastreams/processor.js +16 -13
  117. package/packages/dd-trace/src/datastreams/size.js +5 -0
  118. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  119. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  120. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  121. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  122. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  123. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  124. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  125. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  126. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  127. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  128. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  129. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  130. package/packages/dd-trace/src/llmobs/index.js +34 -2
  131. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  132. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  133. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  134. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  135. package/packages/dd-trace/src/llmobs/util.js +78 -0
  136. package/packages/dd-trace/src/noop/proxy.js +3 -4
  137. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  138. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  139. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  140. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  141. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  142. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  143. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  145. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  146. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  147. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  148. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  149. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  150. package/packages/dd-trace/src/plugin_manager.js +5 -3
  151. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  152. package/packages/dd-trace/src/plugins/index.js +1 -0
  153. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
  154. package/packages/dd-trace/src/plugins/util/test.js +93 -14
  155. package/packages/dd-trace/src/plugins/util/web.js +56 -2
  156. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  157. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  158. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  159. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  160. package/packages/dd-trace/src/proxy.js +38 -29
  161. package/packages/dd-trace/src/ritm.js +9 -2
  162. package/packages/dd-trace/src/serverless.js +37 -0
  163. package/packages/dd-trace/src/span_format.js +1 -0
  164. package/packages/dd-trace/src/span_stats.js +30 -3
  165. package/packages/dd-trace/src/storage-channels.js +86 -0
  166. package/packages/dd-trace/src/tracer.js +9 -2
  167. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  168. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  169. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  170. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  171. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  172. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  173. package/packages/dd-trace/src/feature-registry.js +0 -29
  174. package/packages/dd-trace/src/openfeature/register.js +0 -35
  175. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const { features } = require('../feature-registry')
4
3
  const NoopAppsecSdk = require('../appsec/sdk/noop')
5
4
  const NoopLLMObsSDK = require('../llmobs/noop')
6
5
  const NoopAIGuardSDK = require('../aiguard/noop')
6
+ const NoopFlaggingProvider = require('../openfeature/noop')
7
7
  const NoopDogStatsDClient = require('./dogstatsd')
8
8
  const NoopTracer = require('./tracer')
9
9
 
@@ -12,6 +12,7 @@ const noopAppsec = new NoopAppsecSdk()
12
12
  const noopDogStatsDClient = new NoopDogStatsDClient()
13
13
  const noopLLMObs = new NoopLLMObsSDK(noop)
14
14
  const noopAIGuard = new NoopAIGuardSDK()
15
+ const noopFlaggingProviderInstance = new NoopFlaggingProvider()
15
16
  const noopProfiling = {
16
17
  setCustomLabelKeys () {},
17
18
  runWithLabels (labels, fn) { return fn() },
@@ -25,9 +26,7 @@ class NoopProxy {
25
26
  this.dogstatsd = noopDogStatsDClient
26
27
  this.llmobs = noopLLMObs
27
28
  this.aiguard = noopAIGuard
28
- for (const { name, noop } of Object.values(features)) {
29
- this[name] = noop
30
- }
29
+ this.openfeature = noopFlaggingProviderInstance
31
30
  this.setBaggageItem = (key, value) => {}
32
31
  this.getBaggageItem = (key) => {}
33
32
  this.getAllBaggageItems = () => {}
@@ -1,14 +1,14 @@
1
1
  'use strict'
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
+
5
+ const { DatadogNodeServerProvider } = require('../../../../vendor/dist/@datadog/openfeature-node-server')
4
6
  const log = require('../log')
5
7
  const configurationSource = require('./configuration_source')
6
8
  const { EXPOSURE_CHANNEL } = require('./constants/constants')
7
9
  const EvalMetricsHook = require('./eval-metrics-hook')
8
10
  const SpanEnrichmentHook = require('./span-enrichment-hook')
9
11
 
10
- const { DatadogNodeServerProvider } = require('./require-provider')
11
-
12
12
  /**
13
13
  * OpenFeature provider that integrates with Datadog's feature flagging system.
14
14
  * Extends DatadogNodeServerProvider to add tracer integration and configuration management.
@@ -48,6 +48,22 @@ class FlaggingProvider extends DatadogNodeServerProvider {
48
48
  this.#configurationSource?.start()
49
49
  }
50
50
 
51
+ /**
52
+ * @param {import('@openfeature/core').EvaluationContext} [context]
53
+ * @returns {Promise<void>}
54
+ */
55
+ initialize (context) {
56
+ const promise = super.initialize(context)
57
+
58
+ // `DatadogNodeServerProvider#initialize` starts a timer that is never unref'd, which would
59
+ // otherwise keep an idle process (a short script, a serverless handler) alive for up to
60
+ // `initializationTimeoutMs` while waiting for configuration to arrive.
61
+ // TODO: remove once `@datadog/openfeature-node-server` unrefs this timer itself.
62
+ this.initController?.timeoutId?.unref?.()
63
+
64
+ return promise
65
+ }
66
+
51
67
  /**
52
68
  * Called when the provider is shut down.
53
69
  * Cleans up resources including channel subscriptions.
@@ -6,7 +6,7 @@ const RemoteConfigCapabilities = require('../remote_config/capabilities')
6
6
  * Configures remote config handlers for openfeature feature flagging
7
7
  *
8
8
  * @param {import('../remote_config')} rc - RemoteConfig instance
9
- * @param {() => import('./flagging_provider')} getOpenfeatureProxy
9
+ * @param {() => InstanceType<ReturnType<typeof import('./flagging_provider')>>} getOpenfeatureProxy
10
10
  * @param {boolean} subscribe - Whether Agent Remote Config owns UFC delivery
11
11
  */
12
12
  function enable (rc, getOpenfeatureProxy, subscribe) {
@@ -21,14 +21,14 @@ class ContextManager {
21
21
  const storedSpan = store ? trace.getSpan(store) : null
22
22
 
23
23
  // Convert DD baggage to OTel format
24
- const baggages = getAllBaggageItems()
25
- const hasBaggage = Object.keys(baggages).length > 0
24
+ let entries
25
+ for (const [key, value] of Object.entries(getAllBaggageItems())) {
26
+ entries ??= {}
27
+ entries[key] = { value }
28
+ }
29
+
26
30
  let otelBaggages
27
- if (hasBaggage) {
28
- const entries = {}
29
- for (const [key, value] of Object.entries(baggages)) {
30
- entries[key] = { value }
31
- }
31
+ if (entries !== undefined) {
32
32
  otelBaggages = propagation.createBaggage(entries)
33
33
  }
34
34
 
@@ -42,7 +42,8 @@ class BatchLogRecordProcessor {
42
42
  */
43
43
  onEmit (logRecord, instrumentationScope) {
44
44
  // Store the log record (already enriched by Logger.emit)
45
- this.#logRecords.push({ ...logRecord, instrumentationScope })
45
+ logRecord.instrumentationScope = instrumentationScope
46
+ this.#logRecords.push(logRecord)
46
47
 
47
48
  if (this.#logRecords.length >= this.#maxExportBatchSize) {
48
49
  this.#export()
@@ -1,8 +1,65 @@
1
1
  'use strict'
2
2
 
3
3
  const { context } = require('@opentelemetry/api')
4
- const { sanitizeAttributes } = require('../../../../../vendor/dist/@opentelemetry/core')
4
+ const {
5
+ millisToHrTime,
6
+ sanitizeAttributes,
7
+ timeInputToHrTime,
8
+ } = require('../../../../../vendor/dist/@opentelemetry/core')
5
9
  const { VERSION: packageVersion } = require('../../../../../version')
10
+ const log = require('../../log')
11
+
12
+ const MAX_DATE_MILLISECONDS = 8.64e15
13
+ const NANOSECONDS_PER_SECOND = 1e9
14
+ const MAX_UINT64_SECONDS = 18_446_744_073
15
+ const MAX_UINT64_NANOSECONDS = 709_551_615
16
+ let invalidTimestampWarningLogged = false
17
+
18
+ function isValidHrTime (hrTime) {
19
+ return Array.isArray(hrTime) &&
20
+ hrTime.length === 2 &&
21
+ Number.isInteger(hrTime[0]) &&
22
+ Number.isInteger(hrTime[1]) &&
23
+ hrTime[0] >= 0 &&
24
+ hrTime[1] >= 0 &&
25
+ hrTime[1] < NANOSECONDS_PER_SECOND &&
26
+ (hrTime[0] < MAX_UINT64_SECONDS ||
27
+ (hrTime[0] === MAX_UINT64_SECONDS && hrTime[1] <= MAX_UINT64_NANOSECONDS))
28
+ }
29
+
30
+ function toHrTime (timestamp) {
31
+ if (typeof timestamp === 'number') {
32
+ if (!Number.isFinite(timestamp)) return
33
+
34
+ // Older versions documented numeric timestamps as Unix nanoseconds.
35
+ if (Math.abs(timestamp) > MAX_DATE_MILLISECONDS) {
36
+ const seconds = Math.trunc(timestamp / NANOSECONDS_PER_SECOND)
37
+ const hrTime = [seconds, timestamp - seconds * NANOSECONDS_PER_SECOND]
38
+ return isValidHrTime(hrTime) ? hrTime : undefined
39
+ }
40
+ }
41
+
42
+ try {
43
+ const hrTime = timeInputToHrTime(timestamp)
44
+ return isValidHrTime(hrTime) ? hrTime : undefined
45
+ } catch {
46
+ // Invalid timestamps fall back to the current time in normalizeTimestamp.
47
+ }
48
+ }
49
+
50
+ function normalizeTimestamp (timestamp) {
51
+ if (timestamp !== undefined) {
52
+ const hrTime = toHrTime(timestamp)
53
+ if (hrTime) return hrTime
54
+
55
+ if (!invalidTimestampWarningLogged) {
56
+ invalidTimestampWarningLogged = true
57
+ log.warn('Invalid OpenTelemetry log timestamp; using the current time instead')
58
+ }
59
+ }
60
+
61
+ return millisToHrTime(Date.now())
62
+ }
6
63
 
7
64
  /**
8
65
  * @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
@@ -62,21 +119,20 @@ class Logger {
62
119
  return
63
120
  }
64
121
 
65
- if (logRecord.attributes) {
66
- logRecord.attributes = sanitizeAttributes(logRecord.attributes)
122
+ const record = {
123
+ ...logRecord,
124
+ timestamp: normalizeTimestamp(logRecord.timestamp),
125
+ context: logRecord.context || context.active(),
67
126
  }
68
127
 
69
- // Note: timestamp is in nanoseconds (as defined by OpenTelemetry LogRecord API)
70
- if (!logRecord.timestamp) {
71
- logRecord.timestamp = Number(process.hrtime.bigint())
128
+ if (logRecord.observedTimestamp !== undefined) {
129
+ record.observedTimestamp = normalizeTimestamp(logRecord.observedTimestamp)
72
130
  }
73
-
74
- if (!logRecord.context) {
75
- // Store span context in the log record context for trace correlation
76
- logRecord.context = context.active()
131
+ if (logRecord.attributes) {
132
+ record.attributes = sanitizeAttributes(logRecord.attributes)
77
133
  }
78
134
 
79
- this.loggerProvider.processor.onEmit(logRecord, this.#instrumentationScope)
135
+ this.loggerProvider.processor.onEmit(record, this.#instrumentationScope)
80
136
  }
81
137
  }
82
138
 
@@ -38,6 +38,17 @@ const SEVERITY_MAP = {
38
38
  [SeverityNumber.FATAL4]: 'SEVERITY_NUMBER_FATAL4',
39
39
  }
40
40
 
41
+ const NANOSECONDS_PER_SECOND = 1_000_000_000n
42
+
43
+ /**
44
+ * Converts an OpenTelemetry HrTime to an exact OTLP uint64 decimal string.
45
+ * @param {import('@opentelemetry/api').HrTime} hrTime
46
+ * @returns {string}
47
+ */
48
+ function encodeHrTime (hrTime) {
49
+ return (BigInt(hrTime[0]) * NANOSECONDS_PER_SECOND + BigInt(hrTime[1])).toString()
50
+ }
51
+
41
52
  /**
42
53
  * OtlpTransformer transforms log records to OTLP format.
43
54
  *
@@ -138,13 +149,13 @@ class OtlpTransformer extends OtlpTransformerBase {
138
149
  const spanContext = this.#extractSpanContext(logRecord.context)
139
150
 
140
151
  const result = {
141
- timeUnixNano: logRecord.timestamp,
152
+ timeUnixNano: encodeHrTime(logRecord.timestamp),
142
153
  body: this.#transformBody(logRecord.body),
143
154
  }
144
155
 
145
156
  // Add optional fields only if they are set
146
- if (logRecord.observedTimestamp) {
147
- result.observedTimeUnixNano = logRecord.observedTimestamp
157
+ if (logRecord.observedTimestamp !== undefined) {
158
+ result.observedTimeUnixNano = encodeHrTime(logRecord.observedTimestamp)
148
159
  }
149
160
 
150
161
  if (logRecord.severityNumber !== undefined) {
@@ -10,6 +10,8 @@ const MeterProvider = require('./meter_provider')
10
10
  const PeriodicMetricReader = require('./periodic_metric_reader')
11
11
  const OtlpHttpMetricExporter = require('./otlp_http_metric_exporter')
12
12
 
13
+ const RESERVED_TRACER_TAGS = new Set(['service', 'env', 'version', 'runtime_id', 'runtime-id'])
14
+
13
15
  /**
14
16
  * @typedef {import('../../config')} Config
15
17
  */
@@ -78,7 +80,16 @@ function initializeOpenTelemetryMetrics (config) {
78
80
  metrics.setGlobalMeterProvider(meterProvider)
79
81
  }
80
82
 
81
- function buildResourceAttributes (tags, { reportHostname, otelSemanticsEnabled, service, env, serviceVersion } = {}) {
83
+ /**
84
+ * @param {Record<string, unknown>} tags
85
+ * @param {object} [options]
86
+ * @param {boolean} [options.reportHostname]
87
+ * @param {string} [options.service]
88
+ * @param {string} [options.env]
89
+ * @param {string} [options.serviceVersion]
90
+ * @returns {import('@opentelemetry/api').Attributes}
91
+ */
92
+ function buildResourceAttributes (tags, { reportHostname, service, env, serviceVersion } = {}) {
82
93
  const attrs = {
83
94
  'telemetry.sdk.name': 'datadog',
84
95
  'telemetry.sdk.language': 'nodejs',
@@ -89,14 +100,19 @@ function buildResourceAttributes (tags, { reportHostname, otelSemanticsEnabled,
89
100
  if (env) attrs['deployment.environment.name'] = env
90
101
  if (reportHostname) attrs['host.name'] = os.hostname()
91
102
 
92
- if (!otelSemanticsEnabled) {
93
- if (tags?.['runtime-id']) attrs['datadog.runtime_id'] = tags['runtime-id']
94
- const processTagsObject = processTags.tagsObject
95
- if (processTagsObject) {
96
- for (const key of Object.keys(processTagsObject)) {
97
- attrs[`datadog.${key}`] = processTagsObject[key]
98
- }
99
- }
103
+ if (tags['runtime-id']) attrs['datadog.runtime_id'] = tags['runtime-id']
104
+ const tracerTags = []
105
+ for (const [key, value] of Object.entries(tags)) {
106
+ const valueType = typeof value
107
+ const supported = valueType === 'string' || valueType === 'boolean' ||
108
+ (valueType === 'number' && Number.isFinite(value))
109
+ if (!RESERVED_TRACER_TAGS.has(key) && supported) tracerTags.push(`${key}:${value}`)
110
+ }
111
+ if (tracerTags.length) attrs['datadog.tracer_tags'] = tracerTags
112
+ // Mirrors the legacy v0.6/stats ProcessTags shape (buildProcessTags().tagsArray); keep both in sync.
113
+ const processTagsArray = processTags.tagsArray
114
+ if (processTagsArray.length) {
115
+ attrs['datadog.process_tags'] = processTagsArray
100
116
  }
101
117
  return attrs
102
118
  }
@@ -106,7 +122,6 @@ function createOtlpSpanStatsExporter (config) {
106
122
  const protocol = config.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL || 'http/json'
107
123
  const resourceAttributes = buildResourceAttributes(config.tags, {
108
124
  reportHostname: config.reportHostname,
109
- otelSemanticsEnabled: config.DD_TRACE_OTEL_SEMANTICS_ENABLED,
110
125
  service: config.service,
111
126
  env: config.env,
112
127
  serviceVersion: config.version,
@@ -115,8 +130,6 @@ function createOtlpSpanStatsExporter (config) {
115
130
  config.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
116
131
  protocol,
117
132
  resourceAttributes,
118
- config.DD_TRACE_OTEL_SEMANTICS_ENABLED,
119
- config.service,
120
133
  config.OTEL_EXPORTER_OTLP_METRICS_HEADERS,
121
134
  config.OTEL_EXPORTER_OTLP_METRICS_TIMEOUT
122
135
  )
@@ -11,15 +11,12 @@ class OtlpStatsExporter extends OtlpHttpExporterBase {
11
11
  * @param {string} url
12
12
  * @param {string} protocol
13
13
  * @param {import('@opentelemetry/api').Attributes} resourceAttributes
14
- * @param {boolean} [otelSemanticsEnabled]
15
- * @param {string} [defaultService]
16
14
  * @param {Record<string, string>} [headers]
17
15
  * @param {number} [timeout]
18
16
  */
19
- constructor (url, protocol, resourceAttributes, otelSemanticsEnabled = false, defaultService = '',
20
- headers, timeout = 10_000) {
17
+ constructor (url, protocol, resourceAttributes, headers, timeout = 10_000) {
21
18
  super(url, headers, timeout, protocol, 'span-stats')
22
- this.#transformer = new OtlpStatsTransformer(resourceAttributes, protocol, otelSemanticsEnabled, defaultService)
19
+ this.#transformer = new OtlpStatsTransformer(resourceAttributes, protocol)
23
20
  }
24
21
 
25
22
  /**
@@ -1,10 +1,11 @@
1
1
  'use strict'
2
2
 
3
- const { LogCollapsingLowestDenseDDSketch } = require('../../../../../vendor/dist/@datadog/sketches-js')
4
3
  const OtlpTransformerBase = require('../otlp/otlp_transformer_base')
5
4
  const { getProtobufTypes } = require('../otlp/protobuf_loader')
6
5
  const { GRPC_STATUS_NAMES } = require('../../constants')
7
6
 
7
+ const { stableStringify } = OtlpTransformerBase
8
+
8
9
  const NS_PER_S = 1e9
9
10
 
10
11
  // Must match libdatadog's EXPLICIT_BOUNDS_SECONDS and OTel spanmetrics connector defaults.
@@ -12,6 +13,19 @@ const EXPLICIT_BOUNDS_SECONDS = [
12
13
  0.002, 0.004, 0.006, 0.008, 0.01, 0.05, 0.1, 0.2, 0.4, 0.8, 1, 1.4, 2, 5, 10, 15,
13
14
  ]
14
15
 
16
+ const SPAN_KIND_METRIC_MAP = {
17
+ internal: 'SPAN_KIND_INTERNAL',
18
+ SPAN_KIND_INTERNAL: 'SPAN_KIND_INTERNAL',
19
+ server: 'SPAN_KIND_SERVER',
20
+ SPAN_KIND_SERVER: 'SPAN_KIND_SERVER',
21
+ client: 'SPAN_KIND_CLIENT',
22
+ SPAN_KIND_CLIENT: 'SPAN_KIND_CLIENT',
23
+ producer: 'SPAN_KIND_PRODUCER',
24
+ SPAN_KIND_PRODUCER: 'SPAN_KIND_PRODUCER',
25
+ consumer: 'SPAN_KIND_CONSUMER',
26
+ SPAN_KIND_CONSUMER: 'SPAN_KIND_CONSUMER',
27
+ }
28
+
15
29
  /**
16
30
  * @param {object} sketch
17
31
  * @returns {number[]}
@@ -41,22 +55,16 @@ function getDeltaTemporality () {
41
55
  return _deltaTemporality
42
56
  }
43
57
 
44
- const ERROR_STATUS_ATTR = { key: 'status.code', value: { intValue: 2 } }
58
+ const STATUS_CODE_OK = 'STATUS_CODE_OK'
59
+ const STATUS_CODE_ERROR = 'STATUS_CODE_ERROR'
45
60
 
46
61
  class OtlpStatsTransformer extends OtlpTransformerBase {
47
- #otelSemanticsEnabled
48
- #defaultService
49
-
50
62
  /**
51
63
  * @param {import('@opentelemetry/api').Attributes} resourceAttributes
52
64
  * @param {string} protocol
53
- * @param {boolean} [otelSemanticsEnabled]
54
- * @param {string} [defaultService]
55
65
  */
56
- constructor (resourceAttributes, protocol, otelSemanticsEnabled = false, defaultService = '') {
66
+ constructor (resourceAttributes, protocol) {
57
67
  super(resourceAttributes, protocol, 'span-stats')
58
- this.#otelSemanticsEnabled = otelSemanticsEnabled
59
- this.#defaultService = defaultService
60
68
  }
61
69
 
62
70
  /**
@@ -82,32 +90,35 @@ class OtlpStatsTransformer extends OtlpTransformerBase {
82
90
  const dataPoints = []
83
91
 
84
92
  for (const { timeNs, bucket } of drained) {
93
+ const distributions = new Map()
85
94
  const endTimeNs = timeNs + bucketSizeNs
86
95
  const startNano = isJson ? String(timeNs) : timeNs
87
96
  const endNano = isJson ? String(endTimeNs) : endTimeNs
88
97
 
89
98
  for (const aggStats of bucket.values()) {
90
- const baseAttrs = this.#buildAttributes(aggStats.aggKey)
91
-
92
- if (this.#otelSemanticsEnabled) {
93
- const okDist = new LogCollapsingLowestDenseDDSketch()
94
- okDist.merge(aggStats.topLevelOkDistribution)
95
- okDist.merge(aggStats.nonTopLevelOkDistribution)
96
- const errDist = new LogCollapsingLowestDenseDDSketch()
97
- errDist.merge(aggStats.topLevelErrorDistribution)
98
- errDist.merge(aggStats.nonTopLevelErrorDistribution)
99
- this.#pushPoint(dataPoints, okDist, startNano, endNano, baseAttrs)
100
- this.#pushPoint(dataPoints, errDist, startNano, endNano, [...baseAttrs, ERROR_STATUS_ATTR])
101
- } else {
102
- const tlAttrs = [...baseAttrs, { key: 'datadog.span.top_level', value: { boolValue: true } }]
103
- const ntlAttrs = [...baseAttrs, { key: 'datadog.span.top_level', value: { boolValue: false } }]
104
- this.#pushPoint(dataPoints, aggStats.topLevelOkDistribution, startNano, endNano, tlAttrs)
105
- this.#pushPoint(dataPoints, aggStats.topLevelErrorDistribution, startNano, endNano,
106
- [...tlAttrs, ERROR_STATUS_ATTR])
107
- this.#pushPoint(dataPoints, aggStats.nonTopLevelOkDistribution, startNano, endNano, ntlAttrs)
108
- this.#pushPoint(dataPoints, aggStats.nonTopLevelErrorDistribution, startNano, endNano,
109
- [...ntlAttrs, ERROR_STATUS_ATTR])
110
- }
99
+ const baseAttributes = this.#buildAttributes(aggStats.aggKey)
100
+ const baseKey = stableStringify(baseAttributes)
101
+
102
+ this.#addDistribution(
103
+ distributions, aggStats.topLevelOkDistribution, startNano, endNano,
104
+ baseAttributes, baseKey, true, STATUS_CODE_OK
105
+ )
106
+ this.#addDistribution(
107
+ distributions, aggStats.topLevelErrorDistribution, startNano, endNano,
108
+ baseAttributes, baseKey, true, STATUS_CODE_ERROR
109
+ )
110
+ this.#addDistribution(
111
+ distributions, aggStats.nonTopLevelOkDistribution, startNano, endNano,
112
+ baseAttributes, baseKey, false, STATUS_CODE_OK
113
+ )
114
+ this.#addDistribution(
115
+ distributions, aggStats.nonTopLevelErrorDistribution, startNano, endNano,
116
+ baseAttributes, baseKey, false, STATUS_CODE_ERROR
117
+ )
118
+ }
119
+
120
+ for (const { sketch, startNano, endNano, attributes } of distributions.values()) {
121
+ this.#pushPoint(dataPoints, sketch, startNano, endNano, attributes)
111
122
  }
112
123
  }
113
124
 
@@ -123,8 +134,45 @@ class OtlpStatsTransformer extends OtlpTransformerBase {
123
134
  }]
124
135
  }
125
136
 
126
- #pushPoint (points, sketch, startNano, endNano, attributes) {
137
+ /**
138
+ * @param {Map<string, {
139
+ * sketch: object,
140
+ * startNano: string | number,
141
+ * endNano: string | number,
142
+ * attributes: object[]
143
+ * }>} distributions
144
+ * @param {object} sketch
145
+ * @param {string | number} startNano
146
+ * @param {string | number} endNano
147
+ * @param {import('@opentelemetry/api').Attributes} baseAttributes
148
+ * @param {string} baseKey
149
+ * @param {boolean} topLevel
150
+ * @param {string} statusCode
151
+ * @returns {void}
152
+ */
153
+ #addDistribution (distributions, sketch, startNano, endNano, baseAttributes, baseKey, topLevel, statusCode) {
127
154
  if (!sketch || sketch.count === 0) return
155
+
156
+ const key = `${baseKey},${topLevel},${statusCode}`
157
+ const existing = distributions.get(key)
158
+ if (existing) {
159
+ existing.sketch.merge(sketch)
160
+ } else {
161
+ const attributes = this.transformAttributes(baseAttributes)
162
+ attributes.push(
163
+ { key: 'datadog.span.top_level', value: { boolValue: topLevel } },
164
+ { key: 'status.code', value: { stringValue: statusCode } }
165
+ )
166
+ distributions.set(key, {
167
+ sketch,
168
+ startNano,
169
+ endNano,
170
+ attributes,
171
+ })
172
+ }
173
+ }
174
+
175
+ #pushPoint (points, sketch, startNano, endNano, attributes) {
128
176
  points.push({
129
177
  attributes,
130
178
  startTimeUnixNano: startNano,
@@ -140,15 +188,18 @@ class OtlpStatsTransformer extends OtlpTransformerBase {
140
188
 
141
189
  /**
142
190
  * @param {import('../../span_stats').SpanAggKey} aggKey
191
+ * @returns {import('@opentelemetry/api').Attributes}
143
192
  */
144
193
  #buildAttributes (aggKey) {
145
- const raw = { 'span.name': aggKey.resource }
146
-
147
- if (aggKey.service && aggKey.service !== this.#defaultService) {
148
- raw['service.name'] = aggKey.service
194
+ const spanKind = Object.hasOwn(SPAN_KIND_METRIC_MAP, aggKey.spanKind)
195
+ ? SPAN_KIND_METRIC_MAP[aggKey.spanKind]
196
+ : 'SPAN_KIND_INTERNAL'
197
+ const raw = {
198
+ 'span.name': aggKey.resource,
199
+ 'service.name': aggKey.service,
200
+ 'span.kind': spanKind,
149
201
  }
150
202
 
151
- if (aggKey.spanKind) raw['span.kind'] = aggKey.spanKind
152
203
  if (aggKey.statusCode) raw['http.response.status_code'] = Number(aggKey.statusCode)
153
204
  if (aggKey.method) raw['http.request.method'] = aggKey.method
154
205
  if (aggKey.endpoint) raw['http.route'] = aggKey.endpoint
@@ -159,13 +210,15 @@ class OtlpStatsTransformer extends OtlpTransformerBase {
159
210
  : String(aggKey.rpcStatusCode).toUpperCase()
160
211
  }
161
212
 
162
- if (!this.#otelSemanticsEnabled) {
163
- raw['datadog.operation.name'] = aggKey.name
164
- if (aggKey.type) raw['datadog.span.type'] = aggKey.type
165
- if (aggKey.synthetics) raw['datadog.origin'] = 'synthetics'
166
- }
213
+ // TODO: additional_metric_tags support is still evolving/TBD across most SDKs; not implemented here yet.
214
+
215
+ raw['datadog.operation.name'] = aggKey.name
216
+ if (aggKey.type) raw['datadog.span.type'] = aggKey.type
217
+ if (aggKey.synthetics) raw['datadog.origin'] = 'synthetics'
218
+ if (aggKey.srvSrc) raw['datadog.svc_src'] = aggKey.srvSrc
219
+ if (typeof aggKey.isTraceRoot === 'boolean') raw['datadog.is_trace_root'] = aggKey.isTraceRoot
167
220
 
168
- return this.transformAttributes(raw)
221
+ return raw
169
222
  }
170
223
  }
171
224