dd-trace 2.4.1 → 2.6.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/ci/init.js +6 -0
- package/ci/jest/env.js +16 -3
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/index.d.ts +17 -1
- package/package.json +5 -4
- package/packages/datadog-instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/amqplib.js +1 -1
- package/packages/datadog-instrumentations/src/cypress.js +8 -0
- package/packages/datadog-instrumentations/src/http/client.js +10 -10
- package/packages/datadog-instrumentations/src/jest.js +170 -0
- package/packages/datadog-plugin-aws-sdk/src/helpers.js +4 -4
- package/packages/datadog-plugin-aws-sdk/src/index.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +16 -16
- package/packages/datadog-plugin-cypress/src/index.js +10 -5
- package/packages/datadog-plugin-cypress/src/plugin.js +18 -17
- package/packages/datadog-plugin-elasticsearch/src/index.js +4 -2
- package/packages/datadog-plugin-fs/src/index.js +2 -0
- package/packages/datadog-plugin-http/src/client.js +4 -1
- package/packages/datadog-plugin-http/src/server.js +7 -10
- package/packages/datadog-plugin-jest/src/index.js +101 -3
- package/packages/datadog-plugin-jest/src/util.js +1 -29
- package/packages/datadog-plugin-mocha/src/index.js +14 -15
- package/packages/dd-trace/lib/version.js +1 -1
- package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +29 -12
- package/packages/dd-trace/src/appsec/index.js +7 -3
- package/packages/dd-trace/src/appsec/recommended.json +119 -210
- package/packages/dd-trace/src/appsec/reporter.js +29 -3
- package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +32 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +51 -0
- package/packages/dd-trace/src/config.js +33 -4
- package/packages/dd-trace/src/encode/0.4.js +0 -1
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +193 -0
- package/packages/dd-trace/src/encode/tags-processors.js +116 -0
- package/packages/dd-trace/src/exporter.js +3 -0
- package/packages/dd-trace/src/exporters/agent/index.js +1 -1
- package/packages/dd-trace/src/exporters/agent/writer.js +7 -32
- package/packages/dd-trace/src/exporters/{agent → common}/docker.js +0 -0
- package/packages/dd-trace/src/exporters/common/request.js +83 -0
- package/packages/dd-trace/src/exporters/common/writer.js +36 -0
- package/packages/dd-trace/src/exporters/{agent/scheduler.js → scheduler.js} +0 -0
- package/packages/dd-trace/src/format.js +9 -5
- package/packages/dd-trace/src/instrumenter.js +3 -0
- package/packages/dd-trace/src/pkg.js +11 -6
- package/packages/dd-trace/src/plugins/util/test.js +79 -1
- package/packages/dd-trace/src/plugins/util/web.js +11 -10
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/cpu.js +1 -1
- package/packages/dd-trace/src/proxy.js +2 -0
- package/packages/dd-trace/src/span_processor.js +4 -1
- package/packages/dd-trace/src/telemetry.js +187 -0
- package/scripts/install_plugin_modules.js +1 -0
- package/packages/datadog-plugin-jest/src/jest-environment.js +0 -272
- package/packages/datadog-plugin-jest/src/jest-jasmine2.js +0 -185
- package/packages/dd-trace/src/exporters/agent/request.js +0 -86
|
@@ -35,6 +35,8 @@ const RESPONSE_HEADERS_PASSLIST = [
|
|
|
35
35
|
'content-type'
|
|
36
36
|
]
|
|
37
37
|
|
|
38
|
+
const metricsQueue = new Map()
|
|
39
|
+
|
|
38
40
|
function resolveHTTPRequest (context) {
|
|
39
41
|
if (!context) return {}
|
|
40
42
|
|
|
@@ -82,6 +84,20 @@ function formatHeaderName (name) {
|
|
|
82
84
|
.toLowerCase()
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
function reportMetrics (metrics, store) {
|
|
88
|
+
const req = store && store.get('req')
|
|
89
|
+
const topSpan = web.root(req)
|
|
90
|
+
if (!topSpan) return false
|
|
91
|
+
|
|
92
|
+
if (metrics.duration) {
|
|
93
|
+
topSpan.setTag('_dd.appsec.waf.duration', metrics.duration)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (metrics.rulesVersion) {
|
|
97
|
+
topSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
85
101
|
function reportAttack (attackData, store) {
|
|
86
102
|
const req = store && store.get('req')
|
|
87
103
|
const topSpan = web.root(req)
|
|
@@ -129,9 +145,17 @@ function reportAttack (attackData, store) {
|
|
|
129
145
|
topSpan.addTags(newTags)
|
|
130
146
|
}
|
|
131
147
|
|
|
132
|
-
function
|
|
148
|
+
function finishRequest (req, context) {
|
|
133
149
|
const topSpan = web.root(req)
|
|
134
|
-
if (!topSpan
|
|
150
|
+
if (!topSpan) return false
|
|
151
|
+
|
|
152
|
+
if (metricsQueue.size) {
|
|
153
|
+
topSpan.addTags(Object.fromEntries(metricsQueue))
|
|
154
|
+
|
|
155
|
+
metricsQueue.clear()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!context || !topSpan.context()._tags['appsec.event']) return false
|
|
135
159
|
|
|
136
160
|
const resolvedResponse = resolveHTTPResponse(context)
|
|
137
161
|
|
|
@@ -149,11 +173,13 @@ function setRateLimit (rateLimit) {
|
|
|
149
173
|
}
|
|
150
174
|
|
|
151
175
|
module.exports = {
|
|
176
|
+
metricsQueue,
|
|
152
177
|
resolveHTTPRequest,
|
|
153
178
|
resolveHTTPResponse,
|
|
154
179
|
filterHeaders,
|
|
155
180
|
formatHeaderName,
|
|
181
|
+
reportMetrics,
|
|
156
182
|
reportAttack,
|
|
157
|
-
|
|
183
|
+
finishRequest,
|
|
158
184
|
setRateLimit
|
|
159
185
|
}
|
|
@@ -4,11 +4,11 @@ const callbacks = require('./callbacks')
|
|
|
4
4
|
|
|
5
5
|
const appliedCallbacks = new Map()
|
|
6
6
|
|
|
7
|
-
function applyRules (rules) {
|
|
7
|
+
function applyRules (rules, config) {
|
|
8
8
|
if (appliedCallbacks.has(rules)) return
|
|
9
9
|
|
|
10
10
|
// for now there is only WAF
|
|
11
|
-
const callback = new callbacks.DDWAF(rules)
|
|
11
|
+
const callback = new callbacks.DDWAF(rules, config)
|
|
12
12
|
|
|
13
13
|
appliedCallbacks.set(rules, callback)
|
|
14
14
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const URL = require('url').URL
|
|
4
|
+
const Writer = require('./writer')
|
|
5
|
+
const Scheduler = require('../../../exporters/scheduler')
|
|
6
|
+
|
|
7
|
+
class AgentlessCiVisibilityExporter {
|
|
8
|
+
constructor (config) {
|
|
9
|
+
const { flushInterval, tags, site, url } = config
|
|
10
|
+
this._url = url || new URL(`https://citestcycle-intake.${site}`)
|
|
11
|
+
this._writer = new Writer({ url: this._url, tags })
|
|
12
|
+
|
|
13
|
+
if (flushInterval > 0) {
|
|
14
|
+
this._scheduler = new Scheduler(() => this._writer.flush(), flushInterval)
|
|
15
|
+
}
|
|
16
|
+
this._scheduler && this._scheduler.start()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export (trace) {
|
|
20
|
+
this._writer.append(trace)
|
|
21
|
+
|
|
22
|
+
if (!this._scheduler) {
|
|
23
|
+
this._writer.flush()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
flush () {
|
|
28
|
+
this._writer.flush()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = AgentlessCiVisibilityExporter
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const request = require('../../../exporters/common/request')
|
|
3
|
+
const log = require('../../../log')
|
|
4
|
+
|
|
5
|
+
const { AgentlessCiVisibilityEncoder } = require('../../../encode/agentless-ci-visibility')
|
|
6
|
+
const BaseWriter = require('../../../exporters/common/writer')
|
|
7
|
+
|
|
8
|
+
class Writer extends BaseWriter {
|
|
9
|
+
constructor ({ url, tags }) {
|
|
10
|
+
super(...arguments)
|
|
11
|
+
const { 'runtime-id': runtimeId, env, service } = tags
|
|
12
|
+
this._url = url
|
|
13
|
+
this._encoder = new AgentlessCiVisibilityEncoder({ runtimeId, env, service })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_sendPayload (data, _, done) {
|
|
17
|
+
makeRequest(data, this._url, (err, res) => {
|
|
18
|
+
if (err) {
|
|
19
|
+
log.error(err)
|
|
20
|
+
done()
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
log.debug(`Response from the intake: ${res}`)
|
|
24
|
+
done()
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeRequest (data, url, cb) {
|
|
30
|
+
const options = {
|
|
31
|
+
path: '/api/v2/citestcycle',
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/msgpack',
|
|
35
|
+
'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY
|
|
36
|
+
},
|
|
37
|
+
timeout: 15000
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
options.protocol = url.protocol
|
|
41
|
+
options.hostname = url.hostname
|
|
42
|
+
options.port = url.port
|
|
43
|
+
|
|
44
|
+
log.debug(() => `Request to the intake: ${JSON.stringify(options)}`)
|
|
45
|
+
|
|
46
|
+
request(data, options, false, (err, res) => {
|
|
47
|
+
cb(err, res)
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = Writer
|
|
@@ -66,6 +66,7 @@ class Config {
|
|
|
66
66
|
process.env.DD_TRACE_URL,
|
|
67
67
|
null
|
|
68
68
|
)
|
|
69
|
+
const DD_CIVISIBILITY_AGENTLESS_URL = process.env.DD_CIVISIBILITY_AGENTLESS_URL
|
|
69
70
|
const DD_SERVICE = options.service ||
|
|
70
71
|
process.env.DD_SERVICE ||
|
|
71
72
|
process.env.DD_SERVICE_NAME ||
|
|
@@ -90,6 +91,10 @@ class Config {
|
|
|
90
91
|
process.env.DD_TRACE_STARTUP_LOGS,
|
|
91
92
|
false
|
|
92
93
|
)
|
|
94
|
+
const DD_TRACE_TELEMETRY_ENABLED = coalesce(
|
|
95
|
+
process.env.DD_TRACE_TELEMETRY_ENABLED,
|
|
96
|
+
true
|
|
97
|
+
)
|
|
93
98
|
const DD_TRACE_DEBUG = coalesce(
|
|
94
99
|
process.env.DD_TRACE_DEBUG,
|
|
95
100
|
false
|
|
@@ -145,10 +150,29 @@ class Config {
|
|
|
145
150
|
path.join(__dirname, 'appsec', 'recommended.json')
|
|
146
151
|
)
|
|
147
152
|
const DD_APPSEC_TRACE_RATE_LIMIT = coalesce(
|
|
148
|
-
appsec.rateLimit,
|
|
149
|
-
process.env.DD_APPSEC_TRACE_RATE_LIMIT,
|
|
153
|
+
parseInt(appsec.rateLimit),
|
|
154
|
+
parseInt(process.env.DD_APPSEC_TRACE_RATE_LIMIT),
|
|
150
155
|
100
|
|
151
156
|
)
|
|
157
|
+
const DD_APPSEC_WAF_TIMEOUT = coalesce(
|
|
158
|
+
parseInt(appsec.wafTimeout),
|
|
159
|
+
parseInt(process.env.DD_APPSEC_WAF_TIMEOUT),
|
|
160
|
+
5e3 // µs
|
|
161
|
+
)
|
|
162
|
+
const DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP = coalesce(
|
|
163
|
+
appsec.obfuscatorKeyRegex,
|
|
164
|
+
process.env.DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
|
|
165
|
+
`(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|se\
|
|
166
|
+
cret)|sign(?:ed|ature)|bearer|authorization`
|
|
167
|
+
)
|
|
168
|
+
const DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP = coalesce(
|
|
169
|
+
appsec.obfuscatorValueRegex,
|
|
170
|
+
process.env.DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
|
|
171
|
+
`(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|to\
|
|
172
|
+
ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:\\s*=[^;]|"\\s*:\\s*"[^"]+")|bearer\
|
|
173
|
+
\\s+[a-z0-9\\._\\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(?:\\.[\\w.+\\/=-]+)?\
|
|
174
|
+
|[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}`
|
|
175
|
+
)
|
|
152
176
|
|
|
153
177
|
const sampler = (options.experimental && options.experimental.sampler) || {}
|
|
154
178
|
const ingestion = options.ingestion || {}
|
|
@@ -171,7 +195,8 @@ class Config {
|
|
|
171
195
|
this.debug = isTrue(DD_TRACE_DEBUG)
|
|
172
196
|
this.logInjection = isTrue(DD_LOGS_INJECTION)
|
|
173
197
|
this.env = DD_ENV
|
|
174
|
-
this.url =
|
|
198
|
+
this.url = DD_CIVISIBILITY_AGENTLESS_URL ? new URL(DD_CIVISIBILITY_AGENTLESS_URL)
|
|
199
|
+
: getAgentUrl(DD_TRACE_AGENT_URL, options)
|
|
175
200
|
this.site = coalesce(options.site, process.env.DD_SITE, 'datadoghq.com')
|
|
176
201
|
this.hostname = DD_AGENT_HOST || (this.url && this.url.hostname)
|
|
177
202
|
this.port = String(DD_TRACE_AGENT_PORT || (this.url && this.url.port))
|
|
@@ -212,11 +237,15 @@ class Config {
|
|
|
212
237
|
}
|
|
213
238
|
this.lookup = options.lookup
|
|
214
239
|
this.startupLogs = isTrue(DD_TRACE_STARTUP_LOGS)
|
|
240
|
+
this.telemetryEnabled = isTrue(DD_TRACE_TELEMETRY_ENABLED)
|
|
215
241
|
this.protocolVersion = DD_TRACE_AGENT_PROTOCOL_VERSION
|
|
216
242
|
this.appsec = {
|
|
217
243
|
enabled: isTrue(DD_APPSEC_ENABLED),
|
|
218
244
|
rules: DD_APPSEC_RULES,
|
|
219
|
-
rateLimit: DD_APPSEC_TRACE_RATE_LIMIT
|
|
245
|
+
rateLimit: DD_APPSEC_TRACE_RATE_LIMIT,
|
|
246
|
+
wafTimeout: DD_APPSEC_WAF_TIMEOUT,
|
|
247
|
+
obfuscatorKeyRegex: DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
|
|
248
|
+
obfuscatorValueRegex: DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP
|
|
220
249
|
}
|
|
221
250
|
|
|
222
251
|
tagger.add(this.tags, {
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const { truncateSpan, normalizeSpan } = require('./tags-processors')
|
|
3
|
+
const Chunk = require('./chunk')
|
|
4
|
+
const { AgentEncoder } = require('./0.4')
|
|
5
|
+
|
|
6
|
+
const ENCODING_VERSION = 1
|
|
7
|
+
|
|
8
|
+
function formatSpan (span) {
|
|
9
|
+
return {
|
|
10
|
+
type: span.type === 'test' ? 'test' : 'span',
|
|
11
|
+
version: ENCODING_VERSION,
|
|
12
|
+
content: normalizeSpan(truncateSpan(span))
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
17
|
+
constructor ({ runtimeId, service, env }) {
|
|
18
|
+
super(...arguments)
|
|
19
|
+
this._events = []
|
|
20
|
+
this.runtimeId = runtimeId
|
|
21
|
+
this.service = service
|
|
22
|
+
this.env = env
|
|
23
|
+
this._traceBytes = new Chunk()
|
|
24
|
+
this._stringBytes = new Chunk()
|
|
25
|
+
this._stringCount = 0
|
|
26
|
+
this._stringMap = {}
|
|
27
|
+
|
|
28
|
+
// Used to keep track of the number of encoded events to update the
|
|
29
|
+
// length of `payload.events` when calling `makePayload`
|
|
30
|
+
this._eventCount = 0
|
|
31
|
+
|
|
32
|
+
this.reset()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_encodeEventContent (bytes, content) {
|
|
36
|
+
this._encodeMapPrefix(bytes, content)
|
|
37
|
+
if (content.type) {
|
|
38
|
+
this._encodeString(bytes, 'type')
|
|
39
|
+
this._encodeString(bytes, content.type)
|
|
40
|
+
}
|
|
41
|
+
this._encodeString(bytes, 'trace_id')
|
|
42
|
+
this._encodeId(bytes, content.trace_id)
|
|
43
|
+
this._encodeString(bytes, 'span_id')
|
|
44
|
+
this._encodeId(bytes, content.span_id)
|
|
45
|
+
this._encodeString(bytes, 'parent_id')
|
|
46
|
+
this._encodeId(bytes, content.parent_id)
|
|
47
|
+
this._encodeString(bytes, 'name')
|
|
48
|
+
this._encodeString(bytes, content.name)
|
|
49
|
+
this._encodeString(bytes, 'resource')
|
|
50
|
+
this._encodeString(bytes, content.resource)
|
|
51
|
+
this._encodeString(bytes, 'service')
|
|
52
|
+
this._encodeString(bytes, content.service)
|
|
53
|
+
this._encodeString(bytes, 'error')
|
|
54
|
+
this._encodeNumber(bytes, content.error)
|
|
55
|
+
this._encodeString(bytes, 'start')
|
|
56
|
+
this._encodeNumber(bytes, content.start)
|
|
57
|
+
this._encodeString(bytes, 'duration')
|
|
58
|
+
this._encodeNumber(bytes, content.duration)
|
|
59
|
+
this._encodeString(bytes, 'meta')
|
|
60
|
+
this._encodeMap(bytes, content.meta)
|
|
61
|
+
this._encodeString(bytes, 'metrics')
|
|
62
|
+
this._encodeMap(bytes, content.metrics)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_encodeEvent (bytes, event) {
|
|
66
|
+
this._encodeMapPrefix(bytes, event)
|
|
67
|
+
this._encodeString(bytes, 'type')
|
|
68
|
+
this._encodeString(bytes, event.type)
|
|
69
|
+
|
|
70
|
+
this._encodeString(bytes, 'version')
|
|
71
|
+
this._encodeNumber(bytes, event.version)
|
|
72
|
+
|
|
73
|
+
this._encodeString(bytes, 'content')
|
|
74
|
+
this._encodeEventContent(bytes, event.content)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_encodeNumber (bytes, value) {
|
|
78
|
+
if (Math.floor(value) !== value) { // float 64
|
|
79
|
+
return this._encodeFloat(bytes, value)
|
|
80
|
+
}
|
|
81
|
+
return this._encodeLong(bytes, value)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
_encodeLong (bytes, value) {
|
|
85
|
+
const isPositive = value >= 0
|
|
86
|
+
|
|
87
|
+
const hi = isPositive ? (value / Math.pow(2, 32)) >> 0 : Math.floor(value / Math.pow(2, 32))
|
|
88
|
+
const lo = value >>> 0
|
|
89
|
+
const flag = isPositive ? 0xcf : 0xd3
|
|
90
|
+
|
|
91
|
+
const buffer = bytes.buffer
|
|
92
|
+
const offset = bytes.length
|
|
93
|
+
|
|
94
|
+
// int 64
|
|
95
|
+
bytes.reserve(9)
|
|
96
|
+
bytes.length += 9
|
|
97
|
+
|
|
98
|
+
buffer[offset] = flag
|
|
99
|
+
buffer[offset + 1] = hi >> 24
|
|
100
|
+
buffer[offset + 2] = hi >> 16
|
|
101
|
+
buffer[offset + 3] = hi >> 8
|
|
102
|
+
buffer[offset + 4] = hi
|
|
103
|
+
buffer[offset + 5] = lo >> 24
|
|
104
|
+
buffer[offset + 6] = lo >> 16
|
|
105
|
+
buffer[offset + 7] = lo >> 8
|
|
106
|
+
buffer[offset + 8] = lo
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_encodeMapPrefix (bytes, map) {
|
|
110
|
+
const keys = Object.keys(map)
|
|
111
|
+
const buffer = bytes.buffer
|
|
112
|
+
const offset = bytes.length
|
|
113
|
+
|
|
114
|
+
bytes.reserve(5)
|
|
115
|
+
bytes.length += 5
|
|
116
|
+
buffer[offset] = 0xdf
|
|
117
|
+
buffer[offset + 1] = keys.length >> 24
|
|
118
|
+
buffer[offset + 2] = keys.length >> 16
|
|
119
|
+
buffer[offset + 3] = keys.length >> 8
|
|
120
|
+
buffer[offset + 4] = keys.length
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_encode (bytes, trace) {
|
|
124
|
+
this._eventCount += trace.length
|
|
125
|
+
const events = trace.map(formatSpan)
|
|
126
|
+
|
|
127
|
+
for (const event of events) {
|
|
128
|
+
this._encodeEvent(bytes, event)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
makePayload () {
|
|
133
|
+
const bytes = this._traceBytes
|
|
134
|
+
const eventsOffset = this._eventsOffset
|
|
135
|
+
const eventsCount = this._eventCount
|
|
136
|
+
|
|
137
|
+
bytes.buffer[eventsOffset] = 0xdd
|
|
138
|
+
bytes.buffer[eventsOffset + 1] = eventsCount >> 24
|
|
139
|
+
bytes.buffer[eventsOffset + 2] = eventsCount >> 16
|
|
140
|
+
bytes.buffer[eventsOffset + 3] = eventsCount >> 8
|
|
141
|
+
bytes.buffer[eventsOffset + 4] = eventsCount
|
|
142
|
+
|
|
143
|
+
const traceSize = bytes.length
|
|
144
|
+
const buffer = Buffer.allocUnsafe(traceSize)
|
|
145
|
+
|
|
146
|
+
bytes.buffer.copy(buffer, 0, 0, bytes.length)
|
|
147
|
+
|
|
148
|
+
this.reset()
|
|
149
|
+
|
|
150
|
+
return buffer
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
_encodePayloadStart (bytes) {
|
|
154
|
+
// encodes the payload up to `events`. `events` will be encoded via _encode
|
|
155
|
+
const payload = {
|
|
156
|
+
version: ENCODING_VERSION,
|
|
157
|
+
metadata: {
|
|
158
|
+
'*': {
|
|
159
|
+
'language': 'javascript'
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
events: []
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (this.env) {
|
|
166
|
+
payload.metadata['*'].env = this.env
|
|
167
|
+
}
|
|
168
|
+
if (this.runtimeId) {
|
|
169
|
+
payload.metadata['*']['runtime-id'] = this.runtimeId
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this._encodeMapPrefix(bytes, payload)
|
|
173
|
+
this._encodeString(bytes, 'version')
|
|
174
|
+
this._encodeNumber(bytes, payload.version)
|
|
175
|
+
this._encodeString(bytes, 'metadata')
|
|
176
|
+
this._encodeMapPrefix(bytes, payload.metadata)
|
|
177
|
+
this._encodeString(bytes, '*')
|
|
178
|
+
this._encodeMap(bytes, payload.metadata['*'])
|
|
179
|
+
this._encodeString(bytes, 'events')
|
|
180
|
+
// Get offset of the events list to update the length of the array when calling `makePayload`
|
|
181
|
+
this._eventsOffset = bytes.length
|
|
182
|
+
bytes.reserve(5)
|
|
183
|
+
bytes.length += 5
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
reset () {
|
|
187
|
+
this._reset()
|
|
188
|
+
this._eventCount = 0
|
|
189
|
+
this._encodePayloadStart(this._traceBytes)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = { AgentlessCiVisibilityEncoder }
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// From agent truncators: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/agent/truncator.go
|
|
2
|
+
|
|
3
|
+
// Values from: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/truncate.go#L22-L27
|
|
4
|
+
// MAX_RESOURCE_NAME_LENGTH the maximum length a span resource can have
|
|
5
|
+
const MAX_RESOURCE_NAME_LENGTH = 5000
|
|
6
|
+
// MAX_META_KEY_LENGTH the maximum length of metadata key
|
|
7
|
+
const MAX_META_KEY_LENGTH = 200
|
|
8
|
+
// MAX_META_VALUE_LENGTH the maximum length of metadata value
|
|
9
|
+
const MAX_META_VALUE_LENGTH = 25000
|
|
10
|
+
// MAX_METRIC_KEY_LENGTH the maximum length of a metric name key
|
|
11
|
+
const MAX_METRIC_KEY_LENGTH = MAX_META_KEY_LENGTH
|
|
12
|
+
// MAX_METRIC_VALUE_LENGTH the maximum length of a metric name value
|
|
13
|
+
const MAX_METRIC_VALUE_LENGTH = MAX_META_VALUE_LENGTH
|
|
14
|
+
|
|
15
|
+
// From agent normalizer:
|
|
16
|
+
// https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize.go
|
|
17
|
+
// DEFAULT_SPAN_NAME is the default name we assign a span if it's missing and we have no reasonable fallback
|
|
18
|
+
const DEFAULT_SPAN_NAME = 'unnamed_operation'
|
|
19
|
+
// DEFAULT_SERVICE_NAME is the default name we assign a service if it's missing and we have no reasonable fallback
|
|
20
|
+
const DEFAULT_SERVICE_NAME = 'unnamed-service'
|
|
21
|
+
// MAX_NAME_LENGTH the maximum length a name can have
|
|
22
|
+
const MAX_NAME_LENGTH = 100
|
|
23
|
+
// MAX_SERVICE_LENGTH the maximum length a service can have
|
|
24
|
+
const MAX_SERVICE_LENGTH = 100
|
|
25
|
+
// MAX_TYPE_LENGTH the maximum length a span type can have
|
|
26
|
+
const MAX_TYPE_LENGTH = 100
|
|
27
|
+
|
|
28
|
+
const fromEntries = Object.fromEntries || (entries =>
|
|
29
|
+
entries.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {}))
|
|
30
|
+
|
|
31
|
+
function truncateToLength (value, maxLength) {
|
|
32
|
+
if (!value) {
|
|
33
|
+
return value
|
|
34
|
+
}
|
|
35
|
+
if (value.length > maxLength) {
|
|
36
|
+
return `${value.slice(0, maxLength)}...`
|
|
37
|
+
}
|
|
38
|
+
return value
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function truncateSpan (span) {
|
|
42
|
+
return fromEntries(Object.entries(span).map(([key, value]) => {
|
|
43
|
+
switch (key) {
|
|
44
|
+
case 'resource':
|
|
45
|
+
return ['resource', truncateToLength(value, MAX_RESOURCE_NAME_LENGTH)]
|
|
46
|
+
case 'meta':
|
|
47
|
+
return ['meta', fromEntries(Object.entries(value).map(([metaKey, metaValue]) =>
|
|
48
|
+
[truncateToLength(metaKey, MAX_META_KEY_LENGTH), truncateToLength(metaValue, MAX_META_VALUE_LENGTH)]
|
|
49
|
+
))]
|
|
50
|
+
case 'metrics':
|
|
51
|
+
return ['metrics', fromEntries(Object.entries(value).map(([metricsKey, metricsValue]) =>
|
|
52
|
+
[truncateToLength(metricsKey, MAX_METRIC_KEY_LENGTH), truncateToLength(metricsValue, MAX_METRIC_VALUE_LENGTH)]
|
|
53
|
+
))]
|
|
54
|
+
default:
|
|
55
|
+
return [key, value]
|
|
56
|
+
}
|
|
57
|
+
}))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeSpan (span) {
|
|
61
|
+
const normalizedSpan = fromEntries(Object.entries(span).map(([key, value]) => {
|
|
62
|
+
switch (key) {
|
|
63
|
+
case 'service':
|
|
64
|
+
if (!value) {
|
|
65
|
+
return [key, DEFAULT_SERVICE_NAME]
|
|
66
|
+
}
|
|
67
|
+
if (value.length > MAX_SERVICE_LENGTH) {
|
|
68
|
+
return [key, value.slice(0, MAX_SERVICE_LENGTH)]
|
|
69
|
+
}
|
|
70
|
+
break
|
|
71
|
+
case 'name':
|
|
72
|
+
if (!value) {
|
|
73
|
+
return [key, DEFAULT_SPAN_NAME]
|
|
74
|
+
}
|
|
75
|
+
if (value.length > MAX_NAME_LENGTH) {
|
|
76
|
+
return [key, value.slice(0, MAX_NAME_LENGTH)]
|
|
77
|
+
}
|
|
78
|
+
break
|
|
79
|
+
case 'resource':
|
|
80
|
+
if (!value) {
|
|
81
|
+
return [key, span.name || DEFAULT_SPAN_NAME]
|
|
82
|
+
}
|
|
83
|
+
break
|
|
84
|
+
case 'type':
|
|
85
|
+
if (!value) {
|
|
86
|
+
return [key, value]
|
|
87
|
+
}
|
|
88
|
+
if (value.length > MAX_TYPE_LENGTH) {
|
|
89
|
+
return [key, value.slice(0, MAX_TYPE_LENGTH)]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return [key, value]
|
|
93
|
+
}))
|
|
94
|
+
if (!normalizedSpan.service) {
|
|
95
|
+
normalizedSpan.service = DEFAULT_SERVICE_NAME
|
|
96
|
+
}
|
|
97
|
+
if (!normalizedSpan.name) {
|
|
98
|
+
normalizedSpan.name = DEFAULT_SPAN_NAME
|
|
99
|
+
}
|
|
100
|
+
return normalizedSpan
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
truncateSpan,
|
|
105
|
+
normalizeSpan,
|
|
106
|
+
MAX_META_KEY_LENGTH,
|
|
107
|
+
MAX_META_VALUE_LENGTH,
|
|
108
|
+
MAX_METRIC_KEY_LENGTH,
|
|
109
|
+
MAX_METRIC_VALUE_LENGTH,
|
|
110
|
+
MAX_NAME_LENGTH,
|
|
111
|
+
MAX_SERVICE_LENGTH,
|
|
112
|
+
MAX_TYPE_LENGTH,
|
|
113
|
+
MAX_RESOURCE_NAME_LENGTH,
|
|
114
|
+
DEFAULT_SPAN_NAME,
|
|
115
|
+
DEFAULT_SERVICE_NAME
|
|
116
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const AgentExporter = require('./exporters/agent')
|
|
4
4
|
const LogExporter = require('./exporters/log')
|
|
5
|
+
const AgentlessCiVisibilityExporter = require('./ci-visibility/exporters/agentless')
|
|
5
6
|
const exporters = require('../../../ext/exporters')
|
|
6
7
|
const fs = require('fs')
|
|
7
8
|
const constants = require('./constants')
|
|
@@ -15,6 +16,8 @@ module.exports = name => {
|
|
|
15
16
|
return LogExporter
|
|
16
17
|
case exporters.AGENT:
|
|
17
18
|
return AgentExporter
|
|
19
|
+
case exporters.DATADOG:
|
|
20
|
+
return AgentlessCiVisibilityExporter
|
|
18
21
|
default:
|
|
19
22
|
return inAWSLambda && !usingLambdaExtension ? LogExporter : AgentExporter
|
|
20
23
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const URL = require('url').URL
|
|
4
4
|
const log = require('../../log')
|
|
5
5
|
const Writer = require('./writer')
|
|
6
|
-
const Scheduler = require('
|
|
6
|
+
const Scheduler = require('../scheduler')
|
|
7
7
|
|
|
8
8
|
class AgentExporter {
|
|
9
9
|
constructor ({ url, hostname, port, flushInterval, lookup, protocolVersion }, prioritySampler) {
|
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const request = require('
|
|
3
|
+
const request = require('../common/request')
|
|
4
4
|
const { startupLog } = require('../../startup-log')
|
|
5
5
|
const metrics = require('../../metrics')
|
|
6
6
|
const log = require('../../log')
|
|
7
7
|
const tracerVersion = require('../../../lib/version')
|
|
8
|
+
const BaseWriter = require('../common/writer')
|
|
8
9
|
|
|
9
10
|
const METRIC_PREFIX = 'datadog.tracer.node.exporter.agent'
|
|
10
11
|
|
|
11
|
-
class Writer {
|
|
12
|
-
constructor ({
|
|
12
|
+
class Writer extends BaseWriter {
|
|
13
|
+
constructor ({ prioritySampler, lookup, protocolVersion }) {
|
|
14
|
+
super(...arguments)
|
|
13
15
|
const AgentEncoder = getEncoder(protocolVersion)
|
|
14
16
|
|
|
15
|
-
this._url = url
|
|
16
17
|
this._prioritySampler = prioritySampler
|
|
17
18
|
this._lookup = lookup
|
|
18
19
|
this._protocolVersion = protocolVersion
|
|
19
|
-
this.
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
append (spans) {
|
|
23
|
-
log.debug(() => `Encoding trace: ${JSON.stringify(spans)}`)
|
|
24
|
-
|
|
25
|
-
this._encode(spans)
|
|
20
|
+
this._encoder = new AgentEncoder(this)
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
_sendPayload (data, count, done) {
|
|
@@ -62,26 +57,6 @@ class Writer {
|
|
|
62
57
|
done()
|
|
63
58
|
})
|
|
64
59
|
}
|
|
65
|
-
|
|
66
|
-
setUrl (url) {
|
|
67
|
-
this._url = url
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
_encode (trace) {
|
|
71
|
-
this._encoderForVersion.encode(trace)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
flush (done = () => {}) {
|
|
75
|
-
const count = this._encoderForVersion.count()
|
|
76
|
-
|
|
77
|
-
if (count > 0) {
|
|
78
|
-
const payload = this._encoderForVersion.makePayload()
|
|
79
|
-
|
|
80
|
-
this._sendPayload(payload, count, done)
|
|
81
|
-
} else {
|
|
82
|
-
done()
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
60
|
}
|
|
86
61
|
|
|
87
62
|
function setHeader (headers, key, value) {
|
|
@@ -124,7 +99,7 @@ function makeRequest (version, data, count, url, lookup, needsStartupLog, cb) {
|
|
|
124
99
|
|
|
125
100
|
log.debug(() => `Request to the agent: ${JSON.stringify(options)}`)
|
|
126
101
|
|
|
127
|
-
request(
|
|
102
|
+
request(data, options, true, (err, res, status) => {
|
|
128
103
|
if (needsStartupLog) {
|
|
129
104
|
// Note that logging will only happen once, regardless of how many times this is called.
|
|
130
105
|
startupLog({
|
|
File without changes
|