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.
- package/README.md +13 -8
- package/ci/init.js +25 -5
- package/ci/vitest-no-worker-init-setup.mjs +430 -0
- package/ext/tags.js +2 -0
- package/index.d.ts +79 -24
- package/initialize.mjs +5 -6
- package/loader-hook.mjs +154 -47
- package/package.json +20 -16
- package/packages/datadog-esbuild/index.js +26 -0
- package/packages/datadog-esbuild/src/utils.js +46 -3
- package/packages/datadog-instrumentations/src/connect.js +4 -3
- package/packages/datadog-instrumentations/src/fastify.js +4 -12
- package/packages/datadog-instrumentations/src/fs.js +8 -6
- package/packages/datadog-instrumentations/src/graphql.js +26 -484
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
- package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
- package/packages/datadog-instrumentations/src/koa.js +3 -2
- package/packages/datadog-instrumentations/src/mariadb.js +4 -2
- package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +14 -2
- package/packages/datadog-instrumentations/src/next.js +10 -10
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-instrumentations/src/restify.js +4 -3
- package/packages/datadog-instrumentations/src/router.js +18 -5
- package/packages/datadog-instrumentations/src/selenium.js +1 -2
- package/packages/datadog-instrumentations/src/tedious.js +28 -0
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1293 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +1567 -0
- package/packages/datadog-instrumentations/src/vitest-util.js +249 -0
- package/packages/datadog-instrumentations/src/vitest-worker.js +753 -0
- package/packages/datadog-instrumentations/src/vitest.js +11 -1598
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +18 -10
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
- package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -5
- package/packages/datadog-plugin-cypress/src/support.js +45 -1
- package/packages/datadog-plugin-express/src/code_origin.js +1 -1
- package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +627 -11
- package/packages/datadog-plugin-graphql/src/index.js +20 -8
- package/packages/datadog-plugin-graphql/src/parse.js +24 -4
- package/packages/datadog-plugin-graphql/src/validate.js +16 -5
- package/packages/datadog-plugin-http/src/client.js +8 -3
- package/packages/datadog-plugin-http/src/index.js +6 -8
- package/packages/datadog-plugin-http2/src/client.js +5 -2
- package/packages/datadog-plugin-jest/src/index.js +1 -1
- package/packages/datadog-plugin-mocha/src/index.js +1 -1
- package/packages/datadog-plugin-mysql/src/index.js +7 -2
- package/packages/datadog-plugin-openai/src/services.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
- package/packages/datadog-plugin-undici/src/index.js +5 -2
- package/packages/datadog-plugin-vitest/src/index.js +76 -64
- package/packages/datadog-shimmer/src/shimmer.js +37 -16
- package/packages/datadog-webpack/index.js +17 -1
- package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
- package/packages/dd-trace/src/aiguard/sdk.js +15 -3
- package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
- package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
- package/packages/dd-trace/src/appsec/channels.js +3 -1
- package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
- package/packages/dd-trace/src/appsec/graphql.js +5 -5
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +26 -19
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
- package/packages/dd-trace/src/appsec/index.js +20 -7
- package/packages/dd-trace/src/appsec/lambda.js +135 -0
- package/packages/dd-trace/src/appsec/reporter.js +50 -10
- package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +27 -1
- package/packages/dd-trace/src/appsec/waf/index.js +16 -4
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -13
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
- package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
- package/packages/dd-trace/src/config/defaults.js +25 -5
- package/packages/dd-trace/src/config/generated-config-types.d.ts +596 -58
- package/packages/dd-trace/src/config/helper.js +134 -33
- package/packages/dd-trace/src/config/index.js +39 -23
- package/packages/dd-trace/src/config/major-overrides.js +4 -2
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +109 -57
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/datastreams/processor.js +11 -3
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
- package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
- package/packages/dd-trace/src/debugger/index.js +5 -0
- package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
- package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
- package/packages/dd-trace/src/exporters/agent/index.js +1 -1
- package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
- package/packages/dd-trace/src/exporters/common/request.js +1 -20
- package/packages/dd-trace/src/exporters/common/url.js +34 -0
- package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
- package/packages/dd-trace/src/guardrails/index.js +8 -5
- package/packages/dd-trace/src/index.js +6 -4
- package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
- package/packages/dd-trace/src/llmobs/index.js +17 -6
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +8 -6
- package/packages/dd-trace/src/llmobs/span_processor.js +5 -1
- package/packages/dd-trace/src/llmobs/tagger.js +58 -3
- package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
- package/packages/dd-trace/src/log/index.js +18 -20
- package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
- package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
- package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +44 -38
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
- package/packages/dd-trace/src/opentelemetry/span.js +14 -3
- package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
- package/packages/dd-trace/src/opentracing/span_context.js +17 -0
- package/packages/dd-trace/src/opentracing/tracer.js +6 -1
- package/packages/dd-trace/src/plugin_manager.js +14 -8
- package/packages/dd-trace/src/plugins/ci_plugin.js +21 -4
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
- package/packages/dd-trace/src/plugins/util/llm.js +6 -1
- package/packages/dd-trace/src/plugins/util/test.js +91 -9
- package/packages/dd-trace/src/plugins/util/url.js +68 -1
- package/packages/dd-trace/src/plugins/util/web.js +19 -36
- package/packages/dd-trace/src/priority_sampler.js +1 -13
- package/packages/dd-trace/src/profiling/config.js +129 -198
- package/packages/dd-trace/src/profiling/constants.js +4 -7
- package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
- package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
- package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
- package/packages/dd-trace/src/profiling/oom.js +71 -0
- package/packages/dd-trace/src/profiling/profiler.js +35 -23
- package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
- package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
- package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
- package/packages/dd-trace/src/proxy.js +8 -8
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +1 -2
- package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
- package/packages/dd-trace/src/span_format.js +2 -1
- package/packages/dd-trace/src/span_processor.js +11 -6
- package/packages/dd-trace/src/span_stats.js +96 -78
- package/packages/dd-trace/src/startup-log.js +2 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
- package/packages/dd-trace/src/telemetry/index.js +2 -2
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/telemetry/send-data.js +20 -18
- package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
- package/packages/dd-trace/src/tracer.js +15 -1
- package/packages/dd-trace/src/util.js +14 -0
- package/register.js +58 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
- 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
|
-
|
|
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
|
|
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]:
|
|
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, //
|
|
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
|
|
437
|
-
// route
|
|
438
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
/**
|