dd-trace 2.14.0 → 2.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/LICENSE-3rdparty.csv +1 -0
- package/ext/tags.d.ts +2 -1
- package/ext/tags.js +2 -1
- package/index.d.ts +43 -20
- package/package.json +4 -3
- package/packages/datadog-instrumentations/src/crypto.js +30 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/http/server.js +1 -1
- package/packages/datadog-instrumentations/src/net.js +13 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +19 -10
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/index.js +20 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +48 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +24 -0
- package/packages/dd-trace/src/appsec/iast/iast-context.js +50 -0
- package/packages/dd-trace/src/appsec/iast/index.js +59 -0
- package/packages/dd-trace/src/appsec/iast/overhead-controller.js +94 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +70 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +113 -0
- package/packages/dd-trace/src/config.js +76 -10
- package/packages/dd-trace/src/constants.js +9 -1
- package/packages/dd-trace/src/encode/span-stats.js +155 -0
- package/packages/dd-trace/src/exporters/agent/index.js +14 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +6 -3
- package/packages/dd-trace/src/exporters/common/request.js +6 -2
- package/packages/dd-trace/src/exporters/span-stats/index.js +20 -0
- package/packages/dd-trace/src/exporters/span-stats/writer.js +54 -0
- package/packages/dd-trace/src/format.js +2 -0
- package/packages/dd-trace/src/iitm.js +11 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +71 -0
- package/packages/dd-trace/src/opentracing/tracer.js +1 -1
- package/packages/dd-trace/src/plugin_manager.js +12 -2
- package/packages/dd-trace/src/plugins/log_plugin.js +16 -9
- package/packages/dd-trace/src/plugins/util/ip_blocklist.js +25 -0
- package/packages/dd-trace/src/plugins/util/web.js +99 -2
- package/packages/dd-trace/src/priority_sampler.js +36 -1
- package/packages/dd-trace/src/proxy.js +3 -0
- package/packages/dd-trace/src/ritm.js +10 -1
- package/packages/dd-trace/src/span_processor.js +7 -1
- package/packages/dd-trace/src/span_stats.js +210 -0
- package/packages/dd-trace/src/telemetry/dependencies.js +83 -0
- package/packages/dd-trace/src/{telemetry.js → telemetry/index.js} +10 -65
- package/packages/dd-trace/src/telemetry/send-data.js +35 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { MANUAL_KEEP } = require('../../../../../ext/tags')
|
|
2
|
+
const VULNERABILITIES_KEY = 'vulnerabilities'
|
|
3
|
+
const IAST_JSON_TAG_KEY = '_dd.iast.json'
|
|
4
|
+
|
|
5
|
+
function createVulnerability (type, evidence, spanId, location) {
|
|
6
|
+
if (type && evidence && spanId) {
|
|
7
|
+
return {
|
|
8
|
+
type,
|
|
9
|
+
evidence,
|
|
10
|
+
location: {
|
|
11
|
+
spanId,
|
|
12
|
+
...location
|
|
13
|
+
},
|
|
14
|
+
hash: createHash(type, location)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createHash (type, location) {
|
|
21
|
+
let hashSource
|
|
22
|
+
if (location) {
|
|
23
|
+
hashSource = `${type}:${location.path}:${location.line}`
|
|
24
|
+
} else {
|
|
25
|
+
hashSource = type
|
|
26
|
+
}
|
|
27
|
+
let hash = 0
|
|
28
|
+
let offset = 0
|
|
29
|
+
const size = hashSource.length
|
|
30
|
+
for (let i = 0; i < size; i++) {
|
|
31
|
+
hash = ((hash << 5) - hash) + hashSource.charCodeAt(offset++)
|
|
32
|
+
}
|
|
33
|
+
return hash
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function addVulnerability (iastContext, vulnerability) {
|
|
37
|
+
if (iastContext && vulnerability && vulnerability.evidence && vulnerability.type &&
|
|
38
|
+
vulnerability.location && vulnerability.location.spanId) {
|
|
39
|
+
iastContext[VULNERABILITIES_KEY] = iastContext[VULNERABILITIES_KEY] || []
|
|
40
|
+
iastContext[VULNERABILITIES_KEY].push(vulnerability)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isValidVulnerability (vulnerability) {
|
|
45
|
+
return vulnerability && vulnerability.type &&
|
|
46
|
+
vulnerability.evidence && vulnerability.evidence.value &&
|
|
47
|
+
vulnerability.location && vulnerability.location.spanId
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function jsonVulnerabilityFromVulnerability (vulnerability) {
|
|
51
|
+
const jsonVulnerability = {
|
|
52
|
+
type: vulnerability.type,
|
|
53
|
+
hash: vulnerability.hash,
|
|
54
|
+
evidence: {
|
|
55
|
+
value: vulnerability.evidence.value
|
|
56
|
+
},
|
|
57
|
+
location: {
|
|
58
|
+
spanId: vulnerability.location.spanId
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (vulnerability.location.path) {
|
|
62
|
+
jsonVulnerability.location.path = vulnerability.location.path
|
|
63
|
+
}
|
|
64
|
+
if (vulnerability.location.line) {
|
|
65
|
+
jsonVulnerability.location.line = vulnerability.location.line
|
|
66
|
+
}
|
|
67
|
+
return jsonVulnerability
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sendVulnerabilities (iastContext) {
|
|
71
|
+
if (iastContext && iastContext.rootSpan && iastContext[VULNERABILITIES_KEY] &&
|
|
72
|
+
iastContext[VULNERABILITIES_KEY].length && iastContext.rootSpan.addTags) {
|
|
73
|
+
const span = iastContext.rootSpan
|
|
74
|
+
const allVulnerabilities = iastContext[VULNERABILITIES_KEY]
|
|
75
|
+
// TODO support sources and ranges
|
|
76
|
+
const jsonToSend = {
|
|
77
|
+
vulnerabilities: []
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
deduplicateVulnerabilities(allVulnerabilities).forEach((vulnerability) => {
|
|
81
|
+
if (isValidVulnerability(vulnerability)) {
|
|
82
|
+
jsonToSend.vulnerabilities.push(jsonVulnerabilityFromVulnerability(vulnerability))
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
if (jsonToSend.vulnerabilities.length > 0) {
|
|
87
|
+
const tags = {}
|
|
88
|
+
// TODO: Store this outside of the span and set the tag in the exporter.
|
|
89
|
+
tags[IAST_JSON_TAG_KEY] = JSON.stringify(jsonToSend)
|
|
90
|
+
tags[MANUAL_KEEP] = 'true'
|
|
91
|
+
span.addTags(tags)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return IAST_JSON_TAG_KEY
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function deduplicateVulnerabilities (vulnerabilities) {
|
|
98
|
+
const uniqueVulnerabilities = new Set()
|
|
99
|
+
return vulnerabilities.filter((vulnerability) => {
|
|
100
|
+
const key = `${vulnerability.type}${vulnerability.hash}`
|
|
101
|
+
if (!uniqueVulnerabilities.has(key)) {
|
|
102
|
+
uniqueVulnerabilities.add(key)
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
return false
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
createVulnerability,
|
|
111
|
+
addVulnerability,
|
|
112
|
+
sendVulnerabilities
|
|
113
|
+
}
|
|
@@ -13,6 +13,14 @@ const uuid = require('crypto-randomuuid')
|
|
|
13
13
|
const fromEntries = Object.fromEntries || (entries =>
|
|
14
14
|
entries.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {}))
|
|
15
15
|
|
|
16
|
+
function safeJsonParse (input) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(input)
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
class Config {
|
|
17
25
|
constructor (options) {
|
|
18
26
|
options = options || {}
|
|
@@ -115,6 +123,14 @@ class Config {
|
|
|
115
123
|
parseInt(process.env.DD_TRACE_PARTIAL_FLUSH_MIN_SPANS),
|
|
116
124
|
1000
|
|
117
125
|
)
|
|
126
|
+
const DD_TRACE_CLIENT_IP_HEADER_DISABLED = coalesce(
|
|
127
|
+
process.env.DD_TRACE_CLIENT_IP_HEADER_DISABLED,
|
|
128
|
+
false
|
|
129
|
+
)
|
|
130
|
+
const DD_TRACE_CLIENT_IP_HEADER = coalesce(
|
|
131
|
+
process.env.DD_TRACE_CLIENT_IP_HEADER,
|
|
132
|
+
null
|
|
133
|
+
)
|
|
118
134
|
const DD_TRACE_B3_ENABLED = coalesce(
|
|
119
135
|
options.experimental && options.experimental.b3,
|
|
120
136
|
process.env.DD_TRACE_EXPERIMENTAL_B3_ENABLED,
|
|
@@ -140,6 +156,17 @@ class Config {
|
|
|
140
156
|
false
|
|
141
157
|
)
|
|
142
158
|
|
|
159
|
+
const DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH = coalesce(
|
|
160
|
+
process.env.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH,
|
|
161
|
+
'512'
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
const DD_TRACE_STATS_COMPUTATION_ENABLED = coalesce(
|
|
165
|
+
options.stats,
|
|
166
|
+
process.env.DD_TRACE_STATS_COMPUTATION_ENABLED,
|
|
167
|
+
false
|
|
168
|
+
)
|
|
169
|
+
|
|
143
170
|
let appsec = options.appsec || (options.experimental && options.experimental.appsec)
|
|
144
171
|
|
|
145
172
|
const DD_APPSEC_ENABLED = coalesce(
|
|
@@ -180,24 +207,51 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
180
207
|
|[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}`
|
|
181
208
|
)
|
|
182
209
|
|
|
210
|
+
const iastOptions = options.experimental && options.experimental.iast
|
|
211
|
+
const DD_IAST_ENABLED = coalesce(
|
|
212
|
+
iastOptions &&
|
|
213
|
+
(iastOptions === true || iastOptions.enabled === true),
|
|
214
|
+
process.env.DD_IAST_ENABLED,
|
|
215
|
+
false
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
const defaultIastRequestSampling = 30
|
|
219
|
+
const iastRequestSampling = coalesce(
|
|
220
|
+
parseInt(iastOptions && iastOptions.requestSampling),
|
|
221
|
+
parseInt(process.env.DD_IAST_REQUEST_SAMPLING),
|
|
222
|
+
defaultIastRequestSampling
|
|
223
|
+
)
|
|
224
|
+
const DD_IAST_REQUEST_SAMPLING = iastRequestSampling < 0 ||
|
|
225
|
+
iastRequestSampling > 100 ? defaultIastRequestSampling : iastRequestSampling
|
|
226
|
+
|
|
227
|
+
const DD_IAST_MAX_CONCURRENT_REQUESTS = coalesce(
|
|
228
|
+
parseInt(iastOptions && iastOptions.maxConcurrentRequests),
|
|
229
|
+
parseInt(process.env.DD_IAST_MAX_CONCURRENT_REQUESTS),
|
|
230
|
+
2
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
const DD_IAST_MAX_CONTEXT_OPERATIONS = coalesce(
|
|
234
|
+
parseInt(iastOptions && iastOptions.maxContextOperations),
|
|
235
|
+
parseInt(process.env.DD_IAST_MAX_CONTEXT_OPERATIONS),
|
|
236
|
+
2
|
|
237
|
+
)
|
|
238
|
+
|
|
183
239
|
const DD_CIVISIBILITY_GIT_UPLOAD_ENABLED = coalesce(
|
|
184
240
|
process.env.DD_CIVISIBILITY_GIT_UPLOAD_ENABLED,
|
|
185
241
|
false
|
|
186
242
|
)
|
|
187
243
|
|
|
188
|
-
const sampler = (options.experimental && options.experimental.sampler) || {}
|
|
189
244
|
const ingestion = options.ingestion || {}
|
|
190
245
|
const dogstatsd = coalesce(options.dogstatsd, {})
|
|
191
|
-
|
|
192
|
-
Object.assign(sampler, {
|
|
246
|
+
const sampler = {
|
|
193
247
|
sampleRate: coalesce(
|
|
194
248
|
options.sampleRate,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
process.env.DD_TRACE_SAMPLE_RATE
|
|
249
|
+
process.env.DD_TRACE_SAMPLE_RATE,
|
|
250
|
+
ingestion.sampleRate
|
|
198
251
|
),
|
|
199
|
-
rateLimit: coalesce(
|
|
200
|
-
|
|
252
|
+
rateLimit: coalesce(options.rateLimit, process.env.DD_TRACE_RATE_LIMIT, ingestion.rateLimit),
|
|
253
|
+
rules: coalesce(options.samplingRules, safeJsonParse(process.env.DD_TRACE_SAMPLING_RULES), [])
|
|
254
|
+
}
|
|
201
255
|
|
|
202
256
|
const inAWSLambda = process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined
|
|
203
257
|
const defaultFlushInterval = inAWSLambda ? 0 : 2000
|
|
@@ -214,6 +268,8 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
214
268
|
this.flushInterval = coalesce(parseInt(options.flushInterval, 10), defaultFlushInterval)
|
|
215
269
|
this.flushMinSpans = DD_TRACE_PARTIAL_FLUSH_MIN_SPANS
|
|
216
270
|
this.sampleRate = coalesce(Math.min(Math.max(sampler.sampleRate, 0), 1), 1)
|
|
271
|
+
this.clientIpHeaderDisabled = isTrue(DD_TRACE_CLIENT_IP_HEADER_DISABLED)
|
|
272
|
+
this.clientIpHeader = DD_TRACE_CLIENT_IP_HEADER
|
|
217
273
|
this.logger = options.logger
|
|
218
274
|
this.plugins = !!coalesce(options.plugins, true)
|
|
219
275
|
this.service = DD_SERVICE
|
|
@@ -231,9 +287,9 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
231
287
|
traceparent: isTrue(DD_TRACE_TRACEPARENT_ENABLED),
|
|
232
288
|
runtimeId: isTrue(DD_TRACE_RUNTIME_ID_ENABLED),
|
|
233
289
|
exporter: DD_TRACE_EXPORTER,
|
|
234
|
-
enableGetRumData: isTrue(DD_TRACE_GET_RUM_DATA_ENABLED)
|
|
235
|
-
sampler
|
|
290
|
+
enableGetRumData: isTrue(DD_TRACE_GET_RUM_DATA_ENABLED)
|
|
236
291
|
}
|
|
292
|
+
this.sampler = sampler
|
|
237
293
|
this.reportHostname = isTrue(coalesce(options.reportHostname, process.env.DD_TRACE_REPORT_HOSTNAME, false))
|
|
238
294
|
this.scope = process.env.DD_TRACE_SCOPE
|
|
239
295
|
this.logLevel = coalesce(
|
|
@@ -251,6 +307,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
251
307
|
// Disabled for CI Visibility's agentless
|
|
252
308
|
this.telemetryEnabled = DD_TRACE_EXPORTER !== 'datadog' && isTrue(DD_TRACE_TELEMETRY_ENABLED)
|
|
253
309
|
this.protocolVersion = DD_TRACE_AGENT_PROTOCOL_VERSION
|
|
310
|
+
this.tagsHeaderMaxLength = parseInt(DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH)
|
|
254
311
|
this.appsec = {
|
|
255
312
|
enabled: isTrue(DD_APPSEC_ENABLED),
|
|
256
313
|
rules: DD_APPSEC_RULES,
|
|
@@ -259,8 +316,17 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
259
316
|
obfuscatorKeyRegex: DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
|
|
260
317
|
obfuscatorValueRegex: DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP
|
|
261
318
|
}
|
|
319
|
+
this.iast = {
|
|
320
|
+
enabled: isTrue(DD_IAST_ENABLED),
|
|
321
|
+
requestSampling: DD_IAST_REQUEST_SAMPLING,
|
|
322
|
+
maxConcurrentRequests: DD_IAST_MAX_CONCURRENT_REQUESTS,
|
|
323
|
+
maxContextOperations: DD_IAST_MAX_CONTEXT_OPERATIONS
|
|
324
|
+
}
|
|
262
325
|
this.isGitUploadEnabled = isTrue(DD_CIVISIBILITY_GIT_UPLOAD_ENABLED)
|
|
263
326
|
this.isIntelligentTestRunnerEnabled = isTrue(DD_CIVISIBILITY_ITR_ENABLED)
|
|
327
|
+
this.stats = {
|
|
328
|
+
enabled: isTrue(DD_TRACE_STATS_COMPUTATION_ENABLED)
|
|
329
|
+
}
|
|
264
330
|
|
|
265
331
|
tagger.add(this.tags, {
|
|
266
332
|
service: this.service,
|
|
@@ -5,8 +5,16 @@ module.exports = {
|
|
|
5
5
|
ANALYTICS_KEY: '_dd1.sr.eausr',
|
|
6
6
|
ORIGIN_KEY: '_dd.origin',
|
|
7
7
|
HOSTNAME_KEY: '_dd.hostname',
|
|
8
|
+
TOP_LEVEL_KEY: '_dd.top_level',
|
|
8
9
|
SAMPLING_RULE_DECISION: '_dd.rule_psr',
|
|
9
10
|
SAMPLING_LIMIT_DECISION: '_dd.limit_psr',
|
|
10
11
|
SAMPLING_AGENT_DECISION: '_dd.agent_psr',
|
|
11
|
-
|
|
12
|
+
SAMPLING_MECHANISM_DEFAULT: 0,
|
|
13
|
+
SAMPLING_MECHANISM_AGENT: 1,
|
|
14
|
+
SAMPLING_MECHANISM_RULE: 3,
|
|
15
|
+
SAMPLING_MECHANISM_MANUAL: 4,
|
|
16
|
+
SAMPLING_MECHANISM_APPSEC: 5,
|
|
17
|
+
SAMPLING_MECHANISM_SPAN: 8,
|
|
18
|
+
DATADOG_LAMBDA_EXTENSION_PATH: '/opt/extensions/datadog-agent',
|
|
19
|
+
DECISION_MAKER_KEY: '_dd.p.dm'
|
|
12
20
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { AgentEncoder } = require('./0.4')
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
MAX_NAME_LENGTH,
|
|
7
|
+
MAX_SERVICE_LENGTH,
|
|
8
|
+
MAX_RESOURCE_NAME_LENGTH,
|
|
9
|
+
MAX_TYPE_LENGTH,
|
|
10
|
+
DEFAULT_SPAN_NAME,
|
|
11
|
+
DEFAULT_SERVICE_NAME
|
|
12
|
+
} = require('./tags-processors')
|
|
13
|
+
|
|
14
|
+
function truncate (value, maxLength, suffix = '') {
|
|
15
|
+
if (!value) {
|
|
16
|
+
return value
|
|
17
|
+
}
|
|
18
|
+
if (value.length > maxLength) {
|
|
19
|
+
return `${value.slice(0, maxLength)}${suffix}`
|
|
20
|
+
}
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class SpanStatsEncoder extends AgentEncoder {
|
|
25
|
+
_encodeBool (bytes, value) {
|
|
26
|
+
this._encodeByte(bytes, value ? 0xc3 : 0xc2)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
makePayload () {
|
|
30
|
+
const traceSize = this._traceBytes.length
|
|
31
|
+
const buffer = Buffer.allocUnsafe(traceSize)
|
|
32
|
+
this._traceBytes.copy(buffer, 0, traceSize)
|
|
33
|
+
this._reset()
|
|
34
|
+
return buffer
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_encodeMapPrefix (bytes, length) {
|
|
38
|
+
const offset = bytes.length
|
|
39
|
+
|
|
40
|
+
bytes.reserve(1)
|
|
41
|
+
bytes.length += 1
|
|
42
|
+
|
|
43
|
+
bytes.buffer[offset] = 0x80 + length
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_encodeBuffer (bytes, buffer) {
|
|
47
|
+
const length = buffer.length
|
|
48
|
+
const offset = bytes.length
|
|
49
|
+
|
|
50
|
+
bytes.reserve(5)
|
|
51
|
+
bytes.length += 5
|
|
52
|
+
|
|
53
|
+
bytes.buffer[offset] = 0xc6
|
|
54
|
+
bytes.buffer[offset + 1] = length >> 24
|
|
55
|
+
bytes.buffer[offset + 2] = length >> 16
|
|
56
|
+
bytes.buffer[offset + 3] = length >> 8
|
|
57
|
+
bytes.buffer[offset + 4] = length
|
|
58
|
+
|
|
59
|
+
buffer.copy(bytes.buffer, offset + 5)
|
|
60
|
+
bytes.length += length
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_encodeStat (bytes, stat) {
|
|
64
|
+
this._encodeMapPrefix(bytes, 12)
|
|
65
|
+
|
|
66
|
+
this._encodeString(bytes, 'Service')
|
|
67
|
+
const service = stat.Service || DEFAULT_SERVICE_NAME
|
|
68
|
+
this._encodeString(bytes, truncate(service, MAX_SERVICE_LENGTH))
|
|
69
|
+
|
|
70
|
+
this._encodeString(bytes, 'Name')
|
|
71
|
+
const name = stat.Name || DEFAULT_SPAN_NAME
|
|
72
|
+
this._encodeString(bytes, truncate(name, MAX_NAME_LENGTH))
|
|
73
|
+
|
|
74
|
+
this._encodeString(bytes, 'Resource')
|
|
75
|
+
this._encodeString(bytes, truncate(stat.Resource, MAX_RESOURCE_NAME_LENGTH, '...'))
|
|
76
|
+
|
|
77
|
+
this._encodeString(bytes, 'HTTPStatusCode')
|
|
78
|
+
this._encodeInteger(bytes, stat.HTTPStatusCode)
|
|
79
|
+
|
|
80
|
+
this._encodeString(bytes, 'Type')
|
|
81
|
+
this._encodeString(bytes, truncate(stat.Type, MAX_TYPE_LENGTH))
|
|
82
|
+
|
|
83
|
+
this._encodeString(bytes, 'Hits')
|
|
84
|
+
this._encodeLong(bytes, stat.Hits)
|
|
85
|
+
|
|
86
|
+
this._encodeString(bytes, 'Errors')
|
|
87
|
+
this._encodeLong(bytes, stat.Errors)
|
|
88
|
+
|
|
89
|
+
this._encodeString(bytes, 'Duration')
|
|
90
|
+
this._encodeLong(bytes, stat.Duration)
|
|
91
|
+
|
|
92
|
+
this._encodeString(bytes, 'OkSummary')
|
|
93
|
+
this._encodeBuffer(bytes, stat.OkSummary)
|
|
94
|
+
|
|
95
|
+
this._encodeString(bytes, 'ErrorSummary')
|
|
96
|
+
this._encodeBuffer(bytes, stat.ErrorSummary)
|
|
97
|
+
|
|
98
|
+
this._encodeString(bytes, 'Synthetics')
|
|
99
|
+
this._encodeBool(bytes, stat.Synthetics)
|
|
100
|
+
|
|
101
|
+
this._encodeString(bytes, 'TopLevelHits')
|
|
102
|
+
this._encodeLong(bytes, stat.TopLevelHits)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_encodeBucket (bytes, bucket) {
|
|
106
|
+
this._encodeMapPrefix(bytes, 3)
|
|
107
|
+
|
|
108
|
+
this._encodeString(bytes, 'Start')
|
|
109
|
+
this._encodeLong(bytes, bucket.Start)
|
|
110
|
+
|
|
111
|
+
this._encodeString(bytes, 'Duration')
|
|
112
|
+
this._encodeLong(bytes, bucket.Duration)
|
|
113
|
+
|
|
114
|
+
this._encodeString(bytes, 'Stats')
|
|
115
|
+
this._encodeArrayPrefix(bytes, bucket.Stats)
|
|
116
|
+
for (const stat of bucket.Stats) {
|
|
117
|
+
this._encodeStat(bytes, stat)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_encode (bytes, stats) {
|
|
122
|
+
this._encodeMapPrefix(bytes, 8)
|
|
123
|
+
|
|
124
|
+
this._encodeString(bytes, 'Hostname')
|
|
125
|
+
this._encodeString(bytes, stats.Hostname)
|
|
126
|
+
|
|
127
|
+
this._encodeString(bytes, 'Env')
|
|
128
|
+
this._encodeString(bytes, stats.Env)
|
|
129
|
+
|
|
130
|
+
this._encodeString(bytes, 'Version')
|
|
131
|
+
this._encodeString(bytes, stats.Version)
|
|
132
|
+
|
|
133
|
+
this._encodeString(bytes, 'Stats')
|
|
134
|
+
this._encodeArrayPrefix(bytes, stats.Stats)
|
|
135
|
+
for (const bucket of stats.Stats) {
|
|
136
|
+
this._encodeBucket(bytes, bucket)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this._encodeString(bytes, 'Lang')
|
|
140
|
+
this._encodeString(bytes, stats.Lang)
|
|
141
|
+
|
|
142
|
+
this._encodeString(bytes, 'TracerVersion')
|
|
143
|
+
this._encodeString(bytes, stats.TracerVersion)
|
|
144
|
+
|
|
145
|
+
this._encodeString(bytes, 'RuntimeID')
|
|
146
|
+
this._encodeString(bytes, stats.RuntimeID)
|
|
147
|
+
|
|
148
|
+
this._encodeString(bytes, 'Sequence')
|
|
149
|
+
this._encodeLong(bytes, stats.Sequence)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
SpanStatsEncoder
|
|
155
|
+
}
|
|
@@ -7,9 +7,21 @@ const Writer = require('./writer')
|
|
|
7
7
|
class AgentExporter {
|
|
8
8
|
constructor (config, prioritySampler) {
|
|
9
9
|
this._config = config
|
|
10
|
-
const { url, hostname, port, lookup, protocolVersion } = config
|
|
10
|
+
const { url, hostname, port, lookup, protocolVersion, stats = {} } = config
|
|
11
11
|
this._url = url || new URL(`http://${hostname || 'localhost'}:${port}`)
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
const headers = {}
|
|
14
|
+
if (stats.enabled) {
|
|
15
|
+
headers['Datadog-Client-Computed-Stats'] = 'yes'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this._writer = new Writer({
|
|
19
|
+
url: this._url,
|
|
20
|
+
prioritySampler,
|
|
21
|
+
lookup,
|
|
22
|
+
protocolVersion,
|
|
23
|
+
headers
|
|
24
|
+
})
|
|
13
25
|
|
|
14
26
|
this._timer = undefined
|
|
15
27
|
process.once('beforeExit', () => this._writer.flush())
|
|
@@ -10,7 +10,7 @@ const BaseWriter = require('../common/writer')
|
|
|
10
10
|
const METRIC_PREFIX = 'datadog.tracer.node.exporter.agent'
|
|
11
11
|
|
|
12
12
|
class Writer extends BaseWriter {
|
|
13
|
-
constructor ({ prioritySampler, lookup, protocolVersion }) {
|
|
13
|
+
constructor ({ prioritySampler, lookup, protocolVersion, headers }) {
|
|
14
14
|
super(...arguments)
|
|
15
15
|
const AgentEncoder = getEncoder(protocolVersion)
|
|
16
16
|
|
|
@@ -18,12 +18,14 @@ class Writer extends BaseWriter {
|
|
|
18
18
|
this._lookup = lookup
|
|
19
19
|
this._protocolVersion = protocolVersion
|
|
20
20
|
this._encoder = new AgentEncoder(this)
|
|
21
|
+
this._headers = headers
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
_sendPayload (data, count, done) {
|
|
24
25
|
metrics.increment(`${METRIC_PREFIX}.requests`, true)
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
const { _headers, _lookup, _protocolVersion, _url } = this
|
|
28
|
+
makeRequest(_protocolVersion, data, count, _url, _headers, _lookup, true, (err, res, status) => {
|
|
27
29
|
if (status) {
|
|
28
30
|
metrics.increment(`${METRIC_PREFIX}.responses`, true)
|
|
29
31
|
metrics.increment(`${METRIC_PREFIX}.responses.by.status`, `status:${status}`, true)
|
|
@@ -73,11 +75,12 @@ function getEncoder (protocolVersion) {
|
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
function makeRequest (version, data, count, url, lookup, needsStartupLog, cb) {
|
|
78
|
+
function makeRequest (version, data, count, url, headers, lookup, needsStartupLog, cb) {
|
|
77
79
|
const options = {
|
|
78
80
|
path: `/v${version}/traces`,
|
|
79
81
|
method: 'PUT',
|
|
80
82
|
headers: {
|
|
83
|
+
...headers,
|
|
81
84
|
'Content-Type': 'application/msgpack',
|
|
82
85
|
'Datadog-Meta-Tracer-Version': tracerVersion,
|
|
83
86
|
'X-Datadog-Trace-Count': String(count)
|
|
@@ -84,7 +84,7 @@ function request (data, options, callback) {
|
|
|
84
84
|
req.setTimeout(timeout, req.abort)
|
|
85
85
|
|
|
86
86
|
if (isReadable) {
|
|
87
|
-
data.pipe(req)
|
|
87
|
+
data.pipe(req) // TODO: Validate whether this is actually retriable.
|
|
88
88
|
} else {
|
|
89
89
|
dataArray.forEach(buffer => req.write(buffer))
|
|
90
90
|
req.end()
|
|
@@ -93,7 +93,11 @@ function request (data, options, callback) {
|
|
|
93
93
|
storage.enterWith(store)
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
// TODO: Figure out why setTimeout is needed to avoid losing the async context
|
|
97
|
+
// in the retry request before socket.connect() is called.
|
|
98
|
+
// TODO: Test that this doesn't trace itself on retry when the diagnostics
|
|
99
|
+
// channel events are available in the agent exporter.
|
|
100
|
+
makeRequest(() => setTimeout(() => makeRequest(callback)))
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
function byteLength (data) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { URL } = require('url')
|
|
2
|
+
|
|
3
|
+
const { Writer } = require('./writer')
|
|
4
|
+
|
|
5
|
+
class SpanStatsExporter {
|
|
6
|
+
constructor (config) {
|
|
7
|
+
const { hostname = '127.0.0.1', port = 8126, tags, url } = config
|
|
8
|
+
this._url = url || new URL(`http://${hostname || 'localhost'}:${port}`)
|
|
9
|
+
this._writer = new Writer({ url: this._url, tags })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export (payload) {
|
|
13
|
+
this._writer.append(payload)
|
|
14
|
+
this._writer.flush()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
SpanStatsExporter
|
|
20
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
const { SpanStatsEncoder } = require('../../encode/span-stats')
|
|
3
|
+
|
|
4
|
+
const pkg = require('../../../../../package.json')
|
|
5
|
+
|
|
6
|
+
const BaseWriter = require('../common/writer')
|
|
7
|
+
const request = require('../common/request')
|
|
8
|
+
const log = require('../../log')
|
|
9
|
+
|
|
10
|
+
class Writer extends BaseWriter {
|
|
11
|
+
constructor ({ url }) {
|
|
12
|
+
super(...arguments)
|
|
13
|
+
this._url = url
|
|
14
|
+
this._encoder = new SpanStatsEncoder(this)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_sendPayload (data, _, done) {
|
|
18
|
+
makeRequest(data, this._url, (err, res) => {
|
|
19
|
+
if (err) {
|
|
20
|
+
log.error(err)
|
|
21
|
+
done()
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
log.debug(`Response from the intake: ${res}`)
|
|
25
|
+
done()
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeRequest (data, url, cb) {
|
|
31
|
+
const options = {
|
|
32
|
+
path: '/v0.6/stats',
|
|
33
|
+
method: 'PUT',
|
|
34
|
+
headers: {
|
|
35
|
+
'Datadog-Meta-Lang': 'javascript',
|
|
36
|
+
'Datadog-Meta-Tracer-Version': pkg.version,
|
|
37
|
+
'Content-Type': 'application/msgpack'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
options.protocol = url.protocol
|
|
42
|
+
options.hostname = url.hostname
|
|
43
|
+
options.port = url.port
|
|
44
|
+
|
|
45
|
+
log.debug(() => `Request to the intake: ${JSON.stringify(options)}`)
|
|
46
|
+
|
|
47
|
+
request(data, options, (err, res) => {
|
|
48
|
+
cb(err, res)
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
Writer
|
|
54
|
+
}
|
|
@@ -12,6 +12,7 @@ const SAMPLING_AGENT_DECISION = constants.SAMPLING_AGENT_DECISION
|
|
|
12
12
|
const MEASURED = tags.MEASURED
|
|
13
13
|
const ORIGIN_KEY = constants.ORIGIN_KEY
|
|
14
14
|
const HOSTNAME_KEY = constants.HOSTNAME_KEY
|
|
15
|
+
const TOP_LEVEL_KEY = constants.TOP_LEVEL_KEY
|
|
15
16
|
|
|
16
17
|
const map = {
|
|
17
18
|
'service.name': 'service',
|
|
@@ -110,6 +111,7 @@ function extractRootTags (trace, span) {
|
|
|
110
111
|
addTag({}, trace.metrics, SAMPLING_RULE_DECISION, context._trace[SAMPLING_RULE_DECISION])
|
|
111
112
|
addTag({}, trace.metrics, SAMPLING_LIMIT_DECISION, context._trace[SAMPLING_LIMIT_DECISION])
|
|
112
113
|
addTag({}, trace.metrics, SAMPLING_AGENT_DECISION, context._trace[SAMPLING_AGENT_DECISION])
|
|
114
|
+
addTag({}, trace.metrics, TOP_LEVEL_KEY, 1)
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
function extractChunkTags (trace, span) {
|
|
@@ -2,8 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
const semver = require('semver')
|
|
4
4
|
const logger = require('./log')
|
|
5
|
+
const { addHook } = require('import-in-the-middle')
|
|
6
|
+
const dc = require('diagnostics_channel')
|
|
5
7
|
|
|
6
8
|
if (semver.satisfies(process.versions.node, '^12.20.0 || >=14.13.1')) {
|
|
9
|
+
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
|
|
10
|
+
addHook((name, namespace) => {
|
|
11
|
+
if (moduleLoadStartChannel.hasSubscribers) {
|
|
12
|
+
moduleLoadStartChannel.publish({
|
|
13
|
+
filename: name,
|
|
14
|
+
module: namespace
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
})
|
|
7
18
|
module.exports = require('import-in-the-middle')
|
|
8
19
|
} else {
|
|
9
20
|
logger.warn('ESM is not fully supported by this version of Node.js, ' +
|