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
@@ -0,0 +1,276 @@
1
+ 'use strict'
2
+
3
+ const { extractPathFromUrl } = require('./url')
4
+
5
+ // OpenTelemetry HTTP semantic-convention attribute names, emitted in place of
6
+ // the Datadog ones when `DD_TRACE_OTEL_SEMANTICS_ENABLED` is set.
7
+ // See https://opentelemetry.io/docs/specs/semconv/http/http-spans/
8
+ const HTTP_REQUEST_METHOD = 'http.request.method'
9
+ const HTTP_RESPONSE_STATUS_CODE = 'http.response.status_code'
10
+ const URL_FULL = 'url.full'
11
+ const URL_PATH = 'url.path'
12
+ const URL_SCHEME = 'url.scheme'
13
+ const URL_QUERY = 'url.query'
14
+ const SERVER_ADDRESS = 'server.address'
15
+ const SERVER_PORT = 'server.port'
16
+ const USER_AGENT_ORIGINAL = 'user_agent.original'
17
+ const CLIENT_ADDRESS = 'client.address'
18
+ const NETWORK_PEER_ADDRESS = 'network.peer.address'
19
+ const HTTP_REQUEST_METHOD_ORIGINAL = 'http.request.method_original'
20
+
21
+ // Known HTTP methods (RFC 9110 + PATCH RFC 5789 + QUERY httpbis draft). A verb
22
+ // outside this set is reported as `_OTHER` with the raw value preserved on
23
+ // `http.request.method_original`, per the OTel HTTP semantic conventions.
24
+ const KNOWN_METHODS = new Set([
25
+ 'CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'QUERY', 'TRACE',
26
+ ])
27
+
28
+ // Datadog HTTP meta keys replaced by OTel names — omitted when rebuilding meta.
29
+ const DD_HTTP_META_KEYS = new Set([
30
+ 'http.method', 'http.status_code', 'http.useragent', 'http.client_ip', 'http.endpoint', 'http.url', 'out.host',
31
+ ])
32
+ const NETWORK_DESTINATION_PORT = 'network.destination.port'
33
+
34
+ // IPv6 literals arrive bracketed (URL.hostname / out.host = `[::1]`); OTel
35
+ // `server.address` is the bare address.
36
+ function stripIpv6Brackets (host) {
37
+ return host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host
38
+ }
39
+
40
+ /**
41
+ * @typedef {object} ServerUrlParts
42
+ * @property {string} [scheme] value for `url.scheme`
43
+ * @property {string} [address] value for `server.address`
44
+ * @property {number} [port] value for `server.port`
45
+ * @property {string} path value for `url.path`
46
+ * @property {string} [query] value for `url.query` (omitted when empty)
47
+ */
48
+
49
+ /**
50
+ * Decompose a server request URL into the OpenTelemetry `url.*` / `server.*`
51
+ * parts. Structural fields (scheme, address, port, path) are read from the raw
52
+ * URL; the query is taken from the already-obfuscated URL so the configured
53
+ * query-string obfuscation is preserved.
54
+ *
55
+ * @param {string} rawUrl full request URL (`scheme://host[:port]/path?query`)
56
+ * @param {string} obfuscatedUrl same URL with its query string obfuscated
57
+ * @returns {ServerUrlParts}
58
+ */
59
+ function decomposeServerUrl (rawUrl, obfuscatedUrl) {
60
+ let scheme
61
+ let address
62
+ let port
63
+ let path
64
+
65
+ try {
66
+ const parsed = new URL(rawUrl)
67
+ scheme = parsed.protocol.length > 1 ? parsed.protocol.slice(0, -1) : undefined
68
+ // `extractURL` builds `http://undefined/...` when the Host header is absent; skip that.
69
+ const hostname = parsed.hostname
70
+ if (hostname && hostname !== 'undefined') {
71
+ address = stripIpv6Brackets(hostname)
72
+ }
73
+ if (parsed.port) {
74
+ const parsedPort = Number.parseInt(parsed.port)
75
+ if (parsedPort > 0) port = parsedPort
76
+ }
77
+ path = parsed.pathname || '/'
78
+ } catch {
79
+ // Malformed or relative URL: fall back to a best-effort path only.
80
+ path = extractPathFromUrl(rawUrl)
81
+ }
82
+
83
+ let query
84
+ const queryIndex = obfuscatedUrl.indexOf('?')
85
+ if (queryIndex !== -1) {
86
+ const rawQuery = obfuscatedUrl.slice(queryIndex + 1)
87
+ if (rawQuery) query = rawQuery
88
+ }
89
+
90
+ return { scheme, address, port, path, query }
91
+ }
92
+
93
+ const ERROR_TYPE = 'error.type'
94
+
95
+ function toHttpScheme (scheme) {
96
+ if (scheme === 'ws') return 'http'
97
+ if (scheme === 'wss') return 'https'
98
+ return scheme
99
+ }
100
+
101
+ /**
102
+ * Redact any userinfo embedded in a URL's authority, since `url.full` must not
103
+ * leak credentials: `user:pass@host` -> `REDACTED:REDACTED@host`, `user@host` ->
104
+ * `REDACTED@host`. Returns the URL unchanged when no userinfo is present.
105
+ *
106
+ * @param {string} url
107
+ * @returns {string}
108
+ */
109
+ function redactUrlCredentials (url) {
110
+ const schemeEnd = url.indexOf('://')
111
+ if (schemeEnd === -1) return url
112
+ const authorityStart = schemeEnd + 3
113
+
114
+ let authorityEnd = url.length
115
+ for (let i = authorityStart; i < url.length; i++) {
116
+ const char = url[i]
117
+ if (char === '/' || char === '?' || char === '#') {
118
+ authorityEnd = i
119
+ break
120
+ }
121
+ }
122
+
123
+ // userinfo runs to the LAST '@' in the authority (WHATWG); using the first
124
+ // '@' would leak the remainder, e.g. `user:p@ss@host`.
125
+ const at = url.lastIndexOf('@', authorityEnd - 1)
126
+ if (at < authorityStart) return url
127
+
128
+ const redacted = url.slice(authorityStart, at).includes(':') ? 'REDACTED:REDACTED' : 'REDACTED'
129
+ return url.slice(0, authorityStart) + redacted + url.slice(at)
130
+ }
131
+
132
+ /**
133
+ * The scheme's default port, used as the `server.port` fallback for client spans
134
+ * (the attribute is required for clients but the explicit port is absent for
135
+ * default-port requests).
136
+ *
137
+ * @param {string} [url]
138
+ * @returns {number | undefined}
139
+ */
140
+ function defaultPortForUrl (url) {
141
+ if (url === undefined) return
142
+ if (url.startsWith('https:') || url.startsWith('wss:')) return 443
143
+ if (url.startsWith('http:') || url.startsWith('ws:')) return 80
144
+ }
145
+
146
+ /**
147
+ * @typedef {object} FormattedHttpSpan
148
+ * @property {Record<string, string>} meta
149
+ * @property {Record<string, number>} metrics
150
+ * @property {number} error
151
+ */
152
+
153
+ /**
154
+ * Rewrite a formatted span's Datadog HTTP tags to OpenTelemetry HTTP
155
+ * semantic-convention names, in place. Called at serialization time (from
156
+ * `span_format`) when `DD_TRACE_OTEL_SEMANTICS_ENABLED` is set, so every HTTP
157
+ * integration is covered from one place. No-op for non-HTTP spans. The span
158
+ * keeps the Datadog tag names throughout its lifetime — only the serialized
159
+ * output is renamed — so runtime consumers (peer.service, AppSec, trace stats)
160
+ * are unaffected.
161
+ *
162
+ * @param {FormattedHttpSpan} formattedSpan
163
+ */
164
+ function applyHttpOtelSemantics (formattedSpan) {
165
+ const meta = formattedSpan.meta
166
+ const metrics = formattedSpan.metrics
167
+ const method = meta['http.method']
168
+ const url = meta['http.url']
169
+ if (method === undefined && url === undefined) return
170
+
171
+ // Rebuild meta/metrics as fresh objects that omit the renamed Datadog HTTP
172
+ // keys. Deleting them in place demotes the formatted span to V8 dictionary
173
+ // mode (~40% slower than this rebuild, measured); a fresh object keeps fast
174
+ // properties and can't leak a renamed key as `undefined` on the OTLP path.
175
+ const newMeta = {}
176
+ for (const key of Object.keys(meta)) {
177
+ if (!DD_HTTP_META_KEYS.has(key)) newMeta[key] = meta[key]
178
+ }
179
+ const newMetrics = {}
180
+ for (const key of Object.keys(metrics)) {
181
+ if (key !== NETWORK_DESTINATION_PORT) newMetrics[key] = metrics[key]
182
+ }
183
+
184
+ const kind = meta['span.kind']
185
+
186
+ if (method !== undefined) {
187
+ if (KNOWN_METHODS.has(method)) {
188
+ newMeta[HTTP_REQUEST_METHOD] = method
189
+ } else {
190
+ // Unknown verb: bucket to `_OTHER`, preserve the raw value, and use the
191
+ // literal "HTTP" in the span name (the spec forbids the URL path there).
192
+ // Known-method names are already `{method} {route}`.
193
+ newMeta[HTTP_REQUEST_METHOD] = '_OTHER'
194
+ newMeta[HTTP_REQUEST_METHOD_ORIGINAL] = method
195
+ const resource = formattedSpan.resource
196
+ if (typeof resource === 'string') {
197
+ if (resource === method) {
198
+ formattedSpan.resource = 'HTTP'
199
+ } else if (resource.startsWith(`${method} `)) {
200
+ formattedSpan.resource = `HTTP${resource.slice(method.length)}`
201
+ }
202
+ }
203
+ }
204
+ }
205
+
206
+ const status = meta['http.status_code']
207
+ let statusCode
208
+ if (status !== undefined) {
209
+ // OTel types http.response.status_code as an int, so emit it as a numeric
210
+ // metric (the OTLP exporter serializes meta as stringValue but metrics as
211
+ // intValue) — mirroring how server.port is handled below. Guard against a
212
+ // non-numeric status, which would otherwise write a NaN metric.
213
+ statusCode = Number.parseInt(status)
214
+ if (Number.isFinite(statusCode)) newMetrics[HTTP_RESPONSE_STATUS_CODE] = statusCode
215
+ }
216
+
217
+ const userAgent = meta['http.useragent']
218
+ if (userAgent !== undefined) newMeta[USER_AGENT_ORIGINAL] = userAgent
219
+
220
+ const clientIp = meta['http.client_ip']
221
+ if (clientIp !== undefined) newMeta[CLIENT_ADDRESS] = clientIp
222
+
223
+ // http.endpoint is Datadog-only (omitted above); it has no OTel equivalent.
224
+
225
+ if (kind === 'server') {
226
+ // FIXME: some server frameworks (e.g. Next.js — `packages/datadog-plugin-next`)
227
+ // never populate `http.url`, so the OTel `url.*` / `server.*` attributes below
228
+ // can't be derived and are omitted for those spans. This needs a fix; the
229
+ // short-term option is to set `http.url` in those integrations so they emit
230
+ // the full server attribute set.
231
+ if (url !== undefined) {
232
+ // The query in `http.url` is already obfuscated per config, so it is preserved.
233
+ const { scheme, address, port, path, query } = decomposeServerUrl(url, url)
234
+ if (path !== undefined) newMeta[URL_PATH] = path
235
+ if (scheme !== undefined) newMeta[URL_SCHEME] = toHttpScheme(scheme)
236
+ if (query !== undefined) newMeta[URL_QUERY] = query
237
+ if (address !== undefined) newMeta[SERVER_ADDRESS] = address
238
+ if (port !== undefined) newMetrics[SERVER_PORT] = port
239
+ }
240
+ } else {
241
+ if (url !== undefined) {
242
+ // url.full must not carry embedded credentials.
243
+ newMeta[URL_FULL] = redactUrlCredentials(url)
244
+ }
245
+ const outHost = meta['out.host']
246
+ if (outHost !== undefined) newMeta[SERVER_ADDRESS] = stripIpv6Brackets(outHost)
247
+ const clientPort = metrics[NETWORK_DESTINATION_PORT]
248
+ if (clientPort === undefined) {
249
+ // server.port is required for client spans; fall back to the scheme default.
250
+ const defaultPort = defaultPortForUrl(url)
251
+ if (defaultPort !== undefined) newMetrics[SERVER_PORT] = defaultPort
252
+ } else {
253
+ newMetrics[SERVER_PORT] = clientPort
254
+ }
255
+ }
256
+
257
+ // OTel error semantics for an error response (no-clobber on an exception-derived
258
+ // type): server spans are errors on 5xx only (4xx MUST be left unset per the
259
+ // spec); client spans on any status >= 400.
260
+ if (status !== undefined && newMeta[ERROR_TYPE] === undefined) {
261
+ const isError = kind === 'server' ? statusCode >= 500 : statusCode >= 400
262
+ if (isError) {
263
+ newMeta[ERROR_TYPE] = status
264
+ formattedSpan.error = 1
265
+ }
266
+ }
267
+
268
+ formattedSpan.meta = newMeta
269
+ formattedSpan.metrics = newMetrics
270
+ }
271
+
272
+ module.exports = {
273
+ NETWORK_PEER_ADDRESS, // imported by web.js (set from req.socket, not at serialization)
274
+ decomposeServerUrl, // exercised directly by the helper spec
275
+ applyHttpOtelSemantics,
276
+ }
@@ -37,7 +37,12 @@ function isPromptCompletionSampled (sampler, span) {
37
37
 
38
38
  module.exports = function makeUtilities (integrationName, tracerConfig) {
39
39
  const integrationConfig = tracerConfig[integrationName] || {}
40
- const { spanCharLimit, spanPromptCompletionSampleRate } = integrationConfig
40
+ // The per-integration config is keyed by the canonical environment variable
41
+ // names (e.g. `DD_LANGCHAIN_SPAN_CHAR_LIMIT`), nested under the integration's
42
+ // namespace. Derive those leaf names from the integration name.
43
+ const prefix = `DD_${integrationName.toUpperCase()}_`
44
+ const spanCharLimit = integrationConfig[`${prefix}SPAN_CHAR_LIMIT`]
45
+ const spanPromptCompletionSampleRate = integrationConfig[`${prefix}SPAN_PROMPT_COMPLETION_SAMPLE_RATE`]
41
46
 
42
47
  const sampler = new Sampler(spanPromptCompletionSampleRate ?? 1)
43
48
 
@@ -22,17 +22,40 @@ const { SAMPLING_RULE_DECISION } = require('../../constants')
22
22
  const { AUTO_KEEP } = require('../../../../../ext/priority')
23
23
  const { version: ddTraceVersion } = require('../../../../../package.json')
24
24
  const {
25
+ CI_JOB_ID,
25
26
  GIT_BRANCH,
26
27
  GIT_COMMIT_SHA,
27
28
  GIT_REPOSITORY_URL,
28
29
  GIT_TAG,
30
+ GIT_COMMIT_AUTHOR_DATE,
29
31
  GIT_COMMIT_AUTHOR_EMAIL,
30
32
  GIT_COMMIT_AUTHOR_NAME,
33
+ GIT_COMMIT_COMMITTER_DATE,
34
+ GIT_COMMIT_COMMITTER_EMAIL,
35
+ GIT_COMMIT_COMMITTER_NAME,
31
36
  GIT_COMMIT_MESSAGE,
37
+ GIT_COMMIT_HEAD_AUTHOR_DATE,
38
+ GIT_COMMIT_HEAD_AUTHOR_EMAIL,
39
+ GIT_COMMIT_HEAD_AUTHOR_NAME,
40
+ GIT_COMMIT_HEAD_COMMITTER_DATE,
41
+ GIT_COMMIT_HEAD_COMMITTER_EMAIL,
42
+ GIT_COMMIT_HEAD_COMMITTER_NAME,
43
+ GIT_COMMIT_HEAD_MESSAGE,
32
44
  CI_WORKSPACE_PATH,
45
+ CI_PIPELINE_ID,
46
+ CI_PIPELINE_NAME,
47
+ CI_PIPELINE_NUMBER,
33
48
  CI_PIPELINE_URL,
34
49
  CI_JOB_NAME,
50
+ CI_JOB_URL,
51
+ CI_NODE_LABELS,
52
+ CI_NODE_NAME,
53
+ CI_PROVIDER_NAME,
54
+ CI_STAGE_NAME,
35
55
  GIT_COMMIT_HEAD_SHA,
56
+ GIT_PULL_REQUEST_BASE_BRANCH,
57
+ GIT_PULL_REQUEST_BASE_BRANCH_HEAD_SHA,
58
+ GIT_PULL_REQUEST_BASE_BRANCH_SHA,
36
59
  } = require('./tags')
37
60
  const { getRuntimeAndOSMetadata } = require('./env')
38
61
  const { getCIMetadata } = require('./ci')
@@ -89,6 +112,12 @@ const TEST_RETRY_REASON = 'test.retry_reason'
89
112
  const TEST_HAS_FAILED_ALL_RETRIES = 'test.has_failed_all_retries'
90
113
  const TEST_IS_MODIFIED = 'test.is_modified'
91
114
  const TEST_HAS_DYNAMIC_NAME = '_dd.has_dynamic_name'
115
+ const EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS = [
116
+ { limitMs: 5 * 1000, key: '5s' },
117
+ { limitMs: 10 * 1000, key: '10s' },
118
+ { limitMs: 30 * 1000, key: '30s' },
119
+ { limitMs: 5 * 60 * 1000, key: '5m' },
120
+ ]
92
121
  const CI_APP_ORIGIN = 'ciapp-test'
93
122
 
94
123
  // Matches patterns that are almost certainly runtime-generated values in test names:
@@ -198,6 +227,43 @@ const TEST_LEVEL_EVENT_TYPES = [
198
227
  'test_module_end',
199
228
  'test_session_end',
200
229
  ]
230
+ const TEST_LEVELS_METADATA = 'test_levels'
231
+ const TEST_LEVELS_METADATA_TAGS = [
232
+ CI_JOB_ID,
233
+ CI_JOB_NAME,
234
+ CI_JOB_URL,
235
+ CI_NODE_LABELS,
236
+ CI_NODE_NAME,
237
+ CI_PIPELINE_ID,
238
+ CI_PIPELINE_NAME,
239
+ CI_PIPELINE_NUMBER,
240
+ CI_PIPELINE_URL,
241
+ CI_PROVIDER_NAME,
242
+ CI_STAGE_NAME,
243
+ CI_WORKSPACE_PATH,
244
+ GIT_BRANCH,
245
+ GIT_COMMIT_AUTHOR_DATE,
246
+ GIT_COMMIT_AUTHOR_EMAIL,
247
+ GIT_COMMIT_AUTHOR_NAME,
248
+ GIT_COMMIT_COMMITTER_DATE,
249
+ GIT_COMMIT_COMMITTER_EMAIL,
250
+ GIT_COMMIT_COMMITTER_NAME,
251
+ GIT_COMMIT_HEAD_AUTHOR_DATE,
252
+ GIT_COMMIT_HEAD_AUTHOR_EMAIL,
253
+ GIT_COMMIT_HEAD_AUTHOR_NAME,
254
+ GIT_COMMIT_HEAD_COMMITTER_DATE,
255
+ GIT_COMMIT_HEAD_COMMITTER_EMAIL,
256
+ GIT_COMMIT_HEAD_COMMITTER_NAME,
257
+ GIT_COMMIT_HEAD_MESSAGE,
258
+ GIT_COMMIT_HEAD_SHA,
259
+ GIT_COMMIT_MESSAGE,
260
+ GIT_COMMIT_SHA,
261
+ GIT_PULL_REQUEST_BASE_BRANCH,
262
+ GIT_PULL_REQUEST_BASE_BRANCH_HEAD_SHA,
263
+ GIT_PULL_REQUEST_BASE_BRANCH_SHA,
264
+ GIT_REPOSITORY_URL,
265
+ GIT_TAG,
266
+ ]
201
267
  const TEST_RETRY_REASON_TYPES = {
202
268
  efd: 'early_flake_detection',
203
269
  atr: 'auto_test_retry',
@@ -398,6 +464,7 @@ module.exports = {
398
464
  getCodeOwnersFileEntries,
399
465
  getCodeOwnersForFilename,
400
466
  getTestCommonTags,
467
+ getTestLevelsMetadataTags,
401
468
  getTestSessionCommonTags,
402
469
  getTestModuleCommonTags,
403
470
  getTestSuiteCommonTags,
@@ -432,6 +499,7 @@ module.exports = {
432
499
  removeInvalidMetadata,
433
500
  parseAnnotations,
434
501
  getIsFaultyEarlyFlakeDetection,
502
+ EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS,
435
503
  getEfdRetryCount,
436
504
  getMaxEfdRetryCount,
437
505
  TEST_BROWSER_DRIVER,
@@ -448,6 +516,7 @@ module.exports = {
448
516
  DD_CAPABILITIES_TEST_MANAGEMENT_ATTEMPT_TO_FIX,
449
517
  DD_CAPABILITIES_FAILED_TEST_REPLAY,
450
518
  TEST_LEVEL_EVENT_TYPES,
519
+ TEST_LEVELS_METADATA,
451
520
  TEST_RETRY_REASON_TYPES,
452
521
  getNumFromKnownTests,
453
522
  getFileAndLineNumberFromError,
@@ -700,6 +769,24 @@ function getTestParametersString (parametersByTestName, testName) {
700
769
  }
701
770
  }
702
771
 
772
+ /**
773
+ * Extracts CI and Git tags that apply to every test level.
774
+ *
775
+ * @param {TestEnvironmentMetadata} testEnvironmentMetadata
776
+ * @returns {Record<string, string|number>}
777
+ */
778
+ function getTestLevelsMetadataTags (testEnvironmentMetadata) {
779
+ const testLevelsMetadataTags = {}
780
+ for (let i = 0; i < TEST_LEVELS_METADATA_TAGS.length; i++) {
781
+ const key = TEST_LEVELS_METADATA_TAGS[i]
782
+ const value = testEnvironmentMetadata[key]
783
+ if (value !== undefined) {
784
+ testLevelsMetadataTags[key] = value
785
+ }
786
+ }
787
+ return testLevelsMetadataTags
788
+ }
789
+
703
790
  function getTestTypeFromFramework (testFramework) {
704
791
  if (testFramework === 'playwright' || testFramework === 'cypress') {
705
792
  return 'browser'
@@ -1367,13 +1454,7 @@ function parseAnnotations (annotations) {
1367
1454
  * @returns {number}
1368
1455
  */
1369
1456
  function getEfdRetryCount (durationMs, slowTestRetries) {
1370
- const thresholds = [
1371
- { limitMs: 5 * 1000, key: '5s' },
1372
- { limitMs: 10 * 1000, key: '10s' },
1373
- { limitMs: 30 * 1000, key: '30s' },
1374
- { limitMs: 5 * 60 * 1000, key: '5m' },
1375
- ]
1376
- for (const { limitMs, key } of thresholds) {
1457
+ for (const { limitMs, key } of EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS) {
1377
1458
  if (durationMs < limitMs) {
1378
1459
  return slowTestRetries[key] ?? 0
1379
1460
  }
@@ -1536,7 +1617,7 @@ function isFailedTestReplaySupported (testFramework, frameworkVersion) {
1536
1617
  : true
1537
1618
  }
1538
1619
 
1539
- function getLibraryCapabilitiesTags (testFramework, frameworkVersion) {
1620
+ function getLibraryCapabilitiesTags (testFramework, frameworkVersion, options = {}) {
1540
1621
  return {
1541
1622
  [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework)
1542
1623
  ? '1'
@@ -1558,7 +1639,8 @@ function getLibraryCapabilitiesTags (testFramework, frameworkVersion) {
1558
1639
  isAttemptToFixSupported(testFramework, frameworkVersion)
1559
1640
  ? '5'
1560
1641
  : undefined,
1561
- [DD_CAPABILITIES_FAILED_TEST_REPLAY]: isFailedTestReplaySupported(testFramework, frameworkVersion)
1642
+ [DD_CAPABILITIES_FAILED_TEST_REPLAY]: !options.omitFailedTestReplay &&
1643
+ isFailedTestReplaySupported(testFramework, frameworkVersion)
1562
1644
  ? '1'
1563
1645
  : undefined,
1564
1646
  }
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { URL } = require('url')
4
4
 
5
+ const log = require('../../log')
6
+
5
7
  const HTTP2_HEADER_AUTHORITY = ':authority'
6
8
  const HTTP2_HEADER_SCHEME = ':scheme'
7
9
  const HTTP2_HEADER_PATH = ':path'
@@ -60,6 +62,69 @@ function obfuscateQs (config, url) {
60
62
  return `${path}?${qs}`
61
63
  }
62
64
 
65
+ const qsObfuscatorCache = new Map()
66
+
67
+ /**
68
+ * Compile the configured query-string obfuscator (a regex string, or a boolean)
69
+ * into the boolean / RegExp form that `obfuscateQs` consumes. The compiled regex
70
+ * is cached, since the configuration is stable for the process lifetime.
71
+ *
72
+ * @param {{ queryStringObfuscation?: boolean | string }} config
73
+ * @returns {boolean | RegExp}
74
+ */
75
+ function getQsObfuscator (config) {
76
+ const obfuscator = config.queryStringObfuscation
77
+
78
+ if (typeof obfuscator === 'boolean') return obfuscator
79
+
80
+ if (typeof obfuscator === 'string') {
81
+ const cached = qsObfuscatorCache.get(obfuscator)
82
+ if (cached !== undefined) return cached
83
+
84
+ let compiled = true
85
+ if (obfuscator === '') {
86
+ compiled = false // disable obfuscator
87
+ } else if (obfuscator !== '.*') { // '.*' optimizes to a full redact (true)
88
+ try {
89
+ compiled = new RegExp(obfuscator, 'gi')
90
+ } catch (err) {
91
+ log.error('Error getting qs obfuscator', err)
92
+ }
93
+ }
94
+
95
+ qsObfuscatorCache.set(obfuscator, compiled)
96
+ return compiled
97
+ }
98
+
99
+ if (Object.hasOwn(config, 'queryStringObfuscation')) {
100
+ log.error('Expected `queryStringObfuscation` to be a regex string or boolean.')
101
+ }
102
+
103
+ return true
104
+ }
105
+
106
+ /**
107
+ * Build a client span's `http.url` with its query retained but obfuscated per
108
+ * `config.queryStringObfuscation` (OTel `url.full` is the absolute URL including
109
+ * the redacted query). Falls back to `strippedUrl` when there is no query.
110
+ * Callers gate this behind `DD_TRACE_OTEL_SEMANTICS_ENABLED`, so the default
111
+ * (flag off) hot path stays a plain tag assignment.
112
+ *
113
+ * @param {{ queryStringObfuscation?: boolean | string }} config
114
+ * @param {string} base `scheme://host[:port]`
115
+ * @param {string} [pathname] raw request path, may include `?query`
116
+ * @param {string} strippedUrl `base` + query-stripped path (used when there is no query)
117
+ * @returns {string}
118
+ */
119
+ function buildClientHttpUrl (config, base, pathname, strippedUrl) {
120
+ if (pathname?.includes('?')) {
121
+ // `config.queryStringObfuscation` is the raw config value here (client plugins
122
+ // don't normalize it the way the server does), so compile it first.
123
+ return obfuscateQs({ queryStringObfuscation: getQsObfuscator(config) }, `${base}${pathname}`)
124
+ }
125
+ return strippedUrl
126
+ }
127
+
63
128
  /**
64
129
  * Extract URL path from URL using regex pattern instead of Node.js URL API because:
65
130
  *
@@ -139,7 +204,9 @@ function filterSensitiveInfoFromRepository (repositoryUrl) {
139
204
  module.exports = {
140
205
  extractURL,
141
206
  obfuscateQs,
207
+ getQsObfuscator,
208
+ buildClientHttpUrl,
142
209
  calculateHttpEndpoint,
143
210
  filterSensitiveInfoFromRepository,
144
- extractPathFromUrl, // test only
211
+ extractPathFromUrl, // used by http-otel-semantics decomposeServerUrl fallback (and tests)
145
212
  }
@@ -13,7 +13,8 @@ const { storage } = require('../../../../datadog-core')
13
13
  const legacyStorage = storage('legacy')
14
14
  const urlFilter = require('./urlfilter')
15
15
  const { createInferredProxySpan, finishInferredProxySpan } = require('./inferred_proxy')
16
- const { extractURL, obfuscateQs, calculateHttpEndpoint } = require('./url')
16
+ const { extractURL, obfuscateQs, getQsObfuscator, calculateHttpEndpoint } = require('./url')
17
+ const { NETWORK_PEER_ADDRESS } = require('./http-otel-semantics')
17
18
 
18
19
  const WEB = types.WEB
19
20
  const SERVER = kinds.SERVER
@@ -389,6 +390,15 @@ function addRequestTags (context, spanType) {
389
390
  [HTTP_USERAGENT]: req.headers['user-agent'],
390
391
  })
391
392
 
393
+ // OTel `network.peer.address` is the immediate socket peer. It has no Datadog
394
+ // equivalent and the socket isn't available at serialization, so (unlike the
395
+ // other HTTP attributes, which are renamed centrally in span_format) it is set
396
+ // here directly when OTel semantics are enabled.
397
+ if (config.DD_TRACE_OTEL_SEMANTICS_ENABLED) {
398
+ const peerAddress = req.socket?.remoteAddress
399
+ if (peerAddress) span.setTag(NETWORK_PEER_ADDRESS, peerAddress)
400
+ }
401
+
392
402
  // if client ip has already been set by appsec, no need to run it again
393
403
  if (config.extractIp && !spanContext.hasTag(HTTP_CLIENT_IP)) {
394
404
  const clientIp = config.extractIp(config, req)
@@ -433,26 +443,25 @@ function applyRouteOrEndpointTag (context) {
433
443
  if (!span) return
434
444
  const spanContext = span.context()
435
445
 
436
- // AppSec calls `web.setRouteOrEndpointTag` from a pre-finish hook so the
437
- // route/endpoint tags are available for API Security sampling, and the
438
- // normal finish-time path runs this again. Either tag being present
439
- // means the work has already been done; paths are stable between the
440
- // two calls, so the second pass has nothing to add.
441
- if (spanContext.hasTag(HTTP_ROUTE) || spanContext.hasTag(HTTP_ENDPOINT)) return
446
+ // AppSec runs this from a pre-finish hook and the finish path runs it
447
+ // again. http.route is terminal once set; nothing supersedes it.
448
+ if (spanContext.hasTag(HTTP_ROUTE)) return
442
449
 
443
450
  // Skip the `Array.prototype.join` builtin in the empty / single-segment
444
451
  // cases; `paths[0]` covers both (`undefined` is falsy for the empty case).
445
452
  const route = paths.length > 1 ? paths.join('') : paths[0]
446
453
 
447
454
  if (route) {
448
- // Use http.route from trusted framework instrumentation.
455
+ // A framework route supersedes any http.endpoint fallback a pre-finish
456
+ // call stamped before the route resolved; it must not be gated on
457
+ // HTTP_ENDPOINT.
449
458
  span.setTag(HTTP_ROUTE, route)
450
459
  return
451
460
  }
452
461
 
453
- if (!config.resourceRenamingEnabled) return
462
+ // Route unavailable. Compute the http.endpoint fallback once across both calls.
463
+ if (!config.resourceRenamingEnabled || spanContext.hasTag(HTTP_ENDPOINT)) return
454
464
 
455
- // Route is unavailable, compute http.endpoint once.
456
465
  const url = spanContext.getTag(HTTP_URL)
457
466
  const endpoint = url ? calculateHttpEndpoint(url) : '/'
458
467
  span.setTag(HTTP_ENDPOINT, endpoint)
@@ -543,30 +552,4 @@ function getMiddlewareSetting (config) {
543
552
  return true
544
553
  }
545
554
 
546
- function getQsObfuscator (config) {
547
- const obfuscator = config.queryStringObfuscation
548
-
549
- if (typeof obfuscator === 'boolean') {
550
- return obfuscator
551
- }
552
-
553
- if (typeof obfuscator === 'string') {
554
- if (obfuscator === '') return false // disable obfuscator
555
-
556
- if (obfuscator === '.*') return true // optimize full redact
557
-
558
- try {
559
- return new RegExp(obfuscator, 'gi')
560
- } catch (err) {
561
- log.error('Web plugin error getting qs obfuscator', err)
562
- }
563
- }
564
-
565
- if (config.hasOwnProperty('queryStringObfuscation')) {
566
- log.error('Expected `queryStringObfuscation` to be a regex string or boolean.')
567
- }
568
-
569
- return true
570
- }
571
-
572
555
  module.exports = web
@@ -19,6 +19,7 @@ const RateLimiter = require('./rate_limiter')
19
19
  const Sampler = require('./sampler')
20
20
  const { setSamplingRules } = require('./startup-log')
21
21
  const SamplingRule = require('./sampling_rule')
22
+ const { formatKnuthRate } = require('./util')
22
23
 
23
24
  const {
24
25
  SAMPLING_MECHANISM_DEFAULT,
@@ -36,19 +37,6 @@ const {
36
37
 
37
38
  const DEFAULT_KEY = 'service:,env:'
38
39
 
39
- /**
40
- * Formats a sampling rate as a string with up to 6 decimal digits and no trailing zeros.
41
- *
42
- * @param {number} rate
43
- */
44
- function formatKnuthRate (rate) {
45
- const string = Number(rate).toFixed(6)
46
- for (let i = string.length - 1; i > 0; i--) {
47
- if (string[i] === '0') continue
48
- return string.slice(0, i + (string[i] === '.' ? 0 : 1))
49
- }
50
- }
51
-
52
40
  const defaultSampler = new Sampler(AUTO_KEEP)
53
41
 
54
42
  /**