dd-trace 6.14.0 → 6.15.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/index.d.ts +8 -0
- package/package.json +3 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +1 -5
- package/packages/datadog-instrumentations/src/jest.js +141 -25
- package/packages/datadog-instrumentations/src/mocha/main.js +41 -5
- package/packages/datadog-instrumentations/src/playwright.js +4 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -70
- package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
- package/packages/dd-trace/src/ci-visibility/requests/video-request.js +4 -0
- package/packages/dd-trace/src/config/supported-configurations.json +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/request-options.js +1 -6
- package/packages/dd-trace/src/evp_proxy/direct.js +2 -28
- package/packages/dd-trace/src/exporters/agentless/writer.js +3 -1
- package/packages/dd-trace/src/exporters/common/proxy.js +52 -0
- package/packages/dd-trace/src/exporters/common/request.js +11 -3
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +5 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +8 -2
- package/packages/dd-trace/src/priority_sampler.js +4 -0
- package/packages/dd-trace/src/sampling_rule.js +3 -1
- package/packages/dd-trace/src/span_processor.js +18 -3
- package/packages/dd-trace/src/telemetry/send-data.js +5 -4
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { getHttpsProxyAgent } = require('../../evp_proxy/direct')
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* @param {ReturnType<import('../config')>} config - Debugger configuration
|
|
7
5
|
* @param {string} path - Request path
|
|
@@ -10,8 +8,7 @@ const { getHttpsProxyAgent } = require('../../evp_proxy/direct')
|
|
|
10
8
|
* method: 'POST',
|
|
11
9
|
* url: string | URL,
|
|
12
10
|
* path: string,
|
|
13
|
-
* headers: Record<string, string
|
|
14
|
-
* agent?: import('node:https').Agent
|
|
11
|
+
* headers: Record<string, string>
|
|
15
12
|
* }}
|
|
16
13
|
*/
|
|
17
14
|
module.exports = function getRequestOptions (config, path, headers) {
|
|
@@ -25,8 +22,6 @@ module.exports = function getRequestOptions (config, path, headers) {
|
|
|
25
22
|
if (config.agentless) {
|
|
26
23
|
if (config.apiKey !== undefined) headers['DD-API-KEY'] = config.apiKey
|
|
27
24
|
headers['DD-EVP-ORIGIN'] = 'agent-debugger'
|
|
28
|
-
const agent = getHttpsProxyAgent(config.url.href)
|
|
29
|
-
if (agent !== undefined) options.agent = agent
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
return options
|
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { HttpsProxyAgent } = require('../../../../vendor/dist/https-proxy-agent')
|
|
4
|
-
const { getProxyForUrl } = require('../../../../vendor/dist/proxy-from-env')
|
|
5
3
|
const { createSiteUrl } = require('../exporters/common/url')
|
|
6
4
|
const log = require('../log')
|
|
7
5
|
|
|
8
|
-
/** @type {Map<string, import('node:https').Agent>} */
|
|
9
|
-
const proxyAgents = new Map()
|
|
10
|
-
|
|
11
6
|
/**
|
|
12
7
|
* @typedef {object} DirectEVPRoute
|
|
13
8
|
* @property {URL} url - Direct intake URL
|
|
14
9
|
* @property {string} basePath - Direct intake base path
|
|
15
10
|
* @property {object} headers - Direct intake authentication headers
|
|
16
|
-
* @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
|
|
17
11
|
*/
|
|
18
12
|
|
|
19
13
|
/**
|
|
@@ -33,36 +27,16 @@ function createDirectEVPRoute (config, intake) {
|
|
|
33
27
|
const url = createSiteUrl(config.site, intake)
|
|
34
28
|
if (url === undefined) throw new Error('Invalid direct EVP intake URL')
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const route = {
|
|
30
|
+
return {
|
|
39
31
|
url,
|
|
40
32
|
basePath: '',
|
|
41
33
|
headers: {
|
|
42
34
|
'DD-API-KEY': apiKey,
|
|
43
35
|
},
|
|
44
36
|
}
|
|
45
|
-
if (agent) route.agent = agent
|
|
46
|
-
return route
|
|
47
37
|
} catch (error) {
|
|
48
38
|
log.debug('Unable to configure direct EVP intake: %s', error.message)
|
|
49
39
|
}
|
|
50
40
|
}
|
|
51
41
|
|
|
52
|
-
|
|
53
|
-
* @param {string} url
|
|
54
|
-
* @returns {import('node:https').Agent|undefined}
|
|
55
|
-
*/
|
|
56
|
-
function getHttpsProxyAgent (url) {
|
|
57
|
-
const proxyUrl = getProxyForUrl(url)
|
|
58
|
-
if (!proxyUrl) return
|
|
59
|
-
|
|
60
|
-
let agent = proxyAgents.get(proxyUrl)
|
|
61
|
-
if (agent === undefined) {
|
|
62
|
-
agent = new HttpsProxyAgent(proxyUrl)
|
|
63
|
-
proxyAgents.set(proxyUrl, agent)
|
|
64
|
-
}
|
|
65
|
-
return agent
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
module.exports = { createDirectEVPRoute, getHttpsProxyAgent }
|
|
42
|
+
module.exports = { createDirectEVPRoute }
|
|
@@ -8,6 +8,7 @@ const log = require('../../log')
|
|
|
8
8
|
const tracerVersion = require('../../../../../package.json').version
|
|
9
9
|
|
|
10
10
|
const { canSendApiKey } = require('../common/url')
|
|
11
|
+
const { getHttpsProxyAgent } = require('../common/proxy')
|
|
11
12
|
const BaseWriter = require('../common/writer')
|
|
12
13
|
const { AgentEncoder } = require('../../encode/0.4')
|
|
13
14
|
const { computeIntakeUrl, INTAKE_PATH } = require('./intake')
|
|
@@ -153,6 +154,7 @@ class AgentlessWriter extends BaseWriter {
|
|
|
153
154
|
|
|
154
155
|
this.#closeExporter()
|
|
155
156
|
const config = getConfig()
|
|
157
|
+
const agent = this._url.protocol === 'https:' ? getHttpsProxyAgent(this._url) : undefined
|
|
156
158
|
this.#exporter = createAgentlessExporter({
|
|
157
159
|
endpoint: this.#endpoint(),
|
|
158
160
|
apiKey,
|
|
@@ -165,7 +167,7 @@ class AgentlessWriter extends BaseWriter {
|
|
|
165
167
|
tracerVersion,
|
|
166
168
|
languageVersion: process.version,
|
|
167
169
|
languageInterpreter: process.versions.bun ? 'JavaScriptCore' : 'v8',
|
|
168
|
-
})
|
|
170
|
+
}, { agent })
|
|
169
171
|
this.#exporterApiKey = apiKey
|
|
170
172
|
this.#exporterEnv = env
|
|
171
173
|
this.#exporterRuntimeId = runtimeID
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { isIPv6 } = require('node:net')
|
|
4
|
+
|
|
5
|
+
let defaultAgentKey
|
|
6
|
+
let getProxyForUrl
|
|
7
|
+
let proxyAgents
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Selects a proxy agent for an HTTPS target while preserving the direct agent's pool boundaries.
|
|
11
|
+
*
|
|
12
|
+
* @param {string|URL|object} url
|
|
13
|
+
* @param {import('node:http').Agent|false} [directAgent]
|
|
14
|
+
* @returns {import('node:http').Agent|false|undefined}
|
|
15
|
+
*/
|
|
16
|
+
function getHttpsProxyAgent (url, directAgent) {
|
|
17
|
+
getProxyForUrl ??= require('../../../../../vendor/dist/proxy-from-env').getProxyForUrl
|
|
18
|
+
|
|
19
|
+
const host = typeof url === 'string' ? undefined : url.host ?? url.hostname
|
|
20
|
+
const isUnbracketedIPv6 = typeof host === 'string' && host.includes(':') && isIPv6(host)
|
|
21
|
+
const target = typeof url === 'string'
|
|
22
|
+
? url
|
|
23
|
+
: { protocol: url.protocol, host: isUnbracketedIPv6 ? `[${host}]` : host, port: url.port }
|
|
24
|
+
const proxyUrl = getProxyForUrl(target)
|
|
25
|
+
if (!proxyUrl) return directAgent
|
|
26
|
+
|
|
27
|
+
directAgent ??= require('node:https').globalAgent
|
|
28
|
+
|
|
29
|
+
if (proxyAgents === undefined) {
|
|
30
|
+
defaultAgentKey = {}
|
|
31
|
+
proxyAgents = new WeakMap()
|
|
32
|
+
}
|
|
33
|
+
const cacheKey = directAgent || defaultAgentKey
|
|
34
|
+
let agents = proxyAgents.get(cacheKey)
|
|
35
|
+
if (agents === undefined) {
|
|
36
|
+
agents = new Map()
|
|
37
|
+
proxyAgents.set(cacheKey, agents)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let agent = agents.get(proxyUrl)
|
|
41
|
+
if (agent === undefined) {
|
|
42
|
+
const { HttpsProxyAgent } = require('../../../../../vendor/dist/https-proxy-agent')
|
|
43
|
+
const options = directAgent
|
|
44
|
+
? { keepAlive: directAgent.keepAlive, maxSockets: directAgent.maxSockets }
|
|
45
|
+
: undefined
|
|
46
|
+
agent = new HttpsProxyAgent(proxyUrl, options)
|
|
47
|
+
agents.set(proxyUrl, agent)
|
|
48
|
+
}
|
|
49
|
+
return agent
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { getHttpsProxyAgent }
|
|
@@ -13,6 +13,7 @@ const log = require('../../log')
|
|
|
13
13
|
const { canSendApiKey, parseUrl } = require('./url')
|
|
14
14
|
const docker = require('./docker')
|
|
15
15
|
const { httpAgent, httpsAgent } = require('./agents')
|
|
16
|
+
const { getHttpsProxyAgent } = require('./proxy')
|
|
16
17
|
const {
|
|
17
18
|
getMaxAttempts,
|
|
18
19
|
getRetryDelay,
|
|
@@ -94,11 +95,18 @@ function request (data, options, callback) {
|
|
|
94
95
|
|
|
95
96
|
docker.inject(options.headers)
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
let agent = options.agent ?? (isSecure ? httpsAgent : httpAgent)
|
|
99
|
+
if (hasApiKey && isSecure) {
|
|
100
|
+
try {
|
|
101
|
+
agent = getHttpsProxyAgent(options, agent)
|
|
102
|
+
} catch (error) {
|
|
103
|
+
callback(error)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
100
106
|
}
|
|
101
107
|
|
|
108
|
+
const connectionOptions = { ...options, agent }
|
|
109
|
+
|
|
102
110
|
/**
|
|
103
111
|
* @param {import('node:http').IncomingMessage} res
|
|
104
112
|
* @param {(error: Error|null, result?: string|null, statusCode?: number,
|
|
@@ -6,7 +6,9 @@ const { URL } = require('node:url')
|
|
|
6
6
|
const { storage } = require('../../../../datadog-core')
|
|
7
7
|
const log = require('../../log')
|
|
8
8
|
const { createServerlessDeliveryTracker } = require('../../serverless')
|
|
9
|
+
const { getHttpsProxyAgent } = require('../../exporters/common/proxy')
|
|
9
10
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
11
|
+
const { version: tracerVersion } = require('../../../../../package.json')
|
|
10
12
|
|
|
11
13
|
const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
|
|
12
14
|
const legacyStorage = storage('legacy')
|
|
@@ -48,8 +50,10 @@ class OtlpHttpExporterBase {
|
|
|
48
50
|
hostname: parsedUrl.hostname,
|
|
49
51
|
port: parsedUrl.port,
|
|
50
52
|
path: parsedUrl.pathname + parsedUrl.search,
|
|
53
|
+
agent: parsedUrl.protocol === 'https:' ? getHttpsProxyAgent(parsedUrl) : undefined,
|
|
51
54
|
headers: {
|
|
52
55
|
'Content-Type': isJson ? 'application/json' : 'application/x-protobuf',
|
|
56
|
+
'User-Agent': `dd-trace-js/${tracerVersion}`,
|
|
53
57
|
...headers,
|
|
54
58
|
},
|
|
55
59
|
}
|
|
@@ -169,6 +173,7 @@ class OtlpHttpExporterBase {
|
|
|
169
173
|
this.options.hostname = parsedUrl.hostname
|
|
170
174
|
this.options.port = parsedUrl.port
|
|
171
175
|
this.options.path = parsedUrl.pathname + parsedUrl.search
|
|
176
|
+
this.options.agent = parsedUrl.protocol === 'https:' ? getHttpsProxyAgent(parsedUrl) : undefined
|
|
172
177
|
this.telemetryTags[0] = `protocol:${this.#transport === https ? 'https' : 'http'}`
|
|
173
178
|
}
|
|
174
179
|
|
|
@@ -363,6 +363,11 @@ class TextMapPropagator {
|
|
|
363
363
|
* @returns {DatadogSpanContext | null}
|
|
364
364
|
*/
|
|
365
365
|
extract (carrier) {
|
|
366
|
+
if (!carrier || typeof carrier !== 'object') {
|
|
367
|
+
if (this.#config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT !== 'ignore') removeAllBaggageItems()
|
|
368
|
+
return null
|
|
369
|
+
}
|
|
370
|
+
|
|
366
371
|
const spanContext = this.#extractSpanContext(carrier)
|
|
367
372
|
if (spanContext === undefined) return null
|
|
368
373
|
|
|
@@ -825,7 +830,6 @@ class TextMapPropagator {
|
|
|
825
830
|
* @returns {DatadogSpanContext | undefined}
|
|
826
831
|
*/
|
|
827
832
|
#extractDatadogContext (carrier) {
|
|
828
|
-
if (!carrier) return
|
|
829
833
|
const traceId = readDatadogTraceId(carrier)
|
|
830
834
|
if (!traceId) return
|
|
831
835
|
const spanContext = extractGenericContext(traceId, readDatadogParentId(carrier), 10)
|
|
@@ -860,6 +864,8 @@ class TextMapPropagator {
|
|
|
860
864
|
} catch {
|
|
861
865
|
return
|
|
862
866
|
}
|
|
867
|
+
if (!parsed || typeof parsed !== 'object') return
|
|
868
|
+
|
|
863
869
|
const spanContext = this.#extractDatadogContext(parsed)
|
|
864
870
|
if (!spanContext) return
|
|
865
871
|
|
|
@@ -983,7 +989,7 @@ class TextMapPropagator {
|
|
|
983
989
|
*/
|
|
984
990
|
#extractBaggageItems (carrier, spanContext, extractBaggage) {
|
|
985
991
|
removeAllBaggageItems()
|
|
986
|
-
if (!
|
|
992
|
+
if (!extractBaggage) return
|
|
987
993
|
const header = readBaggage(carrier)
|
|
988
994
|
if (!header) return
|
|
989
995
|
|
|
@@ -263,6 +263,10 @@ class PrioritySampler {
|
|
|
263
263
|
context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_DYNAMIC
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
if (rule.discard) {
|
|
267
|
+
context._sampling.discard = true
|
|
268
|
+
}
|
|
269
|
+
|
|
266
270
|
return rule.sample(context) && this._isSampledByRateLimit(context)
|
|
267
271
|
? USER_KEEP
|
|
268
272
|
: USER_REJECT
|
|
@@ -185,6 +185,7 @@ function resourceLocator (span) {
|
|
|
185
185
|
* @property {number} [sampleRate=1] - Deterministic sampling rate in [0, 1].
|
|
186
186
|
* @property {string} [provenance] - Optional provenance/metadata for this rule.
|
|
187
187
|
* @property {number} [maxPerSecond] - Maximum samples per second (rate limit).
|
|
188
|
+
* @property {boolean} [discard=false] - Whether to fully drop a trace if not kept.
|
|
188
189
|
*/
|
|
189
190
|
|
|
190
191
|
/**
|
|
@@ -195,7 +196,7 @@ class SamplingRule {
|
|
|
195
196
|
/**
|
|
196
197
|
* @param {SamplingRuleConfig} [config]
|
|
197
198
|
*/
|
|
198
|
-
constructor ({ name, service, resource, tags, sampleRate = 1, provenance, maxPerSecond } = {}) {
|
|
199
|
+
constructor ({ name, service, resource, tags, sampleRate = 1, provenance, maxPerSecond, discard = false } = {}) {
|
|
199
200
|
this.matchers = []
|
|
200
201
|
|
|
201
202
|
if (name !== undefined) {
|
|
@@ -216,6 +217,7 @@ class SamplingRule {
|
|
|
216
217
|
this._sampler = new Sampler(sampleRate)
|
|
217
218
|
this._limiter = undefined
|
|
218
219
|
this.provenance = provenance
|
|
220
|
+
this.discard = !!discard
|
|
219
221
|
|
|
220
222
|
if (Number.isFinite(maxPerSecond)) {
|
|
221
223
|
this._limiter = new RateLimiter(maxPerSecond)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { AUTO_REJECT } = require('../../../ext/priority')
|
|
3
4
|
const log = require('./log')
|
|
4
5
|
const spanFormat = require('./span_format')
|
|
5
6
|
const SpanSampler = require('./span_sampler')
|
|
@@ -34,7 +35,20 @@ class SpanProcessor {
|
|
|
34
35
|
sample (span) {
|
|
35
36
|
const spanContext = span.context()
|
|
36
37
|
this._prioritySampler.sample(spanContext)
|
|
37
|
-
this
|
|
38
|
+
if (!this.#isDiscarded(spanContext)) {
|
|
39
|
+
this._spanSampler.sample(spanContext)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A rule's reject decision can later be overridden (e.g. a product force-keeping the trace via
|
|
45
|
+
* `PrioritySampler.keepTrace()`), so `discard` only applies while the priority is still a reject.
|
|
46
|
+
*
|
|
47
|
+
* @param {import('./opentracing/span_context')} spanContext
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
#isDiscarded (spanContext) {
|
|
51
|
+
return spanContext._sampling.discard && spanContext._sampling.priority <= AUTO_REJECT
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
process (span) {
|
|
@@ -56,11 +70,12 @@ class SpanProcessor {
|
|
|
56
70
|
|
|
57
71
|
let isFirstSpanInChunk = true
|
|
58
72
|
const stampApmDisabled = this._config.apmTracingEnabled === false
|
|
73
|
+
const discard = this.#isDiscarded(spanContext)
|
|
59
74
|
|
|
60
75
|
for (const span of started) {
|
|
61
76
|
if (span._duration === undefined) {
|
|
62
77
|
active.push(span)
|
|
63
|
-
} else {
|
|
78
|
+
} else if (!discard) {
|
|
64
79
|
const formattedSpan = spanFormat(span, isFirstSpanInChunk, this._processTags)
|
|
65
80
|
if (stampApmDisabled) {
|
|
66
81
|
formattedSpan.metrics[APM_TRACING_ENABLED_KEY] = 0
|
|
@@ -76,7 +91,7 @@ class SpanProcessor {
|
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
93
|
|
|
79
|
-
if (formatted.length !== 0 && trace.isRecording !== false) {
|
|
94
|
+
if (!discard && formatted.length !== 0 && trace.isRecording !== false) {
|
|
80
95
|
this._exporter.export(formatted)
|
|
81
96
|
}
|
|
82
97
|
|
|
@@ -76,9 +76,10 @@ let getTestOptimizationAgent
|
|
|
76
76
|
* @param {import('../config/config-base')} config
|
|
77
77
|
* @param {TelemetryApplication} application
|
|
78
78
|
* @param {TelemetryRequestType} reqType
|
|
79
|
+
* @param {string} [apiKey]
|
|
79
80
|
* @returns {Record<string, string>}
|
|
80
81
|
*/
|
|
81
|
-
function getHeaders (config, application, reqType) {
|
|
82
|
+
function getHeaders (config, application, reqType, apiKey) {
|
|
82
83
|
const headers = {
|
|
83
84
|
'DD-Client-Library-Language': application.language_name,
|
|
84
85
|
'DD-Client-Library-Version': application.tracer_version,
|
|
@@ -94,8 +95,8 @@ function getHeaders (config, application, reqType) {
|
|
|
94
95
|
if (debug) {
|
|
95
96
|
headers['dd-telemetry-debug-enabled'] = 'true'
|
|
96
97
|
}
|
|
97
|
-
if (
|
|
98
|
-
headers['dd-api-key'] =
|
|
98
|
+
if (apiKey) {
|
|
99
|
+
headers['dd-api-key'] = apiKey
|
|
99
100
|
}
|
|
100
101
|
return headers
|
|
101
102
|
}
|
|
@@ -161,7 +162,7 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
|
|
|
161
162
|
port,
|
|
162
163
|
method: 'POST',
|
|
163
164
|
path: isAgentlessMode ? '/api/v2/apmtelemetry' : '/telemetry/proxy/api/v2/apmtelemetry',
|
|
164
|
-
headers: getHeaders(config, application, reqType),
|
|
165
|
+
headers: getHeaders(config, application, reqType, isAgentlessMode ? config.DD_API_KEY : undefined),
|
|
165
166
|
}
|
|
166
167
|
if (isCiVisibility) options.agent = getTestOptimizationAgent(url)
|
|
167
168
|
|