dd-trace 5.109.0 → 5.111.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +13 -8
  2. package/ci/init.js +25 -5
  3. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +79 -24
  6. package/initialize.mjs +5 -6
  7. package/loader-hook.mjs +154 -47
  8. package/package.json +20 -16
  9. package/packages/datadog-esbuild/index.js +26 -0
  10. package/packages/datadog-esbuild/src/utils.js +46 -3
  11. package/packages/datadog-instrumentations/src/connect.js +4 -3
  12. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  13. package/packages/datadog-instrumentations/src/fs.js +8 -6
  14. package/packages/datadog-instrumentations/src/graphql.js +26 -484
  15. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  17. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  18. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  19. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  23. package/packages/datadog-instrumentations/src/koa.js +3 -2
  24. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  25. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  26. package/packages/datadog-instrumentations/src/mongoose.js +14 -2
  27. package/packages/datadog-instrumentations/src/next.js +10 -10
  28. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  29. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  30. package/packages/datadog-instrumentations/src/restify.js +4 -3
  31. package/packages/datadog-instrumentations/src/router.js +18 -5
  32. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  33. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  34. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1293 -0
  35. package/packages/datadog-instrumentations/src/vitest-main.js +1567 -0
  36. package/packages/datadog-instrumentations/src/vitest-util.js +249 -0
  37. package/packages/datadog-instrumentations/src/vitest-worker.js +753 -0
  38. package/packages/datadog-instrumentations/src/vitest.js +11 -1598
  39. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  40. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +18 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  45. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
  46. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -5
  48. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  49. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  50. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +627 -11
  52. package/packages/datadog-plugin-graphql/src/index.js +20 -8
  53. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  54. package/packages/datadog-plugin-graphql/src/validate.js +16 -5
  55. package/packages/datadog-plugin-http/src/client.js +8 -3
  56. package/packages/datadog-plugin-http/src/index.js +6 -8
  57. package/packages/datadog-plugin-http2/src/client.js +5 -2
  58. package/packages/datadog-plugin-jest/src/index.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  60. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  61. package/packages/datadog-plugin-openai/src/services.js +2 -2
  62. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  63. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  64. package/packages/datadog-plugin-undici/src/index.js +5 -2
  65. package/packages/datadog-plugin-vitest/src/index.js +76 -64
  66. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  67. package/packages/datadog-webpack/index.js +17 -1
  68. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  69. package/packages/dd-trace/src/aiguard/sdk.js +15 -3
  70. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  71. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  72. package/packages/dd-trace/src/appsec/channels.js +3 -1
  73. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  74. package/packages/dd-trace/src/appsec/graphql.js +5 -5
  75. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +26 -19
  76. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  77. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  78. package/packages/dd-trace/src/appsec/index.js +20 -7
  79. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  80. package/packages/dd-trace/src/appsec/reporter.js +50 -10
  81. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  82. package/packages/dd-trace/src/appsec/telemetry/index.js +27 -1
  83. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  84. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  85. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  86. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -2
  87. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  88. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  89. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -2
  90. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  91. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -13
  92. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  93. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  94. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  95. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  96. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  97. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  98. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  99. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  100. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  101. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  102. package/packages/dd-trace/src/config/defaults.js +25 -5
  103. package/packages/dd-trace/src/config/generated-config-types.d.ts +596 -58
  104. package/packages/dd-trace/src/config/helper.js +134 -33
  105. package/packages/dd-trace/src/config/index.js +39 -23
  106. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  107. package/packages/dd-trace/src/config/parsers.js +19 -1
  108. package/packages/dd-trace/src/config/remote_config.js +1 -1
  109. package/packages/dd-trace/src/config/supported-configurations.json +109 -57
  110. package/packages/dd-trace/src/constants.js +7 -0
  111. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  112. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
  113. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  114. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  115. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  116. package/packages/dd-trace/src/debugger/index.js +5 -0
  117. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  118. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  119. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  120. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  121. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  122. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  123. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  124. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  125. package/packages/dd-trace/src/guardrails/index.js +8 -5
  126. package/packages/dd-trace/src/index.js +6 -4
  127. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  128. package/packages/dd-trace/src/llmobs/index.js +17 -6
  129. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  130. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  131. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/sdk.js +8 -6
  134. package/packages/dd-trace/src/llmobs/span_processor.js +5 -1
  135. package/packages/dd-trace/src/llmobs/tagger.js +58 -3
  136. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  137. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  138. package/packages/dd-trace/src/log/index.js +18 -20
  139. package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
  140. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  141. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  142. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  143. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  145. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  146. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  147. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  148. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  149. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  150. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  151. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  152. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +44 -38
  153. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  154. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  155. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  156. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  157. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  158. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  159. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  160. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  161. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  162. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  163. package/packages/dd-trace/src/plugin_manager.js +14 -8
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -4
  165. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  166. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  167. package/packages/dd-trace/src/plugins/util/test.js +91 -9
  168. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  169. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  170. package/packages/dd-trace/src/priority_sampler.js +1 -13
  171. package/packages/dd-trace/src/profiling/config.js +129 -198
  172. package/packages/dd-trace/src/profiling/constants.js +4 -7
  173. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  174. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  175. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  176. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  177. package/packages/dd-trace/src/profiling/oom.js +71 -0
  178. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  179. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  180. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  181. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  182. package/packages/dd-trace/src/proxy.js +8 -8
  183. package/packages/dd-trace/src/ritm.js +4 -2
  184. package/packages/dd-trace/src/serverless.js +1 -2
  185. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  186. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  187. package/packages/dd-trace/src/span_format.js +2 -1
  188. package/packages/dd-trace/src/span_processor.js +11 -6
  189. package/packages/dd-trace/src/span_stats.js +96 -78
  190. package/packages/dd-trace/src/startup-log.js +2 -1
  191. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  192. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  193. package/packages/dd-trace/src/telemetry/index.js +2 -2
  194. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  195. package/packages/dd-trace/src/telemetry/send-data.js +20 -18
  196. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  197. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  198. package/packages/dd-trace/src/tracer.js +15 -1
  199. package/packages/dd-trace/src/util.js +14 -0
  200. package/register.js +58 -1
  201. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  202. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  203. package/vendor/dist/pprof-format/index.js +1 -1
  204. package/vendor/dist/protobufjs/index.js +1 -1
  205. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  206. package/vendor/dist/shell-quote/index.js +1 -1
  207. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
  208. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -1,6 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const log = require('../log')
4
+ const Sampler = require('../sampler')
5
+ const { formatKnuthRate } = require('../util')
4
6
  const {
5
7
  MODEL_NAME,
6
8
  MODEL_PROVIDER,
@@ -41,6 +43,12 @@ const {
41
43
  ROUTING_SITE,
42
44
  PROMPT_TRACKING_INSTRUMENTATION_METHOD,
43
45
  INSTRUMENTATION_METHOD_ANNOTATED,
46
+ SAMPLE_RATE,
47
+ SAMPLING_DECISION,
48
+ SAMPLING_DECISION_SAMPLED,
49
+ SAMPLING_DECISION_DROPPED,
50
+ PROPAGATED_SAMPLE_RATE_KEY,
51
+ PROPAGATED_SAMPLING_DECISION_KEY,
44
52
  } = require('./constants/tags')
45
53
  const { storage } = require('./storage')
46
54
  const { findGenAIAncestorSpanId, validateCostTags, writeBridgeTags, validateToolDefinitions } = require('./util')
@@ -53,12 +61,31 @@ class LLMObsTagger {
53
61
  /** @type {import('../config/config-base')} */
54
62
  #config
55
63
 
64
+ /** @type {import('../sampler') | null} */
65
+ #sampler = null
66
+
56
67
  constructor (config, softFail = false) {
57
68
  this.#config = config
58
69
 
59
70
  this.softFail = softFail
60
71
  }
61
72
 
73
+ /**
74
+ * The sampler reads its rate from `config.llmobs.sampleRate`, which can change
75
+ * at runtime (e.g. via remote config). Rebuild the sampler whenever the rate
76
+ * changes so decisions reflect the current config, while reusing the existing
77
+ * sampler when it hasn't.
78
+ *
79
+ * @returns {import('../sampler')}
80
+ */
81
+ #getSampler () {
82
+ const rate = this.#config.llmobs?.sampleRate ?? 1
83
+ if (this.#sampler === null || rate !== this.#sampler.rate()) {
84
+ this.#sampler = new Sampler(rate)
85
+ }
86
+ return this.#sampler
87
+ }
88
+
62
89
  static get tagMap () {
63
90
  return registry
64
91
  }
@@ -78,7 +105,7 @@ class LLMObsTagger {
78
105
  integration,
79
106
  _decorator,
80
107
  } = {}) {
81
- if (!this.#config.llmobs.enabled) return
108
+ if (!this.#config.llmobs.DD_LLMOBS_ENABLED) return
82
109
  if (!kind) return // do not register it in the map if it doesn't have an llmobs span kind
83
110
 
84
111
  const spanMlApp =
@@ -124,6 +151,8 @@ class LLMObsTagger {
124
151
  ROOT_PARENT_ID
125
152
  this._setTag(span, PARENT_ID_KEY, parentId)
126
153
 
154
+ this.#tagSamplingDecision(span, parent)
155
+
127
156
  // apply annotation context
128
157
  const annotationContext = storage.getStore()?.annotationContext
129
158
 
@@ -153,6 +182,32 @@ class LLMObsTagger {
153
182
  }
154
183
  }
155
184
 
185
+ #tagSamplingDecision (span, parent) {
186
+ const traceTags = span.context()._trace.tags
187
+ const parentTags = registry.get(parent)
188
+
189
+ let sampleRate, samplingDecision
190
+ if (parentTags) {
191
+ // Local LLMObs parent: inherit its decision.
192
+ sampleRate = parentTags[SAMPLE_RATE]
193
+ samplingDecision = parentTags[SAMPLING_DECISION]
194
+ } else if (traceTags[PROPAGATED_PARENT_ID_KEY]) {
195
+ // Distributed LLMObs parent: inherit whatever was propagated. This may be
196
+ // absent if the upstream service predates sampling propagation, in which
197
+ // case we make no decision here rather than starting a divergent one.
198
+ sampleRate = traceTags[PROPAGATED_SAMPLE_RATE_KEY]
199
+ samplingDecision = traceTags[PROPAGATED_SAMPLING_DECISION_KEY]
200
+ } else {
201
+ // Root span: make the trace's one sampling decision.
202
+ const sampler = this.#getSampler()
203
+ sampleRate = formatKnuthRate(sampler.rate())
204
+ samplingDecision = sampler.isSampled(span) ? SAMPLING_DECISION_SAMPLED : SAMPLING_DECISION_DROPPED
205
+ }
206
+
207
+ if (sampleRate != null) this._setTag(span, SAMPLE_RATE, sampleRate)
208
+ if (samplingDecision != null) this._setTag(span, SAMPLING_DECISION, samplingDecision)
209
+ }
210
+
156
211
  // TODO: similarly for the following `tag` methods,
157
212
  // how can we transition from a span weakmap to core API functionality
158
213
  tagLLMIO (span, inputData, outputData) {
@@ -679,7 +734,7 @@ class LLMObsTagger {
679
734
  }
680
735
 
681
736
  _register (span) {
682
- if (!this.#config.llmobs.enabled) return
737
+ if (!this.#config.llmobs.DD_LLMOBS_ENABLED) return
683
738
  if (registry.has(span)) {
684
739
  this.#handleFailure(`LLMObs Span "${span._name}" already registered.`)
685
740
  return
@@ -689,7 +744,7 @@ class LLMObsTagger {
689
744
  }
690
745
 
691
746
  _setTag (span, key, value) {
692
- if (!this.#config.llmobs.enabled) return
747
+ if (!this.#config.llmobs.DD_LLMOBS_ENABLED) return
693
748
  if (!registry.has(span)) {
694
749
  this.#handleFailure(`Span "${span._name}" must be an LLMObs generated span.`)
695
750
  return
@@ -87,6 +87,7 @@ function recordLLMObsEnabled (startTime, config, value = 1) {
87
87
  site: config.site,
88
88
  auto: Number(autoEnabled),
89
89
  ml_app: config.llmobs.mlApp,
90
+ sample_rate: config.llmobs.sampleRate,
90
91
  }
91
92
  llmobsMetrics.count('product_enabled', tags).inc(value)
92
93
  llmobsMetrics.distribution('init_time', tags).track(initTimeMs)
@@ -229,7 +229,7 @@ class BaseLLMObsWriter {
229
229
  }
230
230
 
231
231
  if (this._agentless) {
232
- options.headers['DD-API-KEY'] = this._config.apiKey || ''
232
+ options.headers['DD-API-KEY'] = this._config.DD_API_KEY || ''
233
233
  } else {
234
234
  options.headers[EVP_SUBDOMAIN_HEADER_NAME] = this._intake
235
235
  }
@@ -2,19 +2,16 @@
2
2
 
3
3
  const { inspect } = require('util')
4
4
 
5
- const { defaults } = require('../config/defaults')
6
- const { isTrue } = require('../util')
7
5
  const { getValueFromEnvSources } = require('../config/helper')
6
+ // Eager require restores the original startup module-load order: pulling `config/defaults` here
7
+ // also installs the instrumented `dns` it transitively loads before the tracer/agent connects.
8
+ // `config/defaults` defers its own `dns` require until after it exports, so this no longer hits
9
+ // the `config/defaults` <-> log parse cycle that motivated the lazy require below.
10
+ const { defaults } = require('../config/defaults')
8
11
  const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
9
12
  const logWriter = require('./writer')
10
13
  const { Log, LogConfig, NoTransmitError } = require('./log')
11
14
 
12
- const config = {
13
- enabled: defaults.DD_TRACE_DEBUG,
14
- logger: undefined,
15
- logLevel: defaults.logLevel,
16
- }
17
-
18
15
  // In most places where we know we want to mute a log we use log.error() directly
19
16
  const NO_TRANSMIT = new LogConfig(false)
20
17
 
@@ -77,19 +74,17 @@ const log = {
77
74
  },
78
75
 
79
76
  configure (options) {
80
- config.logger = options.logger
81
- config.logLevel = options.logLevel ??
82
- getValueFromEnvSources('DD_TRACE_LOG_LEVEL') ??
83
- config.logLevel
84
- config.enabled = isTrue(
85
- getValueFromEnvSources('DD_TRACE_DEBUG') ??
77
+ const logger = options.logger
78
+ const logLevel = options.logLevel ??
79
+ getValueFromEnvSources('DD_TRACE_LOG_LEVEL', true) ??
80
+ defaults?.logLevel
81
+ const enabled = getValueFromEnvSources('DD_TRACE_DEBUG', true) ??
86
82
  // TODO: Handle this by adding a log buffer so that configure may be called with the actual configurations.
87
83
  // eslint-disable-next-line eslint-rules/eslint-process-env
88
- (process.env.OTEL_LOG_LEVEL === 'debug' || config.enabled)
89
- )
90
- logWriter.configure(config.enabled, config.logLevel, options.logger)
84
+ (process.env.OTEL_LOG_LEVEL === 'debug' || defaults?.DD_TRACE_DEBUG)
85
+ logWriter.configure(enabled, logLevel, logger)
91
86
 
92
- return config.enabled
87
+ return enabled
93
88
  },
94
89
  }
95
90
 
@@ -113,6 +108,9 @@ function getErrorLog (err) {
113
108
  return err
114
109
  }
115
110
 
116
- log.configure({})
117
-
111
+ // Assign before the bootstrap configure() call: an invalid DD_TRACE_LOG_LEVEL
112
+ // makes config/defaults re-require this module to warn, which must observe the
113
+ // fully built log object rather than a half-initialized one.
118
114
  module.exports = log
115
+
116
+ log.configure({})
@@ -1,18 +1,13 @@
1
1
  'use strict'
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
+ const requireOptionalPeer = require('../../../datadog-instrumentations/src/helpers/require-optional-peer')
4
5
  const log = require('../log')
5
6
  const { EXPOSURE_CHANNEL } = require('./constants/constants')
6
7
  const EvalMetricsHook = require('./eval-metrics-hook')
7
8
  const SpanEnrichmentHook = require('./span-enrichment-hook')
8
9
 
9
- // Bundler-opaque require for the optional peer chain
10
- // `@datadog/openfeature-node-server` -> `@openfeature/server-sdk` ->
11
- // `@openfeature/core`. Same shape as `helpers/rewriter/compiler.js`.
12
- // Refs: https://github.com/DataDog/dd-trace-js/issues/8635
13
- // eslint-disable-next-line camelcase, no-undef
14
- const runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
15
- const { DatadogNodeServerProvider } = runtimeRequire(['@datadog/openfeature', 'node', 'server'].join('-'))
10
+ const { DatadogNodeServerProvider } = requireOptionalPeer('@datadog/openfeature-node-server')
16
11
 
17
12
  /**
18
13
  * OpenFeature provider that integrates with Datadog's feature flagging system.
@@ -30,6 +30,7 @@ class BridgeSpanBase {
30
30
  */
31
31
  constructor (ddSpan) {
32
32
  this._ddSpan = ddSpan
33
+ this._otelTraceSemanticsEnabled = false
33
34
  }
34
35
 
35
36
  get ended () {
@@ -45,7 +46,7 @@ class BridgeSpanBase {
45
46
  * @param {import('@opentelemetry/api').AttributeValue} value
46
47
  */
47
48
  setAttribute (key, value) {
48
- setOtelAttribute(this._ddSpan, key, value)
49
+ setOtelAttribute(this._ddSpan, key, value, this._otelTraceSemanticsEnabled)
49
50
  return this
50
51
  }
51
52
 
@@ -53,7 +54,7 @@ class BridgeSpanBase {
53
54
  * @param {import('@opentelemetry/api').Attributes} attributes
54
55
  */
55
56
  setAttributes (attributes) {
56
- setOtelAttributes(this._ddSpan, attributes)
57
+ setOtelAttributes(this._ddSpan, attributes, this._otelTraceSemanticsEnabled)
57
58
  return this
58
59
  }
59
60
 
@@ -91,14 +92,14 @@ class BridgeSpanBase {
91
92
  * @param {import('@opentelemetry/api').TimeInput} [timeInput]
92
93
  */
93
94
  recordException (exception, timeInput) {
94
- recordException(this._ddSpan, exception, timeInput)
95
+ recordException(this._ddSpan, exception, timeInput, this._otelTraceSemanticsEnabled)
95
96
  }
96
97
 
97
98
  /**
98
99
  * @param {import('@opentelemetry/api').SpanStatus} status
99
100
  */
100
101
  setStatus (status) {
101
- this.#statusCode = applyOtelStatus(this._ddSpan, this.#statusCode, status)
102
+ this.#statusCode = applyOtelStatus(this._ddSpan, this.#statusCode, status, this._otelTraceSemanticsEnabled)
102
103
  return this
103
104
  }
104
105
  }
@@ -2,6 +2,14 @@
2
2
 
3
3
  const os = require('os')
4
4
 
5
+ const { metrics } = require('@opentelemetry/api')
6
+
7
+ const { VERSION } = require('../../../../../version')
8
+ const processTags = require('../../process-tags')
9
+ const MeterProvider = require('./meter_provider')
10
+ const PeriodicMetricReader = require('./periodic_metric_reader')
11
+ const OtlpHttpMetricExporter = require('./otlp_http_metric_exporter')
12
+
5
13
  /**
6
14
  * @typedef {import('../../config')} Config
7
15
  */
@@ -28,11 +36,6 @@ const os = require('os')
28
36
  * @package
29
37
  */
30
38
 
31
- const { metrics } = require('@opentelemetry/api')
32
- const MeterProvider = require('./meter_provider')
33
- const PeriodicMetricReader = require('./periodic_metric_reader')
34
- const OtlpHttpMetricExporter = require('./otlp_http_metric_exporter')
35
-
36
39
  /**
37
40
  * Initializes OpenTelemetry Metrics support
38
41
  * @param {import('../../config/config-base')} config - Tracer configuration instance
@@ -75,7 +78,53 @@ function initializeOpenTelemetryMetrics (config) {
75
78
  metrics.setGlobalMeterProvider(meterProvider)
76
79
  }
77
80
 
81
+ function buildResourceAttributes (tags, { reportHostname, otelSemanticsEnabled, service, env, serviceVersion } = {}) {
82
+ const attrs = {
83
+ 'telemetry.sdk.name': 'datadog',
84
+ 'telemetry.sdk.language': 'nodejs',
85
+ 'telemetry.sdk.version': VERSION,
86
+ }
87
+ if (service) attrs['service.name'] = service
88
+ if (serviceVersion) attrs['service.version'] = serviceVersion
89
+ if (env) attrs['deployment.environment.name'] = env
90
+ if (reportHostname) attrs['host.name'] = os.hostname()
91
+
92
+ if (!otelSemanticsEnabled) {
93
+ if (tags?.['runtime-id']) attrs['datadog.runtime_id'] = tags['runtime-id']
94
+ const processTagsObject = processTags.tagsObject
95
+ if (processTagsObject) {
96
+ for (const key of Object.keys(processTagsObject)) {
97
+ attrs[`datadog.${key}`] = processTagsObject[key]
98
+ }
99
+ }
100
+ }
101
+ return attrs
102
+ }
103
+
104
+ function createOtlpSpanStatsExporter (config) {
105
+ const { OtlpStatsExporter } = require('./otlp_span_stats_exporter')
106
+ const protocol = config.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL || 'http/json'
107
+ const resourceAttributes = buildResourceAttributes(config.tags, {
108
+ reportHostname: config.reportHostname,
109
+ otelSemanticsEnabled: config.DD_TRACE_OTEL_SEMANTICS_ENABLED,
110
+ service: config.service,
111
+ env: config.env,
112
+ serviceVersion: config.version,
113
+ })
114
+ return new OtlpStatsExporter(
115
+ config.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
116
+ protocol,
117
+ resourceAttributes,
118
+ config.DD_TRACE_OTEL_SEMANTICS_ENABLED,
119
+ config.service,
120
+ config.OTEL_EXPORTER_OTLP_METRICS_HEADERS,
121
+ config.OTEL_EXPORTER_OTLP_METRICS_TIMEOUT
122
+ )
123
+ }
124
+
78
125
  module.exports = {
79
126
  MeterProvider,
80
127
  initializeOpenTelemetryMetrics,
128
+ buildResourceAttributes,
129
+ createOtlpSpanStatsExporter,
81
130
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { sanitizeAttributes } = require('../../../../../vendor/dist/@opentelemetry/core')
4
4
  const { METRIC_TYPES } = require('./constants')
5
+ const { nowUnixNano } = require('./time')
5
6
 
6
7
  /**
7
8
  * @typedef {import('@opentelemetry/api').Attributes} Attributes
@@ -62,7 +63,7 @@ class Instrument {
62
63
  type,
63
64
  value,
64
65
  attributes: sanitizeAttributes(attributes),
65
- timestamp: Number(process.hrtime.bigint()),
66
+ timestamp: nowUnixNano(),
66
67
  }
67
68
  }
68
69
  }
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../log')
4
+ const OtlpHttpExporterBase = require('../otlp/otlp_http_exporter_base')
5
+ const OtlpStatsTransformer = require('./otlp_span_stats_transformer')
6
+
7
+ class OtlpStatsExporter extends OtlpHttpExporterBase {
8
+ #transformer
9
+
10
+ /**
11
+ * @param {string} url
12
+ * @param {string} protocol
13
+ * @param {import('@opentelemetry/api').Attributes} resourceAttributes
14
+ * @param {boolean} [otelSemanticsEnabled]
15
+ * @param {string} [defaultService]
16
+ * @param {Record<string, string>} [headers]
17
+ * @param {number} [timeout]
18
+ */
19
+ constructor (url, protocol, resourceAttributes, otelSemanticsEnabled = false, defaultService = '',
20
+ headers, timeout = 10_000) {
21
+ super(url, headers, timeout, protocol, 'span-stats')
22
+ this.#transformer = new OtlpStatsTransformer(resourceAttributes, protocol, otelSemanticsEnabled, defaultService)
23
+ }
24
+
25
+ /**
26
+ * @param {Array<{timeNs: number, bucket: import('../../span_stats').SpanBuckets}>} drained
27
+ * @param {number} bucketSizeNs
28
+ */
29
+ export (drained, bucketSizeNs) {
30
+ if (drained.length === 0) return
31
+ const payload = this.#transformer.transform(drained, bucketSizeNs)
32
+ this.sendPayload(payload, (result) => {
33
+ if (result.code !== 0) {
34
+ log.error('Failed to export span stats: %s', result.error?.message)
35
+ }
36
+ })
37
+ }
38
+ }
39
+
40
+ module.exports = { OtlpStatsExporter }
@@ -0,0 +1,169 @@
1
+ 'use strict'
2
+
3
+ const { LogCollapsingLowestDenseDDSketch } = require('../../../../../vendor/dist/@datadog/sketches-js')
4
+ const OtlpTransformerBase = require('../otlp/otlp_transformer_base')
5
+ const { getProtobufTypes } = require('../otlp/protobuf_loader')
6
+
7
+ const NS_PER_S = 1e9
8
+
9
+ // Must match libdatadog's EXPLICIT_BOUNDS_SECONDS and OTel spanmetrics connector defaults.
10
+ const EXPLICIT_BOUNDS_SECONDS = [
11
+ 0.002, 0.004, 0.006, 0.008, 0.01, 0.05, 0.1, 0.2, 0.4, 0.8, 1, 1.4, 2, 5, 10, 15,
12
+ ]
13
+
14
+ /**
15
+ * @param {object} sketch
16
+ * @returns {number[]}
17
+ */
18
+ function sketchToFixedHistogram (sketch) {
19
+ const bucketCounts = new Array(EXPLICIT_BOUNDS_SECONDS.length + 1).fill(0)
20
+ if (sketch.zeroCount > 0) bucketCounts[0] += sketch.zeroCount
21
+ const { store, mapping } = sketch
22
+ for (let key = store.minKey; key <= store.maxKey; key++) {
23
+ const weight = store.bins[key - store.offset]
24
+ if (!weight) continue
25
+ const seconds = mapping.value(key) / NS_PER_S
26
+ let idx = EXPLICIT_BOUNDS_SECONDS.findIndex((bound) => seconds <= bound)
27
+ if (idx === -1) idx = EXPLICIT_BOUNDS_SECONDS.length
28
+ bucketCounts[idx] += weight
29
+ }
30
+ return bucketCounts.map((weight) => Math.round(weight))
31
+ }
32
+
33
+ let _deltaTemporality
34
+
35
+ function getDeltaTemporality () {
36
+ if (_deltaTemporality === undefined) {
37
+ const { protoAggregationTemporality } = getProtobufTypes()
38
+ _deltaTemporality = protoAggregationTemporality.values.AGGREGATION_TEMPORALITY_DELTA
39
+ }
40
+ return _deltaTemporality
41
+ }
42
+
43
+ const ERROR_STATUS_ATTR = { key: 'status.code', value: { intValue: 2 } }
44
+
45
+ class OtlpStatsTransformer extends OtlpTransformerBase {
46
+ #otelSemanticsEnabled
47
+ #defaultService
48
+
49
+ /**
50
+ * @param {import('@opentelemetry/api').Attributes} resourceAttributes
51
+ * @param {string} protocol
52
+ * @param {boolean} [otelSemanticsEnabled]
53
+ * @param {string} [defaultService]
54
+ */
55
+ constructor (resourceAttributes, protocol, otelSemanticsEnabled = false, defaultService = '') {
56
+ super(resourceAttributes, protocol, 'span-stats')
57
+ this.#otelSemanticsEnabled = otelSemanticsEnabled
58
+ this.#defaultService = defaultService
59
+ }
60
+
61
+ /**
62
+ * @param {Array<{timeNs: number, bucket: import('../../span_stats').SpanBuckets}>} drained
63
+ * @param {number} bucketSizeNs
64
+ */
65
+ transform (drained, bucketSizeNs) {
66
+ const isJson = this.protocol === 'http/json'
67
+ const data = {
68
+ resourceMetrics: [{
69
+ resource: this.transformResource(),
70
+ scopeMetrics: this.#buildScopeMetrics(drained, bucketSizeNs, isJson),
71
+ }],
72
+ }
73
+ return isJson
74
+ ? this.serializeToJson(data)
75
+ : this.serializeToProtobuf(getProtobufTypes().protoMetricsService, data)
76
+ }
77
+
78
+ #buildScopeMetrics (drained, bucketSizeNs, isJson) {
79
+ const temporality = isJson ? 'AGGREGATION_TEMPORALITY_DELTA' : getDeltaTemporality()
80
+
81
+ const dataPoints = []
82
+
83
+ for (const { timeNs, bucket } of drained) {
84
+ const endTimeNs = timeNs + bucketSizeNs
85
+ const startNano = isJson ? String(timeNs) : timeNs
86
+ const endNano = isJson ? String(endTimeNs) : endTimeNs
87
+
88
+ for (const aggStats of bucket.values()) {
89
+ const baseAttrs = this.#buildAttributes(aggStats.aggKey)
90
+
91
+ if (this.#otelSemanticsEnabled) {
92
+ const okDist = new LogCollapsingLowestDenseDDSketch()
93
+ okDist.merge(aggStats.topLevelOkDistribution)
94
+ okDist.merge(aggStats.nonTopLevelOkDistribution)
95
+ const errDist = new LogCollapsingLowestDenseDDSketch()
96
+ errDist.merge(aggStats.topLevelErrorDistribution)
97
+ errDist.merge(aggStats.nonTopLevelErrorDistribution)
98
+ this.#pushPoint(dataPoints, okDist, startNano, endNano, baseAttrs)
99
+ this.#pushPoint(dataPoints, errDist, startNano, endNano, [...baseAttrs, ERROR_STATUS_ATTR])
100
+ } else {
101
+ const tlAttrs = [...baseAttrs, { key: 'datadog.span.top_level', value: { boolValue: true } }]
102
+ const ntlAttrs = [...baseAttrs, { key: 'datadog.span.top_level', value: { boolValue: false } }]
103
+ this.#pushPoint(dataPoints, aggStats.topLevelOkDistribution, startNano, endNano, tlAttrs)
104
+ this.#pushPoint(dataPoints, aggStats.topLevelErrorDistribution, startNano, endNano,
105
+ [...tlAttrs, ERROR_STATUS_ATTR])
106
+ this.#pushPoint(dataPoints, aggStats.nonTopLevelOkDistribution, startNano, endNano, ntlAttrs)
107
+ this.#pushPoint(dataPoints, aggStats.nonTopLevelErrorDistribution, startNano, endNano,
108
+ [...ntlAttrs, ERROR_STATUS_ATTR])
109
+ }
110
+ }
111
+ }
112
+
113
+ if (dataPoints.length === 0) return []
114
+ return [{
115
+ metrics: [
116
+ {
117
+ name: 'traces.span.sdk.metrics.duration',
118
+ unit: 's',
119
+ histogram: { dataPoints, aggregationTemporality: temporality },
120
+ },
121
+ ],
122
+ }]
123
+ }
124
+
125
+ #pushPoint (points, sketch, startNano, endNano, attributes) {
126
+ if (!sketch || sketch.count === 0) return
127
+ points.push({
128
+ attributes,
129
+ startTimeUnixNano: startNano,
130
+ timeUnixNano: endNano,
131
+ count: sketch.count,
132
+ sum: sketch.sum / NS_PER_S,
133
+ min: sketch.min / NS_PER_S,
134
+ max: sketch.max / NS_PER_S,
135
+ bucketCounts: sketchToFixedHistogram(sketch),
136
+ explicitBounds: EXPLICIT_BOUNDS_SECONDS,
137
+ })
138
+ }
139
+
140
+ /**
141
+ * @param {import('../../span_stats').SpanAggKey} aggKey
142
+ */
143
+ #buildAttributes (aggKey) {
144
+ const raw = { 'span.name': aggKey.resource }
145
+
146
+ if (aggKey.service && aggKey.service !== this.#defaultService) {
147
+ raw['service.name'] = aggKey.service
148
+ }
149
+
150
+ if (aggKey.spanKind) raw['span.kind'] = aggKey.spanKind
151
+ if (aggKey.statusCode) raw['http.response.status_code'] = Number(aggKey.statusCode)
152
+ if (aggKey.method) raw['http.request.method'] = aggKey.method
153
+ if (aggKey.endpoint) raw['http.route'] = aggKey.endpoint
154
+ if (aggKey.rpcStatusCode !== '') {
155
+ raw['rpc.response.status_code'] = String(aggKey.rpcStatusCode).toUpperCase()
156
+ }
157
+
158
+ if (!this.#otelSemanticsEnabled) {
159
+ raw['datadog.operation.name'] = aggKey.name
160
+ if (aggKey.type) raw['datadog.span.type'] = aggKey.type
161
+ if (aggKey.synthetics) raw['datadog.origin'] = 'synthetics'
162
+ }
163
+
164
+ return this.transformAttributes(raw)
165
+ }
166
+ }
167
+
168
+ module.exports = OtlpStatsTransformer
169
+ module.exports.EXPLICIT_BOUNDS_SECONDS = EXPLICIT_BOUNDS_SECONDS
@@ -6,6 +6,7 @@ const {
6
6
  METRIC_TYPES, TEMPORALITY, DEFAULT_HISTOGRAM_BUCKETS, DEFAULT_MAX_MEASUREMENT_QUEUE_SIZE,
7
7
  } = require('./constants')
8
8
  const { ObservableInstrument } = require('./instruments')
9
+ const { nowUnixNano } = require('./time')
9
10
 
10
11
  /**
11
12
  * @typedef {import('@opentelemetry/api').Attributes} Attributes
@@ -309,7 +310,7 @@ class PeriodicMetricReader {
309
310
  *
310
311
  */
311
312
  class MetricAggregator {
312
- #startTime = Number(process.hrtime.bigint())
313
+ #startTime = nowUnixNano()
313
314
  #temporalityPreference
314
315
  #maxBatchedQueueSize
315
316
 
@@ -444,8 +445,8 @@ class MetricAggregator {
444
445
  */
445
446
  #isDeltaType (type) {
446
447
  return type === METRIC_TYPES.COUNTER ||
447
- type === METRIC_TYPES.OBSERVABLECOUNTER ||
448
- type === METRIC_TYPES.HISTOGRAM
448
+ type === METRIC_TYPES.OBSERVABLECOUNTER ||
449
+ type === METRIC_TYPES.HISTOGRAM
449
450
  }
450
451
 
451
452
  /**
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ const { timeOrigin } = performance
4
+
5
+ /**
6
+ * Current wall-clock time as UNIX epoch nanoseconds, the unit OTLP requires for
7
+ * `timeUnixNano` / `startTimeUnixNano`. `process.hrtime.bigint()` is a monotonic
8
+ * clock with an arbitrary origin, so its values decode as 1970 timestamps that
9
+ * the Datadog Agent silently drops. Anchoring on `performance.timeOrigin` puts
10
+ * the value on the epoch while `performance.now()` keeps it monotonic, so a
11
+ * backward wall-clock jump can't make `timeUnixNano` precede `startTimeUnixNano`.
12
+ *
13
+ * @returns {number} Nanoseconds since the UNIX epoch
14
+ */
15
+ function nowUnixNano () {
16
+ return Math.round((timeOrigin + performance.now()) * 1e6)
17
+ }
18
+
19
+ module.exports = { nowUnixNano }
@@ -68,7 +68,7 @@ message KeyValue {
68
68
  }
69
69
 
70
70
  // InstrumentationScope is a message representing the instrumentation scope information
71
- // such as the fully qualified name and version.
71
+ // such as the fully qualified name and version.
72
72
  message InstrumentationScope {
73
73
  // An empty instrumentation scope name means the name is unknown.
74
74
  string name = 1;
@@ -113,4 +113,4 @@ message EntityRef {
113
113
  // These attribute keys are not part of entity's identity.
114
114
  // These keys MUST exist in the containing {message}.attributes.
115
115
  repeated string description_keys = 4;
116
- }
116
+ }
@@ -223,4 +223,4 @@ message LogRecord {
223
223
  //
224
224
  // [Optional].
225
225
  string event_name = 12;
226
- }
226
+ }
@@ -75,4 +75,4 @@ message ExportLogsPartialSuccess {
75
75
  // error_message is an optional field. An error_message with an empty value
76
76
  // is equivalent to it not being set.
77
77
  string error_message = 2;
78
- }
78
+ }
@@ -717,4 +717,4 @@ message Exemplar {
717
717
  // trace_id may be missing if the measurement is not recorded inside a trace
718
718
  // or if the trace is not sampled.
719
719
  bytes trace_id = 5;
720
- }
720
+ }
@@ -75,4 +75,4 @@ message ExportMetricsPartialSuccess {
75
75
  // error_message is an optional field. An error_message with an empty value
76
76
  // is equivalent to it not being set.
77
77
  string error_message = 2;
78
- }
78
+ }