dd-trace 2.20.1 → 2.22.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 +2 -0
- package/index.d.ts +11 -0
- package/package.json +4 -2
- package/packages/datadog-instrumentations/src/body-parser.js +26 -0
- package/packages/datadog-instrumentations/src/child-process.js +30 -0
- package/packages/datadog-instrumentations/src/cucumber.js +1 -1
- package/packages/datadog-instrumentations/src/hapi.js +3 -3
- package/packages/datadog-instrumentations/src/helpers/hooks.js +5 -0
- package/packages/datadog-instrumentations/src/jest.js +110 -40
- package/packages/datadog-instrumentations/src/mocha.js +87 -6
- package/packages/datadog-instrumentations/src/pg.js +1 -2
- package/packages/datadog-instrumentations/src/qs.js +24 -0
- package/packages/datadog-instrumentations/src/restify.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +13 -32
- package/packages/datadog-plugin-cypress/src/plugin.js +2 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +0 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +0 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +0 -1
- package/packages/datadog-plugin-http/src/client.js +14 -4
- package/packages/datadog-plugin-http/src/server.js +3 -0
- package/packages/datadog-plugin-http2/src/client.js +20 -1
- package/packages/datadog-plugin-http2/src/server.js +3 -0
- package/packages/datadog-plugin-jest/src/index.js +11 -129
- package/packages/datadog-plugin-mocha/src/index.js +33 -46
- package/packages/datadog-plugin-net/src/index.js +4 -0
- package/packages/datadog-plugin-next/src/index.js +3 -0
- package/packages/datadog-plugin-oracledb/src/index.js +0 -1
- package/packages/datadog-plugin-pg/src/index.js +5 -2
- package/packages/datadog-plugin-router/src/index.js +2 -0
- package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +3 -5
- package/packages/dd-trace/src/appsec/gateway/engine/index.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +11 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +19 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +13 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/index.js +6 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +8 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/filter.js +16 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +18 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +125 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/origin-types.js +4 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +38 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +66 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +52 -6
- package/packages/dd-trace/src/appsec/index.js +8 -0
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +7 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +34 -0
- package/packages/dd-trace/src/appsec/remote_config/manager.js +264 -0
- package/packages/dd-trace/src/{exporters → appsec/remote_config}/scheduler.js +9 -9
- package/packages/dd-trace/src/appsec/rule_manager.js +3 -0
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +5 -7
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +3 -4
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +3 -4
- package/packages/dd-trace/src/config.js +41 -20
- package/packages/dd-trace/src/constants.js +6 -1
- package/packages/dd-trace/src/exporters/common/request.js +7 -1
- package/packages/dd-trace/src/format.js +12 -10
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -5
- package/packages/dd-trace/src/opentracing/span_context.js +9 -0
- package/packages/dd-trace/src/plugin_manager.js +6 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +132 -0
- package/packages/dd-trace/src/plugins/database.js +46 -0
- package/packages/dd-trace/src/plugins/tracing.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +61 -8
- package/packages/dd-trace/src/plugins/util/web.js +9 -8
- package/packages/dd-trace/src/proxy.js +4 -3
- package/packages/dd-trace/src/span_processor.js +1 -0
- package/packages/dd-trace/src/tracer.js +4 -3
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const uuid = require('crypto-randomuuid')
|
|
4
|
+
const { EventEmitter } = require('events')
|
|
5
|
+
const Scheduler = require('./scheduler')
|
|
6
|
+
const tracerVersion = require('../../../../../package.json').version
|
|
7
|
+
const request = require('../../exporters/common/request')
|
|
8
|
+
const log = require('../../log')
|
|
9
|
+
|
|
10
|
+
const clientId = uuid()
|
|
11
|
+
|
|
12
|
+
const POLL_INTERVAL = 5e3
|
|
13
|
+
const DEFAULT_CAPABILITY = Buffer.alloc(1).toString('base64') // 0x00
|
|
14
|
+
|
|
15
|
+
// There MUST NOT exist separate instances of RC clients in a tracer making separate ClientGetConfigsRequest
|
|
16
|
+
// with their own separated Client.ClientState.
|
|
17
|
+
class RemoteConfigManager extends EventEmitter {
|
|
18
|
+
constructor (config) {
|
|
19
|
+
super()
|
|
20
|
+
|
|
21
|
+
this.scheduler = new Scheduler((cb) => this.poll(cb), POLL_INTERVAL)
|
|
22
|
+
|
|
23
|
+
this.requestOptions = {
|
|
24
|
+
url: config.url,
|
|
25
|
+
hostname: config.hostname,
|
|
26
|
+
port: config.port,
|
|
27
|
+
method: 'POST',
|
|
28
|
+
path: '/v0.7/config'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.state = {
|
|
32
|
+
client: {
|
|
33
|
+
state: { // updated by `parseConfig()`
|
|
34
|
+
root_version: 1,
|
|
35
|
+
targets_version: 0,
|
|
36
|
+
config_states: [],
|
|
37
|
+
has_error: false,
|
|
38
|
+
error: '',
|
|
39
|
+
backend_client_state: ''
|
|
40
|
+
},
|
|
41
|
+
id: clientId,
|
|
42
|
+
products: [], // updated by `updateProducts()`
|
|
43
|
+
is_tracer: true,
|
|
44
|
+
client_tracer: {
|
|
45
|
+
runtime_id: config.tags['runtime-id'],
|
|
46
|
+
language: 'node',
|
|
47
|
+
tracer_version: tracerVersion,
|
|
48
|
+
service: config.service,
|
|
49
|
+
env: config.env,
|
|
50
|
+
app_version: config.version
|
|
51
|
+
},
|
|
52
|
+
capabilities: DEFAULT_CAPABILITY // updated by `updateCapabilities()`
|
|
53
|
+
},
|
|
54
|
+
cached_target_files: [] // updated by `parseConfig()`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.appliedConfigs = new Map()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
updateCapabilities (mask, value) {
|
|
61
|
+
const hex = Buffer.from(this.state.client.capabilities, 'base64').toString('hex')
|
|
62
|
+
|
|
63
|
+
let num = BigInt(`0x${hex}`)
|
|
64
|
+
|
|
65
|
+
if (value) {
|
|
66
|
+
num |= mask
|
|
67
|
+
} else {
|
|
68
|
+
num &= ~mask
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let str = num.toString(16)
|
|
72
|
+
|
|
73
|
+
if (str.length % 2) str = `0${str}`
|
|
74
|
+
|
|
75
|
+
this.state.client.capabilities = Buffer.from(str, 'hex').toString('base64')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
on (event, listener) {
|
|
79
|
+
super.on(event, listener)
|
|
80
|
+
|
|
81
|
+
this.state.client.products = this.eventNames()
|
|
82
|
+
|
|
83
|
+
this.scheduler.start()
|
|
84
|
+
|
|
85
|
+
return this
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
off (event, listener) {
|
|
89
|
+
super.off(event, listener)
|
|
90
|
+
|
|
91
|
+
this.state.client.products = this.eventNames()
|
|
92
|
+
|
|
93
|
+
if (!this.state.client.products.length) {
|
|
94
|
+
this.scheduler.stop()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
poll (cb) {
|
|
101
|
+
request(JSON.stringify(this.state), this.requestOptions, (err, data, statusCode) => {
|
|
102
|
+
// 404 means RC is disabled, ignore it
|
|
103
|
+
if (statusCode === 404) return cb()
|
|
104
|
+
|
|
105
|
+
if (err) {
|
|
106
|
+
log.error(err)
|
|
107
|
+
return cb()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// if error was just sent, reset the state
|
|
111
|
+
if (this.state.client.state.has_error) {
|
|
112
|
+
this.state.client.state.has_error = false
|
|
113
|
+
this.state.client.state.error = ''
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (data && data !== '{}') { // '{}' means the tracer is up to date
|
|
117
|
+
try {
|
|
118
|
+
this.parseConfig(JSON.parse(data))
|
|
119
|
+
} catch (err) {
|
|
120
|
+
log.error(`Could not parse remote config response: ${err}`)
|
|
121
|
+
|
|
122
|
+
this.state.client.state.has_error = true
|
|
123
|
+
this.state.client.state.error = err.toString()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
cb()
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// `client_configs` is the list of config paths to have applied
|
|
132
|
+
// `targets` is the signed index with metadata for config files
|
|
133
|
+
// `target_files` is the list of config files containing the actual config data
|
|
134
|
+
parseConfig ({
|
|
135
|
+
client_configs: clientConfigs = [],
|
|
136
|
+
targets,
|
|
137
|
+
target_files: targetFiles = []
|
|
138
|
+
}) {
|
|
139
|
+
const toUnapply = []
|
|
140
|
+
const toApply = []
|
|
141
|
+
const toModify = []
|
|
142
|
+
|
|
143
|
+
for (const appliedConfig of this.appliedConfigs.values()) {
|
|
144
|
+
if (!clientConfigs.includes(appliedConfig.path)) {
|
|
145
|
+
toUnapply.push(appliedConfig)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
targets = fromBase64JSON(targets)
|
|
150
|
+
|
|
151
|
+
if (targets) {
|
|
152
|
+
for (const path of clientConfigs) {
|
|
153
|
+
const meta = targets.signed.targets[path]
|
|
154
|
+
if (!meta) throw new Error(`Unable to find target for path ${path}`)
|
|
155
|
+
|
|
156
|
+
const current = this.appliedConfigs.get(path)
|
|
157
|
+
|
|
158
|
+
const newConf = {}
|
|
159
|
+
|
|
160
|
+
if (current) {
|
|
161
|
+
if (current.hashes.sha256 === meta.hashes.sha256) continue
|
|
162
|
+
|
|
163
|
+
toModify.push(newConf)
|
|
164
|
+
} else {
|
|
165
|
+
toApply.push(newConf)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const file = targetFiles.find(file => file.path === path)
|
|
169
|
+
if (!file) throw new Error(`Unable to find file for path ${path}`)
|
|
170
|
+
|
|
171
|
+
// TODO: verify signatures
|
|
172
|
+
// verify length
|
|
173
|
+
// verify hash
|
|
174
|
+
// verify _type
|
|
175
|
+
// TODO: new Date(meta.signed.expires) ignore the Targets data if it has expired ?
|
|
176
|
+
|
|
177
|
+
const { product, id } = parseConfigPath(path)
|
|
178
|
+
|
|
179
|
+
Object.assign(newConf, {
|
|
180
|
+
path,
|
|
181
|
+
product,
|
|
182
|
+
id,
|
|
183
|
+
version: meta.custom.v,
|
|
184
|
+
apply_state: 1,
|
|
185
|
+
apply_error: '',
|
|
186
|
+
length: meta.length,
|
|
187
|
+
hashes: meta.hashes,
|
|
188
|
+
file: fromBase64JSON(file.raw)
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.state.client.state.targets_version = targets.signed.version
|
|
193
|
+
this.state.client.state.backend_client_state = targets.signed.custom.opaque_backend_state
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (toUnapply.length || toApply.length || toModify.length) {
|
|
197
|
+
this.dispatch(toUnapply, 'unapply')
|
|
198
|
+
this.dispatch(toApply, 'apply')
|
|
199
|
+
this.dispatch(toModify, 'modify')
|
|
200
|
+
|
|
201
|
+
this.state.client.state.config_states = []
|
|
202
|
+
this.state.cached_target_files = []
|
|
203
|
+
|
|
204
|
+
for (const conf of this.appliedConfigs.values()) {
|
|
205
|
+
this.state.client.state.config_states.push({
|
|
206
|
+
id: conf.id,
|
|
207
|
+
version: conf.version,
|
|
208
|
+
product: conf.product,
|
|
209
|
+
apply_state: conf.apply_state,
|
|
210
|
+
apply_error: conf.apply_error
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
this.state.cached_target_files.push({
|
|
214
|
+
path: conf.path,
|
|
215
|
+
length: conf.length,
|
|
216
|
+
hashes: Object.entries(conf.hashes).map((entry) => ({ algorithm: entry[0], hash: entry[1] }))
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
dispatch (list, action) {
|
|
223
|
+
for (const item of list) {
|
|
224
|
+
try {
|
|
225
|
+
// TODO: do we want to pass old and new config ?
|
|
226
|
+
this.emit(item.product, action, item.file)
|
|
227
|
+
|
|
228
|
+
item.apply_state = 2
|
|
229
|
+
} catch (err) {
|
|
230
|
+
item.apply_state = 3
|
|
231
|
+
item.apply_error = err.toString()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (action === 'unapply') {
|
|
235
|
+
this.appliedConfigs.delete(item.path)
|
|
236
|
+
} else {
|
|
237
|
+
this.appliedConfigs.set(item.path, item)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function fromBase64JSON (str) {
|
|
244
|
+
if (!str) return null
|
|
245
|
+
|
|
246
|
+
return JSON.parse(Buffer.from(str, 'base64').toString())
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const configPathRegex = /^(?:datadog\/\d+|employee)\/([^/]+)\/([^/]+)\/[^/]+$/
|
|
250
|
+
|
|
251
|
+
function parseConfigPath (configPath) {
|
|
252
|
+
const match = configPathRegex.exec(configPath)
|
|
253
|
+
|
|
254
|
+
if (!match || !match[1] || !match[2]) {
|
|
255
|
+
throw new Error(`Unable to parse path ${configPath}`)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
product: match[1],
|
|
260
|
+
id: match[2]
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = RemoteConfigManager
|
|
@@ -8,21 +8,21 @@ class Scheduler {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
start () {
|
|
11
|
-
|
|
12
|
-
this._timer.unref && this._timer.unref()
|
|
11
|
+
if (this._timer) return
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
this.runAfterDelay(0)
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
runAfterDelay (interval = this._interval) {
|
|
17
|
+
this._timer = setTimeout(this._callback, interval, () => this.runAfterDelay())
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
this._timer.unref && this._timer.unref()
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
this.
|
|
25
|
-
|
|
22
|
+
stop () {
|
|
23
|
+
clearTimeout(this._timer)
|
|
24
|
+
|
|
25
|
+
this._timer = null
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const callbacks = require('./callbacks')
|
|
4
|
+
const Gateway = require('./gateway/engine')
|
|
4
5
|
|
|
5
6
|
const appliedCallbacks = new Map()
|
|
6
7
|
|
|
@@ -14,6 +15,8 @@ function applyRules (rules, config) {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
function clearAllRules () {
|
|
18
|
+
Gateway.manager.clear()
|
|
19
|
+
|
|
17
20
|
for (const [key, callback] of appliedCallbacks) {
|
|
18
21
|
callback.clear()
|
|
19
22
|
|
|
@@ -35,9 +35,7 @@ function getCommonRequestOptions (url) {
|
|
|
35
35
|
'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY
|
|
36
36
|
},
|
|
37
37
|
timeout: 15000,
|
|
38
|
-
|
|
39
|
-
hostname: url.hostname,
|
|
40
|
-
port: url.port
|
|
38
|
+
url
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
|
|
@@ -71,16 +69,16 @@ function getCommitsToExclude ({ url, repositoryUrl }, callback) {
|
|
|
71
69
|
}))
|
|
72
70
|
})
|
|
73
71
|
|
|
74
|
-
request(localCommitData, options, (err, response
|
|
72
|
+
request(localCommitData, options, (err, response) => {
|
|
75
73
|
if (err) {
|
|
76
|
-
const error = new Error(`
|
|
74
|
+
const error = new Error(`Error fetching commits to exclude: ${err.message}`)
|
|
77
75
|
return callback(error)
|
|
78
76
|
}
|
|
79
77
|
let commitsToExclude
|
|
80
78
|
try {
|
|
81
79
|
commitsToExclude = sanitizeCommits(JSON.parse(response).data)
|
|
82
80
|
} catch (e) {
|
|
83
|
-
return callback(new Error(`Can't parse
|
|
81
|
+
return callback(new Error(`Can't parse commits to exclude response: ${e.message}`))
|
|
84
82
|
}
|
|
85
83
|
callback(null, commitsToExclude, headCommit)
|
|
86
84
|
})
|
|
@@ -129,7 +127,7 @@ function uploadPackFile ({ url, packFileToUpload, repositoryUrl, headCommit }, c
|
|
|
129
127
|
}
|
|
130
128
|
request(form, options, (err, _, statusCode) => {
|
|
131
129
|
if (err) {
|
|
132
|
-
const error = new Error(`Could not upload packfiles: status code ${statusCode}`)
|
|
130
|
+
const error = new Error(`Could not upload packfiles: status code ${statusCode}: ${err.message}`)
|
|
133
131
|
return callback(error)
|
|
134
132
|
}
|
|
135
133
|
callback(null)
|
package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js
CHANGED
|
@@ -2,6 +2,7 @@ const request = require('../../exporters/common/request')
|
|
|
2
2
|
const id = require('../../id')
|
|
3
3
|
|
|
4
4
|
function getItrConfiguration ({
|
|
5
|
+
url,
|
|
5
6
|
site,
|
|
6
7
|
env,
|
|
7
8
|
service,
|
|
@@ -14,7 +15,7 @@ function getItrConfiguration ({
|
|
|
14
15
|
runtimeVersion,
|
|
15
16
|
branch
|
|
16
17
|
}, done) {
|
|
17
|
-
const
|
|
18
|
+
const intakeUrl = url || new URL(`https://api.${site}`)
|
|
18
19
|
|
|
19
20
|
const apiKey = process.env.DATADOG_API_KEY || process.env.DD_API_KEY
|
|
20
21
|
const appKey = process.env.DATADOG_APP_KEY ||
|
|
@@ -35,9 +36,7 @@ function getItrConfiguration ({
|
|
|
35
36
|
'dd-application-key': appKey,
|
|
36
37
|
'Content-Type': 'application/json'
|
|
37
38
|
},
|
|
38
|
-
|
|
39
|
-
hostname: url.hostname,
|
|
40
|
-
port: url.port
|
|
39
|
+
url: intakeUrl
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
const data = JSON.stringify({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const request = require('../../exporters/common/request')
|
|
2
2
|
|
|
3
3
|
function getSkippableSuites ({
|
|
4
|
+
url,
|
|
4
5
|
site,
|
|
5
6
|
env,
|
|
6
7
|
service,
|
|
@@ -12,7 +13,7 @@ function getSkippableSuites ({
|
|
|
12
13
|
runtimeName,
|
|
13
14
|
runtimeVersion
|
|
14
15
|
}, done) {
|
|
15
|
-
const
|
|
16
|
+
const intakeUrl = url || new URL(`https://api.${site}`)
|
|
16
17
|
|
|
17
18
|
const apiKey = process.env.DATADOG_API_KEY || process.env.DD_API_KEY
|
|
18
19
|
const appKey = process.env.DATADOG_APP_KEY ||
|
|
@@ -33,9 +34,7 @@ function getSkippableSuites ({
|
|
|
33
34
|
'Content-Type': 'application/json'
|
|
34
35
|
},
|
|
35
36
|
timeout: 15000,
|
|
36
|
-
|
|
37
|
-
hostname: url.hostname,
|
|
38
|
-
port: url.port
|
|
37
|
+
url: intakeUrl
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
const data = JSON.stringify({
|
|
@@ -4,6 +4,7 @@ const fs = require('fs')
|
|
|
4
4
|
const os = require('os')
|
|
5
5
|
const URL = require('url').URL
|
|
6
6
|
const path = require('path')
|
|
7
|
+
const log = require('./log')
|
|
7
8
|
const pkg = require('./pkg')
|
|
8
9
|
const coalesce = require('koalas')
|
|
9
10
|
const tagger = require('./tagger')
|
|
@@ -44,6 +45,21 @@ class Config {
|
|
|
44
45
|
constructor (options) {
|
|
45
46
|
options = options || {}
|
|
46
47
|
|
|
48
|
+
// Configure the logger first so it can be used to warn about other configs
|
|
49
|
+
this.debug = isTrue(coalesce(
|
|
50
|
+
process.env.DD_TRACE_DEBUG,
|
|
51
|
+
false
|
|
52
|
+
))
|
|
53
|
+
this.logger = options.logger
|
|
54
|
+
this.logLevel = coalesce(
|
|
55
|
+
options.logLevel,
|
|
56
|
+
process.env.DD_TRACE_LOG_LEVEL,
|
|
57
|
+
'debug'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
log.use(this.logger)
|
|
61
|
+
log.toggle(this.debug, this.logLevel, this)
|
|
62
|
+
|
|
47
63
|
this.tags = {}
|
|
48
64
|
|
|
49
65
|
tagger.add(this.tags, process.env.DD_TAGS)
|
|
@@ -76,6 +92,11 @@ class Config {
|
|
|
76
92
|
process.env.DD_RUNTIME_METRICS_ENABLED,
|
|
77
93
|
false
|
|
78
94
|
)
|
|
95
|
+
const DD_DBM_PROPAGATION_MODE = coalesce(
|
|
96
|
+
options.dbmPropagationMode,
|
|
97
|
+
process.env.DD_DBM_PROPAGATION_MODE,
|
|
98
|
+
'disabled'
|
|
99
|
+
)
|
|
79
100
|
const DD_AGENT_HOST = coalesce(
|
|
80
101
|
options.hostname,
|
|
81
102
|
process.env.DD_AGENT_HOST,
|
|
@@ -94,6 +115,7 @@ class Config {
|
|
|
94
115
|
null
|
|
95
116
|
)
|
|
96
117
|
const DD_CIVISIBILITY_AGENTLESS_URL = process.env.DD_CIVISIBILITY_AGENTLESS_URL
|
|
118
|
+
const DD_CIVISIBILITY_AGENTLESS_ENABLED = process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED
|
|
97
119
|
|
|
98
120
|
const DD_CIVISIBILITY_ITR_ENABLED = coalesce(
|
|
99
121
|
process.env.DD_CIVISIBILITY_ITR_ENABLED,
|
|
@@ -128,10 +150,6 @@ class Config {
|
|
|
128
150
|
process.env.DD_TRACE_TELEMETRY_ENABLED,
|
|
129
151
|
!process.env.AWS_LAMBDA_FUNCTION_NAME
|
|
130
152
|
)
|
|
131
|
-
const DD_TRACE_DEBUG = coalesce(
|
|
132
|
-
process.env.DD_TRACE_DEBUG,
|
|
133
|
-
false
|
|
134
|
-
)
|
|
135
153
|
const DD_TRACE_AGENT_PROTOCOL_VERSION = coalesce(
|
|
136
154
|
options.protocolVersion,
|
|
137
155
|
process.env.DD_TRACE_AGENT_PROTOCOL_VERSION,
|
|
@@ -186,16 +204,21 @@ class Config {
|
|
|
186
204
|
false
|
|
187
205
|
)
|
|
188
206
|
|
|
189
|
-
let appsec = options.appsec
|
|
207
|
+
let appsec = options.appsec != null ? options.appsec : options.experimental && options.experimental.appsec
|
|
208
|
+
|
|
209
|
+
if (typeof appsec === 'boolean') {
|
|
210
|
+
appsec = {
|
|
211
|
+
enabled: appsec
|
|
212
|
+
}
|
|
213
|
+
} else if (appsec == null) {
|
|
214
|
+
appsec = {}
|
|
215
|
+
}
|
|
190
216
|
|
|
191
217
|
const DD_APPSEC_ENABLED = coalesce(
|
|
192
|
-
appsec
|
|
193
|
-
process.env.DD_APPSEC_ENABLED
|
|
194
|
-
false
|
|
218
|
+
appsec.enabled,
|
|
219
|
+
process.env.DD_APPSEC_ENABLED && isTrue(process.env.DD_APPSEC_ENABLED)
|
|
195
220
|
)
|
|
196
221
|
|
|
197
|
-
appsec = appsec || {}
|
|
198
|
-
|
|
199
222
|
const DD_APPSEC_RULES = coalesce(
|
|
200
223
|
appsec.rules,
|
|
201
224
|
process.env.DD_APPSEC_RULES,
|
|
@@ -295,7 +318,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
295
318
|
const defaultFlushInterval = inAWSLambda ? 0 : 2000
|
|
296
319
|
|
|
297
320
|
this.tracing = !isFalse(DD_TRACING_ENABLED)
|
|
298
|
-
this.
|
|
321
|
+
this.dbmPropagationMode = DD_DBM_PROPAGATION_MODE
|
|
299
322
|
this.logInjection = isTrue(DD_LOGS_INJECTION)
|
|
300
323
|
this.env = DD_ENV
|
|
301
324
|
this.url = DD_CIVISIBILITY_AGENTLESS_URL ? new URL(DD_CIVISIBILITY_AGENTLESS_URL)
|
|
@@ -309,7 +332,6 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
309
332
|
this.clientIpHeaderDisabled = !isTrue(DD_APPSEC_ENABLED)
|
|
310
333
|
this.clientIpHeader = DD_TRACE_CLIENT_IP_HEADER
|
|
311
334
|
this.queryStringObfuscation = DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP
|
|
312
|
-
this.logger = options.logger
|
|
313
335
|
this.plugins = !!coalesce(options.plugins, true)
|
|
314
336
|
this.service = DD_SERVICE
|
|
315
337
|
this.serviceMapping = DD_SERVICE_MAPPING.length ? fromEntries(
|
|
@@ -331,11 +353,6 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
331
353
|
this.sampler = sampler
|
|
332
354
|
this.reportHostname = isTrue(coalesce(options.reportHostname, process.env.DD_TRACE_REPORT_HOSTNAME, false))
|
|
333
355
|
this.scope = process.env.DD_TRACE_SCOPE
|
|
334
|
-
this.logLevel = coalesce(
|
|
335
|
-
options.logLevel,
|
|
336
|
-
process.env.DD_TRACE_LOG_LEVEL,
|
|
337
|
-
'debug'
|
|
338
|
-
)
|
|
339
356
|
this.profiling = {
|
|
340
357
|
enabled: isTrue(DD_PROFILING_ENABLED),
|
|
341
358
|
sourceMap: !isFalse(DD_PROFILING_SOURCE_MAP),
|
|
@@ -348,7 +365,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
348
365
|
this.protocolVersion = DD_TRACE_AGENT_PROTOCOL_VERSION
|
|
349
366
|
this.tagsHeaderMaxLength = parseInt(DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH)
|
|
350
367
|
this.appsec = {
|
|
351
|
-
enabled:
|
|
368
|
+
enabled: DD_APPSEC_ENABLED,
|
|
352
369
|
rules: DD_APPSEC_RULES,
|
|
353
370
|
rateLimit: DD_APPSEC_TRACE_RATE_LIMIT,
|
|
354
371
|
wafTimeout: DD_APPSEC_WAF_TIMEOUT,
|
|
@@ -361,8 +378,12 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
361
378
|
maxConcurrentRequests: DD_IAST_MAX_CONCURRENT_REQUESTS,
|
|
362
379
|
maxContextOperations: DD_IAST_MAX_CONTEXT_OPERATIONS
|
|
363
380
|
}
|
|
364
|
-
|
|
365
|
-
|
|
381
|
+
|
|
382
|
+
const isCiVisibilityAgentlessEnabled = isTrue(DD_CIVISIBILITY_AGENTLESS_ENABLED)
|
|
383
|
+
this.isIntelligentTestRunnerEnabled = isCiVisibilityAgentlessEnabled && isTrue(DD_CIVISIBILITY_ITR_ENABLED)
|
|
384
|
+
this.isGitUploadEnabled = this.isIntelligentTestRunnerEnabled ||
|
|
385
|
+
(isCiVisibilityAgentlessEnabled && isTrue(DD_CIVISIBILITY_GIT_UPLOAD_ENABLED))
|
|
386
|
+
|
|
366
387
|
this.stats = {
|
|
367
388
|
enabled: isTrue(DD_TRACE_STATS_COMPUTATION_ENABLED)
|
|
368
389
|
}
|
|
@@ -19,5 +19,10 @@ module.exports = {
|
|
|
19
19
|
SPAN_SAMPLING_RULE_RATE: '_dd.span_sampling.rule_rate',
|
|
20
20
|
SPAN_SAMPLING_MAX_PER_SECOND: '_dd.span_sampling.max_per_second',
|
|
21
21
|
DATADOG_LAMBDA_EXTENSION_PATH: '/opt/extensions/datadog-agent',
|
|
22
|
-
DECISION_MAKER_KEY: '_dd.p.dm'
|
|
22
|
+
DECISION_MAKER_KEY: '_dd.p.dm',
|
|
23
|
+
PROCESS_ID: 'process_id',
|
|
24
|
+
ERROR_TYPE: 'error.type',
|
|
25
|
+
ERROR_MESSAGE: 'error.message',
|
|
26
|
+
ERROR_STACK: 'error.stack',
|
|
27
|
+
COMPONENT: 'component'
|
|
23
28
|
}
|
|
@@ -66,7 +66,13 @@ function request (data, options, callback) {
|
|
|
66
66
|
if (res.statusCode >= 200 && res.statusCode <= 299) {
|
|
67
67
|
callback(null, responseData, res.statusCode)
|
|
68
68
|
} else {
|
|
69
|
-
const
|
|
69
|
+
const fullUrl = `${options.url || options.hostname || `localhost:${options.port}`}${options.path}`
|
|
70
|
+
// eslint-disable-next-line
|
|
71
|
+
let errorMessage = `Error from ${fullUrl}: ${res.statusCode} ${http.STATUS_CODES[res.statusCode]}.`
|
|
72
|
+
if (responseData) {
|
|
73
|
+
errorMessage += ` Response from the endpoint: "${responseData}"`
|
|
74
|
+
}
|
|
75
|
+
const error = new Error(errorMessage)
|
|
70
76
|
error.status = res.statusCode
|
|
71
77
|
|
|
72
78
|
callback(error, null, res.statusCode)
|
|
@@ -17,6 +17,10 @@ const MEASURED = tags.MEASURED
|
|
|
17
17
|
const ORIGIN_KEY = constants.ORIGIN_KEY
|
|
18
18
|
const HOSTNAME_KEY = constants.HOSTNAME_KEY
|
|
19
19
|
const TOP_LEVEL_KEY = constants.TOP_LEVEL_KEY
|
|
20
|
+
const PROCESS_ID = constants.PROCESS_ID
|
|
21
|
+
const ERROR_MESSAGE = constants.ERROR_MESSAGE
|
|
22
|
+
const ERROR_STACK = constants.ERROR_STACK
|
|
23
|
+
const ERROR_TYPE = constants.ERROR_TYPE
|
|
20
24
|
|
|
21
25
|
const map = {
|
|
22
26
|
'service.name': 'service',
|
|
@@ -89,9 +93,9 @@ function extractTags (trace, span) {
|
|
|
89
93
|
extractError(trace, tags[tag])
|
|
90
94
|
}
|
|
91
95
|
break
|
|
92
|
-
case
|
|
93
|
-
case
|
|
94
|
-
case
|
|
96
|
+
case ERROR_TYPE:
|
|
97
|
+
case ERROR_MESSAGE:
|
|
98
|
+
case ERROR_STACK:
|
|
95
99
|
// HACK: remove when implemented in the backend
|
|
96
100
|
if (context._name !== 'fs.operation') {
|
|
97
101
|
trace.error = 1
|
|
@@ -103,12 +107,10 @@ function extractTags (trace, span) {
|
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
if (span.tracer()._service === tags['service.name']) {
|
|
107
|
-
addTag(trace.meta, trace.metrics, 'language', 'javascript')
|
|
108
|
-
}
|
|
109
|
-
|
|
110
110
|
setSingleSpanIngestionTags(trace, context._sampling.spanSampling)
|
|
111
111
|
|
|
112
|
+
addTag(trace.meta, trace.metrics, 'language', 'javascript')
|
|
113
|
+
addTag(trace.meta, trace.metrics, PROCESS_ID, process.pid)
|
|
112
114
|
addTag(trace.meta, trace.metrics, SAMPLING_PRIORITY_KEY, priority)
|
|
113
115
|
addTag(trace.meta, trace.metrics, ORIGIN_KEY, origin)
|
|
114
116
|
addTag(trace.meta, trace.metrics, HOSTNAME_KEY, hostname)
|
|
@@ -144,9 +146,9 @@ function extractError (trace, error) {
|
|
|
144
146
|
trace.error = 1
|
|
145
147
|
|
|
146
148
|
if (isError(error)) {
|
|
147
|
-
addTag(trace.meta, trace.metrics,
|
|
148
|
-
addTag(trace.meta, trace.metrics,
|
|
149
|
-
addTag(trace.meta, trace.metrics,
|
|
149
|
+
addTag(trace.meta, trace.metrics, ERROR_MESSAGE, error.message)
|
|
150
|
+
addTag(trace.meta, trace.metrics, ERROR_TYPE, error.name)
|
|
151
|
+
addTag(trace.meta, trace.metrics, ERROR_STACK, error.stack)
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
|
|
@@ -130,11 +130,7 @@ class TextMapPropagator {
|
|
|
130
130
|
|
|
131
131
|
_injectTraceparent (spanContext, carrier) {
|
|
132
132
|
if (!this._config.experimental.traceparent) return
|
|
133
|
-
|
|
134
|
-
const sampling = spanContext._sampling.priority >= AUTO_KEEP ? '01' : '00'
|
|
135
|
-
const traceId = spanContext._traceId.toString(16).padStart(32, '0')
|
|
136
|
-
const spanId = spanContext._spanId.toString(16).padStart(16, '0')
|
|
137
|
-
carrier[traceparentKey] = `01-${traceId}-${spanId}-${sampling}`
|
|
133
|
+
carrier[traceparentKey] = spanContext.toTraceparent()
|
|
138
134
|
}
|
|
139
135
|
|
|
140
136
|
_extractSpanContext (carrier) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { AUTO_KEEP } = require('../../../../ext/priority')
|
|
4
|
+
|
|
3
5
|
class DatadogSpanContext {
|
|
4
6
|
constructor (props) {
|
|
5
7
|
props = props || {}
|
|
@@ -27,6 +29,13 @@ class DatadogSpanContext {
|
|
|
27
29
|
toSpanId () {
|
|
28
30
|
return this._spanId.toString(10)
|
|
29
31
|
}
|
|
32
|
+
|
|
33
|
+
toTraceparent () {
|
|
34
|
+
const sampling = this._sampling.priority >= AUTO_KEEP ? '01' : '00'
|
|
35
|
+
const traceId = this._traceId.toString(16).padStart(32, '0')
|
|
36
|
+
const spanId = this._spanId.toString(16).padStart(16, '0')
|
|
37
|
+
return `01-${traceId}-${spanId}-${sampling}`
|
|
38
|
+
}
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
module.exports = DatadogSpanContext
|
|
@@ -124,7 +124,9 @@ module.exports = class PluginManager {
|
|
|
124
124
|
isIntelligentTestRunnerEnabled,
|
|
125
125
|
site,
|
|
126
126
|
experimental,
|
|
127
|
-
queryStringObfuscation
|
|
127
|
+
queryStringObfuscation,
|
|
128
|
+
url,
|
|
129
|
+
dbmPropagationMode
|
|
128
130
|
} = this._tracerConfig
|
|
129
131
|
|
|
130
132
|
const sharedConfig = {}
|
|
@@ -151,11 +153,14 @@ module.exports = class PluginManager {
|
|
|
151
153
|
|
|
152
154
|
sharedConfig.isIntelligentTestRunnerEnabled = isIntelligentTestRunnerEnabled
|
|
153
155
|
|
|
156
|
+
sharedConfig.dbmPropagationMode = dbmPropagationMode
|
|
157
|
+
|
|
154
158
|
if (serviceMapping && serviceMapping[name]) {
|
|
155
159
|
sharedConfig.service = serviceMapping[name]
|
|
156
160
|
}
|
|
157
161
|
|
|
158
162
|
sharedConfig.site = site
|
|
163
|
+
sharedConfig.url = url
|
|
159
164
|
|
|
160
165
|
return sharedConfig
|
|
161
166
|
}
|