dd-trace 2.11.0 → 3.0.0-pre.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 +0 -1
- package/ci/init.js +2 -26
- package/ci/jest/env.js +2 -3
- package/index.d.ts +3 -10
- package/package.json +4 -5
- package/packages/datadog-core/src/storage/async_hooks.js +4 -4
- package/packages/datadog-core/src/storage/async_resource.js +14 -4
- package/packages/datadog-instrumentations/src/connect.js +4 -4
- package/packages/datadog-instrumentations/src/couchbase.js +166 -61
- package/packages/datadog-instrumentations/src/fastify.js +12 -25
- package/packages/datadog-instrumentations/src/graphql.js +17 -5
- package/packages/datadog-instrumentations/src/koa.js +4 -4
- package/packages/datadog-instrumentations/src/mocha.js +88 -18
- package/packages/datadog-instrumentations/src/restify.js +4 -8
- package/packages/datadog-instrumentations/src/router.js +4 -4
- package/packages/datadog-plugin-couchbase/src/index.js +8 -10
- package/packages/datadog-plugin-graphql/src/resolve.js +2 -0
- package/packages/datadog-plugin-grpc/src/client.js +2 -2
- package/packages/datadog-plugin-grpc/src/server.js +2 -2
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-http/src/server.js +4 -9
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-mocha/src/index.js +80 -3
- package/packages/datadog-plugin-next/src/index.js +1 -1
- package/packages/datadog-plugin-router/src/index.js +39 -10
- package/packages/dd-trace/src/config.js +8 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +111 -15
- package/packages/dd-trace/src/exporters/common/request.js +43 -34
- package/packages/dd-trace/src/noop/tracer.js +0 -4
- package/packages/dd-trace/src/plugin_manager.js +53 -33
- package/packages/dd-trace/src/plugins/index.js +0 -1
- package/packages/dd-trace/src/plugins/util/test.js +32 -1
- package/packages/dd-trace/src/plugins/util/web.js +73 -20
- package/packages/dd-trace/src/profiling/config.js +10 -2
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -2
- package/packages/dd-trace/src/profiling/exporters/form-data.js +53 -0
- package/packages/dd-trace/src/profiling/index.js +2 -0
- package/packages/dd-trace/src/profiling/profiler.js +6 -1
- package/packages/dd-trace/src/profiling/profilers/cpu.js +126 -0
- package/packages/dd-trace/src/proxy.js +0 -19
- package/packages/dd-trace/src/scope.js +1 -126
- package/packages/dd-trace/src/tracer.js +0 -4
- package/cypress/plugin.js +0 -5
- package/cypress/support.js +0 -1
- package/packages/datadog-plugin-fs/src/index.js +0 -548
|
@@ -3,13 +3,25 @@ const { truncateSpan, normalizeSpan } = require('./tags-processors')
|
|
|
3
3
|
const Chunk = require('./chunk')
|
|
4
4
|
const { AgentEncoder } = require('./0.4')
|
|
5
5
|
const { version: ddTraceVersion } = require('../../../../package.json')
|
|
6
|
+
const id = require('../../../dd-trace/src/id')
|
|
6
7
|
|
|
7
8
|
const ENCODING_VERSION = 1
|
|
8
9
|
|
|
10
|
+
const ALLOWED_CONTENT_TYPES = ['test_session_end', 'test_suite_end', 'test']
|
|
11
|
+
|
|
12
|
+
const TEST_SUITE_KEYS_LENGTH = 11
|
|
13
|
+
const TEST_SESSION_KEYS_LENGTH = 10
|
|
14
|
+
|
|
15
|
+
const CHUNK_SIZE = 4 * 1024 * 1024 // 4MB
|
|
16
|
+
|
|
9
17
|
function formatSpan (span) {
|
|
18
|
+
let encodingVersion = ENCODING_VERSION
|
|
19
|
+
if (span.type === 'test' && span.meta && span.meta.test_session_id) {
|
|
20
|
+
encodingVersion = 2
|
|
21
|
+
}
|
|
10
22
|
return {
|
|
11
|
-
type: span.type
|
|
12
|
-
version:
|
|
23
|
+
type: ALLOWED_CONTENT_TYPES.includes(span.type) ? span.type : 'span',
|
|
24
|
+
version: encodingVersion,
|
|
13
25
|
content: normalizeSpan(truncateSpan(span))
|
|
14
26
|
}
|
|
15
27
|
}
|
|
@@ -21,8 +33,8 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
21
33
|
this.runtimeId = runtimeId
|
|
22
34
|
this.service = service
|
|
23
35
|
this.env = env
|
|
24
|
-
this._traceBytes = new Chunk()
|
|
25
|
-
this._stringBytes = new Chunk()
|
|
36
|
+
this._traceBytes = new Chunk(CHUNK_SIZE)
|
|
37
|
+
this._stringBytes = new Chunk(CHUNK_SIZE)
|
|
26
38
|
this._stringCount = 0
|
|
27
39
|
this._stringMap = {}
|
|
28
40
|
|
|
@@ -33,8 +45,69 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
33
45
|
this.reset()
|
|
34
46
|
}
|
|
35
47
|
|
|
48
|
+
_encodeTestSuite (bytes, content) {
|
|
49
|
+
this._encodeMapPrefix(bytes, TEST_SUITE_KEYS_LENGTH)
|
|
50
|
+
this._encodeString(bytes, 'type')
|
|
51
|
+
this._encodeString(bytes, content.type)
|
|
52
|
+
|
|
53
|
+
this._encodeString(bytes, 'test_session_id')
|
|
54
|
+
this._encodeId(bytes, content.trace_id)
|
|
55
|
+
|
|
56
|
+
this._encodeString(bytes, 'test_suite_id')
|
|
57
|
+
this._encodeId(bytes, content.span_id)
|
|
58
|
+
|
|
59
|
+
this._encodeString(bytes, 'error')
|
|
60
|
+
this._encodeNumber(bytes, content.error)
|
|
61
|
+
this._encodeString(bytes, 'name')
|
|
62
|
+
this._encodeString(bytes, content.name)
|
|
63
|
+
this._encodeString(bytes, 'service')
|
|
64
|
+
this._encodeString(bytes, content.service)
|
|
65
|
+
this._encodeString(bytes, 'resource')
|
|
66
|
+
this._encodeString(bytes, content.resource)
|
|
67
|
+
this._encodeString(bytes, 'start')
|
|
68
|
+
this._encodeNumber(bytes, content.start)
|
|
69
|
+
this._encodeString(bytes, 'duration')
|
|
70
|
+
this._encodeNumber(bytes, content.duration)
|
|
71
|
+
this._encodeString(bytes, 'meta')
|
|
72
|
+
this._encodeMap(bytes, content.meta)
|
|
73
|
+
this._encodeString(bytes, 'metrics')
|
|
74
|
+
this._encodeMap(bytes, content.metrics)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_encodeTestSession (bytes, content) {
|
|
78
|
+
this._encodeMapPrefix(bytes, TEST_SESSION_KEYS_LENGTH)
|
|
79
|
+
this._encodeString(bytes, 'type')
|
|
80
|
+
this._encodeString(bytes, content.type)
|
|
81
|
+
|
|
82
|
+
this._encodeString(bytes, 'test_session_id')
|
|
83
|
+
this._encodeId(bytes, content.trace_id)
|
|
84
|
+
|
|
85
|
+
this._encodeString(bytes, 'error')
|
|
86
|
+
this._encodeNumber(bytes, content.error)
|
|
87
|
+
this._encodeString(bytes, 'name')
|
|
88
|
+
this._encodeString(bytes, content.name)
|
|
89
|
+
this._encodeString(bytes, 'service')
|
|
90
|
+
this._encodeString(bytes, content.service)
|
|
91
|
+
this._encodeString(bytes, 'resource')
|
|
92
|
+
this._encodeString(bytes, content.resource)
|
|
93
|
+
this._encodeString(bytes, 'start')
|
|
94
|
+
this._encodeNumber(bytes, content.start)
|
|
95
|
+
this._encodeString(bytes, 'duration')
|
|
96
|
+
this._encodeNumber(bytes, content.duration)
|
|
97
|
+
this._encodeString(bytes, 'meta')
|
|
98
|
+
this._encodeMap(bytes, content.meta)
|
|
99
|
+
this._encodeString(bytes, 'metrics')
|
|
100
|
+
this._encodeMap(bytes, content.metrics)
|
|
101
|
+
}
|
|
102
|
+
|
|
36
103
|
_encodeEventContent (bytes, content) {
|
|
37
|
-
|
|
104
|
+
const keysLength = Object.keys(content).length
|
|
105
|
+
if (content.meta.test_session_id) {
|
|
106
|
+
this._encodeMapPrefix(bytes, keysLength + 2)
|
|
107
|
+
} else {
|
|
108
|
+
this._encodeMapPrefix(bytes, keysLength)
|
|
109
|
+
}
|
|
110
|
+
|
|
38
111
|
if (content.type) {
|
|
39
112
|
this._encodeString(bytes, 'type')
|
|
40
113
|
this._encodeString(bytes, content.type)
|
|
@@ -57,6 +130,24 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
57
130
|
this._encodeNumber(bytes, content.start)
|
|
58
131
|
this._encodeString(bytes, 'duration')
|
|
59
132
|
this._encodeNumber(bytes, content.duration)
|
|
133
|
+
/**
|
|
134
|
+
* We include `test_session_id` and `test_suite_id`
|
|
135
|
+
* in the root of the event by passing them via the `meta` dict.
|
|
136
|
+
* This is to avoid changing the span format in packages/dd-trace/src/format.js,
|
|
137
|
+
* which can have undesired side effects in other products.
|
|
138
|
+
* But `test_session_id` and `test_suite_id` are *not* supposed to be in `meta`,
|
|
139
|
+
* so we delete them before enconding the dictionary.
|
|
140
|
+
* TODO: find a better way to do this.
|
|
141
|
+
*/
|
|
142
|
+
if (content.meta.test_session_id) {
|
|
143
|
+
this._encodeString(bytes, 'test_session_id')
|
|
144
|
+
this._encodeId(bytes, id(content.meta.test_session_id))
|
|
145
|
+
delete content.meta.test_session_id
|
|
146
|
+
|
|
147
|
+
this._encodeString(bytes, 'test_suite_id')
|
|
148
|
+
this._encodeId(bytes, id(content.meta.test_suite_id))
|
|
149
|
+
delete content.meta.test_suite_id
|
|
150
|
+
}
|
|
60
151
|
this._encodeString(bytes, 'meta')
|
|
61
152
|
this._encodeMap(bytes, content.meta)
|
|
62
153
|
this._encodeString(bytes, 'metrics')
|
|
@@ -64,7 +155,7 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
64
155
|
}
|
|
65
156
|
|
|
66
157
|
_encodeEvent (bytes, event) {
|
|
67
|
-
this._encodeMapPrefix(bytes, event)
|
|
158
|
+
this._encodeMapPrefix(bytes, Object.keys(event).length)
|
|
68
159
|
this._encodeString(bytes, 'type')
|
|
69
160
|
this._encodeString(bytes, event.type)
|
|
70
161
|
|
|
@@ -72,7 +163,13 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
72
163
|
this._encodeNumber(bytes, event.version)
|
|
73
164
|
|
|
74
165
|
this._encodeString(bytes, 'content')
|
|
75
|
-
|
|
166
|
+
if (event.type === 'span' || event.type === 'test') {
|
|
167
|
+
this._encodeEventContent(bytes, event.content)
|
|
168
|
+
} else if (event.type === 'test_suite_end') {
|
|
169
|
+
this._encodeTestSuite(bytes, event.content)
|
|
170
|
+
} else if (event.type === 'test_session_end') {
|
|
171
|
+
this._encodeTestSession(bytes, event.content)
|
|
172
|
+
}
|
|
76
173
|
}
|
|
77
174
|
|
|
78
175
|
_encodeNumber (bytes, value) {
|
|
@@ -107,18 +204,17 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
107
204
|
buffer[offset + 8] = lo
|
|
108
205
|
}
|
|
109
206
|
|
|
110
|
-
_encodeMapPrefix (bytes,
|
|
111
|
-
const keys = Object.keys(map)
|
|
207
|
+
_encodeMapPrefix (bytes, keysLength) {
|
|
112
208
|
const buffer = bytes.buffer
|
|
113
209
|
const offset = bytes.length
|
|
114
210
|
|
|
115
211
|
bytes.reserve(5)
|
|
116
212
|
bytes.length += 5
|
|
117
213
|
buffer[offset] = 0xdf
|
|
118
|
-
buffer[offset + 1] =
|
|
119
|
-
buffer[offset + 2] =
|
|
120
|
-
buffer[offset + 3] =
|
|
121
|
-
buffer[offset + 4] =
|
|
214
|
+
buffer[offset + 1] = keysLength >> 24
|
|
215
|
+
buffer[offset + 2] = keysLength >> 16
|
|
216
|
+
buffer[offset + 3] = keysLength >> 8
|
|
217
|
+
buffer[offset + 4] = keysLength
|
|
122
218
|
}
|
|
123
219
|
|
|
124
220
|
_encode (bytes, trace) {
|
|
@@ -171,11 +267,11 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
171
267
|
payload.metadata['*']['runtime-id'] = this.runtimeId
|
|
172
268
|
}
|
|
173
269
|
|
|
174
|
-
this._encodeMapPrefix(bytes, payload)
|
|
270
|
+
this._encodeMapPrefix(bytes, Object.keys(payload).length)
|
|
175
271
|
this._encodeString(bytes, 'version')
|
|
176
272
|
this._encodeNumber(bytes, payload.version)
|
|
177
273
|
this._encodeString(bytes, 'metadata')
|
|
178
|
-
this._encodeMapPrefix(bytes, payload.metadata)
|
|
274
|
+
this._encodeMapPrefix(bytes, Object.keys(payload.metadata).length)
|
|
179
275
|
this._encodeString(bytes, '*')
|
|
180
276
|
this._encodeMap(bytes, payload.metadata['*'])
|
|
181
277
|
this._encodeString(bytes, 'events')
|
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
// TODO: Add test with slow or unresponsive agent.
|
|
4
|
+
// TODO: Add telemetry for things like dropped requests, errors, etc.
|
|
5
|
+
|
|
3
6
|
const http = require('http')
|
|
4
7
|
const https = require('https')
|
|
5
|
-
const log = require('../../log')
|
|
6
8
|
const docker = require('./docker')
|
|
7
9
|
const { storage } = require('../../../../datadog-core')
|
|
8
10
|
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
+
const keepAlive = true
|
|
12
|
+
const maxTotalSockets = 8
|
|
13
|
+
const httpAgent = new http.Agent({ keepAlive, maxTotalSockets })
|
|
14
|
+
const httpsAgent = new https.Agent({ keepAlive, maxTotalSockets })
|
|
11
15
|
const containerId = docker.id()
|
|
12
16
|
|
|
17
|
+
let activeRequests = 0
|
|
18
|
+
|
|
13
19
|
function request (data, options, keepAlive, callback) {
|
|
14
20
|
if (!options.headers) {
|
|
15
21
|
options.headers = {}
|
|
16
22
|
}
|
|
23
|
+
|
|
24
|
+
// The timeout should be kept low to avoid excessive queueing.
|
|
25
|
+
const timeout = options.timeout || 2000
|
|
17
26
|
const isSecure = options.protocol === 'https:'
|
|
18
27
|
const client = isSecure ? https : http
|
|
19
28
|
const dataArray = [].concat(data)
|
|
29
|
+
|
|
20
30
|
options.headers['Content-Length'] = byteLength(dataArray)
|
|
21
31
|
|
|
22
32
|
if (containerId) {
|
|
@@ -27,39 +37,15 @@ function request (data, options, keepAlive, callback) {
|
|
|
27
37
|
options.agent = isSecure ? httpsAgent : httpAgent
|
|
28
38
|
}
|
|
29
39
|
|
|
30
|
-
const
|
|
31
|
-
dataArray.forEach(buffer => firstRequest.write(buffer))
|
|
32
|
-
|
|
33
|
-
// The first request will be retried
|
|
34
|
-
const firstRequestErrorHandler = () => {
|
|
35
|
-
log.debug('Retrying request to the intake')
|
|
36
|
-
const retriedReq = retriableRequest(options, client, callback)
|
|
37
|
-
dataArray.forEach(buffer => retriedReq.write(buffer))
|
|
38
|
-
// The retried request will fail normally
|
|
39
|
-
retriedReq.on('error', e => callback(new Error(`Network error trying to reach the intake: ${e.message}`)))
|
|
40
|
-
retriedReq.end()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
firstRequest.on('error', firstRequestErrorHandler)
|
|
44
|
-
firstRequest.end()
|
|
45
|
-
|
|
46
|
-
return firstRequest
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function retriableRequest (options, client, callback) {
|
|
50
|
-
const store = storage.getStore()
|
|
51
|
-
|
|
52
|
-
storage.enterWith({ noop: true })
|
|
53
|
-
|
|
54
|
-
const timeout = options.timeout || 15000
|
|
55
|
-
|
|
56
|
-
const request = client.request(options, res => {
|
|
40
|
+
const onResponse = res => {
|
|
57
41
|
let responseData = ''
|
|
58
42
|
|
|
59
43
|
res.setTimeout(timeout)
|
|
60
44
|
|
|
61
45
|
res.on('data', chunk => { responseData += chunk })
|
|
62
46
|
res.on('end', () => {
|
|
47
|
+
activeRequests--
|
|
48
|
+
|
|
63
49
|
if (res.statusCode >= 200 && res.statusCode <= 299) {
|
|
64
50
|
callback(null, responseData, res.statusCode)
|
|
65
51
|
} else {
|
|
@@ -69,11 +55,34 @@ function retriableRequest (options, client, callback) {
|
|
|
69
55
|
callback(error, null, res.statusCode)
|
|
70
56
|
}
|
|
71
57
|
})
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const makeRequest = onError => {
|
|
61
|
+
// Limit to 1 request by socket, otherwise drop payload.
|
|
62
|
+
if (activeRequests >= maxTotalSockets) return
|
|
63
|
+
|
|
64
|
+
activeRequests++
|
|
65
|
+
|
|
66
|
+
const store = storage.getStore()
|
|
67
|
+
|
|
68
|
+
storage.enterWith({ noop: true })
|
|
69
|
+
|
|
70
|
+
const req = client.request(options, onResponse)
|
|
71
|
+
|
|
72
|
+
req.once('error', err => {
|
|
73
|
+
activeRequests--
|
|
74
|
+
onError(err)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
dataArray.forEach(buffer => req.write(buffer))
|
|
78
|
+
|
|
79
|
+
req.setTimeout(timeout, req.abort)
|
|
80
|
+
req.end()
|
|
81
|
+
|
|
82
|
+
storage.enterWith(store)
|
|
83
|
+
}
|
|
75
84
|
|
|
76
|
-
|
|
85
|
+
makeRequest(() => makeRequest(callback))
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
function byteLength (data) {
|
|
@@ -36,61 +36,81 @@ const collectDisabledPlugins = () => {
|
|
|
36
36
|
// TODO this must always be a singleton.
|
|
37
37
|
module.exports = class PluginManager {
|
|
38
38
|
constructor (tracer) {
|
|
39
|
+
this._tracer = tracer
|
|
39
40
|
this._pluginsByName = {}
|
|
40
41
|
this._configsByName = {}
|
|
41
|
-
|
|
42
|
-
const _disabledPlugins = collectDisabledPlugins()
|
|
43
|
-
|
|
44
|
-
for (const PluginClass of Object.values(plugins)) {
|
|
45
|
-
/**
|
|
46
|
-
* disabling the plugin here instead of in `configure` so we don't waste subscriber
|
|
47
|
-
* resources on a plugin that will eventually be disabled anyways
|
|
48
|
-
*/
|
|
49
|
-
if (_disabledPlugins.has(PluginClass.name)) {
|
|
50
|
-
log.debug(`Plugin "${PluginClass.name}" was disabled via configuration option.`)
|
|
51
|
-
continue
|
|
52
|
-
}
|
|
53
|
-
if (typeof PluginClass !== 'function') continue
|
|
54
|
-
this._pluginsByName[PluginClass.name] = new PluginClass(tracer)
|
|
55
|
-
this._configsByName[PluginClass.name] = {}
|
|
56
|
-
}
|
|
42
|
+
this._disabledPlugins = collectDisabledPlugins()
|
|
57
43
|
}
|
|
58
44
|
|
|
59
45
|
// like instrumenter.use()
|
|
60
46
|
configurePlugin (name, pluginConfig) {
|
|
61
|
-
if (!(name in this._pluginsByName)) return
|
|
62
47
|
if (typeof pluginConfig === 'boolean') {
|
|
63
48
|
pluginConfig = { enabled: pluginConfig }
|
|
64
49
|
}
|
|
50
|
+
if (!pluginConfig) {
|
|
51
|
+
pluginConfig = { enabled: true }
|
|
52
|
+
}
|
|
65
53
|
|
|
66
|
-
|
|
54
|
+
this._configsByName[name] = {
|
|
67
55
|
...this._configsByName[name],
|
|
68
56
|
...pluginConfig
|
|
69
57
|
}
|
|
70
58
|
|
|
71
|
-
this._pluginsByName[name]
|
|
59
|
+
if (this._pluginsByName[name]) {
|
|
60
|
+
this._pluginsByName[name].configure(getConfig(name, this._configsByName[name]))
|
|
61
|
+
}
|
|
72
62
|
}
|
|
73
63
|
|
|
74
64
|
// like instrumenter.enable()
|
|
75
|
-
configure (config) {
|
|
76
|
-
const { logInjection, serviceMapping } = config
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
65
|
+
configure (config = {}) {
|
|
66
|
+
const { logInjection, serviceMapping, experimental, queryStringObfuscation } = config
|
|
67
|
+
|
|
68
|
+
for (const PluginClass of Object.values(plugins)) {
|
|
69
|
+
const name = PluginClass.name
|
|
70
|
+
|
|
71
|
+
if (this._disabledPlugins.has(name)) {
|
|
72
|
+
log.debug(`Plugin "${name}" was disabled via configuration option.`)
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (typeof PluginClass !== 'function') continue
|
|
77
|
+
|
|
78
|
+
this._pluginsByName[name] = new PluginClass(this._tracer)
|
|
79
|
+
|
|
80
|
+
if (config.plugins === false) continue
|
|
81
|
+
|
|
82
|
+
const pluginConfig = {
|
|
83
|
+
...this._configsByName[name]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (logInjection !== undefined) {
|
|
87
|
+
pluginConfig.logInjection = logInjection
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (queryStringObfuscation !== undefined) {
|
|
91
|
+
pluginConfig.queryStringObfuscation = queryStringObfuscation
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// TODO: update so that it's available for every CI Visibility's plugin
|
|
95
|
+
if (name === 'mocha') {
|
|
96
|
+
pluginConfig.isAgentlessEnabled = experimental && experimental.exporter === 'datadog'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (serviceMapping && serviceMapping[name]) {
|
|
100
|
+
pluginConfig.service = serviceMapping[name]
|
|
88
101
|
}
|
|
102
|
+
|
|
103
|
+
this.configurePlugin(name, pluginConfig)
|
|
89
104
|
}
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
// This is basically just for testing. like intrumenter.disable()
|
|
93
108
|
destroy () {
|
|
94
|
-
for (const name in this._pluginsByName)
|
|
109
|
+
for (const name in this._pluginsByName) {
|
|
110
|
+
this._pluginsByName[name].configure({ enabled: false })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this._pluginsByName = {}
|
|
114
|
+
this._configsByName = {}
|
|
95
115
|
}
|
|
96
116
|
}
|
|
@@ -15,7 +15,6 @@ module.exports = {
|
|
|
15
15
|
'express': require('../../../datadog-plugin-express/src'),
|
|
16
16
|
'fastify': require('../../../datadog-plugin-fastify/src'),
|
|
17
17
|
'find-my-way': require('../../../datadog-plugin-find-my-way/src'),
|
|
18
|
-
'fs': require('../../../datadog-plugin-fs/src'),
|
|
19
18
|
'google-cloud-pubsub': require('../../../datadog-plugin-google-cloud-pubsub/src'),
|
|
20
19
|
'graphql': require('../../../datadog-plugin-graphql/src'),
|
|
21
20
|
'grpc': require('../../../datadog-plugin-grpc/src'),
|
|
@@ -36,6 +36,9 @@ const TEST_IS_RUM_ACTIVE = 'test.is_rum_active'
|
|
|
36
36
|
const TEST_CODE_OWNERS = 'test.codeowners'
|
|
37
37
|
const TEST_SOURCE_FILE = 'test.source.file'
|
|
38
38
|
const LIBRARY_VERSION = 'library_version'
|
|
39
|
+
const TEST_COMMAND = 'test.command'
|
|
40
|
+
const TEST_SESSION_ID = 'test_session_id'
|
|
41
|
+
const TEST_SUITE_ID = 'test_suite_id'
|
|
39
42
|
|
|
40
43
|
const ERROR_TYPE = 'error.type'
|
|
41
44
|
const ERROR_MESSAGE = 'error.msg'
|
|
@@ -70,7 +73,12 @@ module.exports = {
|
|
|
70
73
|
getTestSuitePath,
|
|
71
74
|
getCodeOwnersFileEntries,
|
|
72
75
|
getCodeOwnersForFilename,
|
|
73
|
-
getTestCommonTags
|
|
76
|
+
getTestCommonTags,
|
|
77
|
+
getTestSessionCommonTags,
|
|
78
|
+
getTestSuiteCommonTags,
|
|
79
|
+
TEST_COMMAND,
|
|
80
|
+
TEST_SESSION_ID,
|
|
81
|
+
TEST_SUITE_ID
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
function getTestEnvironmentMetadata (testFramework, config) {
|
|
@@ -225,3 +233,26 @@ function getCodeOwnersForFilename (filename, entries) {
|
|
|
225
233
|
}
|
|
226
234
|
return null
|
|
227
235
|
}
|
|
236
|
+
|
|
237
|
+
function getTestSessionCommonTags (command, version) {
|
|
238
|
+
return {
|
|
239
|
+
[SPAN_TYPE]: 'test_session_end',
|
|
240
|
+
[TEST_TYPE]: 'test',
|
|
241
|
+
[RESOURCE_NAME]: `test_session.${command}`,
|
|
242
|
+
[TEST_FRAMEWORK_VERSION]: version,
|
|
243
|
+
[LIBRARY_VERSION]: ddTraceVersion,
|
|
244
|
+
[TEST_COMMAND]: command
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getTestSuiteCommonTags (command, version, testSuite) {
|
|
249
|
+
return {
|
|
250
|
+
[SPAN_TYPE]: 'test_suite_end',
|
|
251
|
+
[TEST_TYPE]: 'test',
|
|
252
|
+
[RESOURCE_NAME]: `test_suite.${testSuite}`,
|
|
253
|
+
[TEST_FRAMEWORK_VERSION]: version,
|
|
254
|
+
[LIBRARY_VERSION]: ddTraceVersion,
|
|
255
|
+
[TEST_SUITE]: testSuite,
|
|
256
|
+
[TEST_COMMAND]: command
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -43,13 +43,15 @@ const web = {
|
|
|
43
43
|
const hooks = getHooks(config)
|
|
44
44
|
const filter = urlFilter.getFilter(config)
|
|
45
45
|
const middleware = getMiddlewareSetting(config)
|
|
46
|
+
const queryStringObfuscation = getQsObfuscator(config)
|
|
46
47
|
|
|
47
48
|
return Object.assign({}, config, {
|
|
48
49
|
headers,
|
|
49
50
|
validateStatus,
|
|
50
51
|
hooks,
|
|
51
52
|
filter,
|
|
52
|
-
middleware
|
|
53
|
+
middleware,
|
|
54
|
+
queryStringObfuscation
|
|
53
55
|
})
|
|
54
56
|
},
|
|
55
57
|
|
|
@@ -259,9 +261,9 @@ const web = {
|
|
|
259
261
|
const context = contexts.get(req)
|
|
260
262
|
const span = context.span
|
|
261
263
|
const error = context.error
|
|
262
|
-
const
|
|
264
|
+
const hasExistingError = span.context()._tags['error'] || span.context()._tags['error.msg']
|
|
263
265
|
|
|
264
|
-
if (!
|
|
266
|
+
if (!hasExistingError && !context.config.validateStatus(statusCode)) {
|
|
265
267
|
span.setTag(ERROR, error || true)
|
|
266
268
|
}
|
|
267
269
|
},
|
|
@@ -270,7 +272,10 @@ const web = {
|
|
|
270
272
|
addError (req, error) {
|
|
271
273
|
if (error instanceof Error) {
|
|
272
274
|
const context = contexts.get(req)
|
|
273
|
-
|
|
275
|
+
|
|
276
|
+
if (context) {
|
|
277
|
+
context.error = error
|
|
278
|
+
}
|
|
274
279
|
}
|
|
275
280
|
},
|
|
276
281
|
|
|
@@ -298,6 +303,40 @@ const web = {
|
|
|
298
303
|
context.span.finish()
|
|
299
304
|
context.finished = true
|
|
300
305
|
},
|
|
306
|
+
|
|
307
|
+
finishAll (context) {
|
|
308
|
+
const { req, res } = context
|
|
309
|
+
|
|
310
|
+
for (const beforeEnd of context.beforeEnd) {
|
|
311
|
+
beforeEnd()
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
web.finishMiddleware(context)
|
|
315
|
+
|
|
316
|
+
if (incomingHttpRequestEnd.hasSubscribers) {
|
|
317
|
+
incomingHttpRequestEnd.publish({ req, res })
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
web.finishSpan(context)
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
obfuscateQs (config, url) {
|
|
324
|
+
const { queryStringObfuscation } = config
|
|
325
|
+
|
|
326
|
+
if (queryStringObfuscation === false) return url
|
|
327
|
+
|
|
328
|
+
const i = url.indexOf('?')
|
|
329
|
+
if (i === -1) return url
|
|
330
|
+
|
|
331
|
+
const path = url.slice(0, i)
|
|
332
|
+
if (queryStringObfuscation === true) return path
|
|
333
|
+
|
|
334
|
+
let qs = url.slice(i + 1)
|
|
335
|
+
|
|
336
|
+
qs = qs.replace(queryStringObfuscation, '<redacted>')
|
|
337
|
+
|
|
338
|
+
return `${path}?${qs}`
|
|
339
|
+
},
|
|
301
340
|
wrapWriteHead (context) {
|
|
302
341
|
const { req, res } = context
|
|
303
342
|
const writeHead = res.writeHead
|
|
@@ -318,21 +357,9 @@ const web = {
|
|
|
318
357
|
},
|
|
319
358
|
wrapRes (context, req, res, end) {
|
|
320
359
|
return function () {
|
|
321
|
-
|
|
322
|
-
beforeEnd()
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
web.finishMiddleware(context)
|
|
360
|
+
web.finishAll(context)
|
|
326
361
|
|
|
327
|
-
|
|
328
|
-
incomingHttpRequestEnd.publish({ req, res })
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const returnValue = end.apply(res, arguments)
|
|
332
|
-
|
|
333
|
-
web.finishSpan(context)
|
|
334
|
-
|
|
335
|
-
return returnValue
|
|
362
|
+
return end.apply(res, arguments)
|
|
336
363
|
}
|
|
337
364
|
},
|
|
338
365
|
wrapEnd (context) {
|
|
@@ -399,11 +426,11 @@ function reactivate (req, fn) {
|
|
|
399
426
|
}
|
|
400
427
|
|
|
401
428
|
function addRequestTags (context) {
|
|
402
|
-
const { req, span } = context
|
|
429
|
+
const { req, span, config } = context
|
|
403
430
|
const url = extractURL(req)
|
|
404
431
|
|
|
405
432
|
span.addTags({
|
|
406
|
-
[HTTP_URL]:
|
|
433
|
+
[HTTP_URL]: web.obfuscateQs(config, url),
|
|
407
434
|
[HTTP_METHOD]: req.method,
|
|
408
435
|
[SPAN_KIND]: SERVER,
|
|
409
436
|
[SPAN_TYPE]: WEB,
|
|
@@ -514,4 +541,30 @@ function getMiddlewareSetting (config) {
|
|
|
514
541
|
return true
|
|
515
542
|
}
|
|
516
543
|
|
|
544
|
+
function getQsObfuscator (config) {
|
|
545
|
+
const obfuscator = config.queryStringObfuscation
|
|
546
|
+
|
|
547
|
+
if (typeof obfuscator === 'boolean') {
|
|
548
|
+
return obfuscator
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (typeof obfuscator === 'string') {
|
|
552
|
+
if (obfuscator === '') return false // disable obfuscator
|
|
553
|
+
|
|
554
|
+
if (obfuscator === '.*') return true // optimize full redact
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
return new RegExp(obfuscator, 'gi')
|
|
558
|
+
} catch (err) {
|
|
559
|
+
log.error(err)
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (config.hasOwnProperty('queryStringObfuscation')) {
|
|
564
|
+
log.error('Expected `queryStringObfuscation` to be a regex string or boolean.')
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return true
|
|
568
|
+
}
|
|
569
|
+
|
|
517
570
|
module.exports = web
|