dd-trace 5.15.0 → 5.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +7 -0
- package/package.json +1 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/http/server.js +98 -0
- package/packages/datadog-instrumentations/src/undici.js +18 -0
- package/packages/datadog-plugin-graphql/src/resolve.js +4 -0
- package/packages/datadog-plugin-undici/src/index.js +12 -0
- package/packages/dd-trace/src/appsec/blocking.js +4 -0
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +44 -10
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
- package/packages/dd-trace/src/config.js +40 -8
- package/packages/dd-trace/src/constants.js +2 -0
- package/packages/dd-trace/src/format.js +21 -2
- package/packages/dd-trace/src/opentelemetry/span.js +41 -7
- package/packages/dd-trace/src/opentracing/span.js +38 -0
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/priority_sampler.js +8 -4
- package/packages/dd-trace/src/sampling_rule.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
- package/packages/dd-trace/src/tagger.js +10 -1
package/index.d.ts
CHANGED
|
@@ -197,6 +197,7 @@ interface Plugins {
|
|
|
197
197
|
"selenium": tracer.plugins.selenium;
|
|
198
198
|
"sharedb": tracer.plugins.sharedb;
|
|
199
199
|
"tedious": tracer.plugins.tedious;
|
|
200
|
+
"undici": tracer.plugins.undici;
|
|
200
201
|
"winston": tracer.plugins.winston;
|
|
201
202
|
}
|
|
202
203
|
|
|
@@ -1800,6 +1801,12 @@ declare namespace tracer {
|
|
|
1800
1801
|
*/
|
|
1801
1802
|
interface tedious extends Instrumentation {}
|
|
1802
1803
|
|
|
1804
|
+
/**
|
|
1805
|
+
* This plugin automatically instruments the
|
|
1806
|
+
* [undici](https://github.com/nodejs/undici) module.
|
|
1807
|
+
*/
|
|
1808
|
+
interface undici extends HttpClient {}
|
|
1809
|
+
|
|
1803
1810
|
/**
|
|
1804
1811
|
* This plugin patches the [winston](https://github.com/winstonjs/winston)
|
|
1805
1812
|
* to automatically inject trace identifiers in log records when the
|
package/package.json
CHANGED
|
@@ -109,6 +109,7 @@ module.exports = {
|
|
|
109
109
|
sequelize: () => require('../sequelize'),
|
|
110
110
|
sharedb: () => require('../sharedb'),
|
|
111
111
|
tedious: () => require('../tedious'),
|
|
112
|
+
undici: () => require('../undici'),
|
|
112
113
|
when: () => require('../when'),
|
|
113
114
|
winston: () => require('../winston')
|
|
114
115
|
}
|
|
@@ -10,6 +10,7 @@ const startServerCh = channel('apm:http:server:request:start')
|
|
|
10
10
|
const exitServerCh = channel('apm:http:server:request:exit')
|
|
11
11
|
const errorServerCh = channel('apm:http:server:request:error')
|
|
12
12
|
const finishServerCh = channel('apm:http:server:request:finish')
|
|
13
|
+
const startWriteHeadCh = channel('apm:http:server:response:writeHead:start')
|
|
13
14
|
const finishSetHeaderCh = channel('datadog:http:server:response:set-header:finish')
|
|
14
15
|
|
|
15
16
|
const requestFinishedSet = new WeakSet()
|
|
@@ -20,6 +21,9 @@ const httpsNames = ['https', 'node:https']
|
|
|
20
21
|
addHook({ name: httpNames }, http => {
|
|
21
22
|
shimmer.wrap(http.ServerResponse.prototype, 'emit', wrapResponseEmit)
|
|
22
23
|
shimmer.wrap(http.Server.prototype, 'emit', wrapEmit)
|
|
24
|
+
shimmer.wrap(http.ServerResponse.prototype, 'writeHead', wrapWriteHead)
|
|
25
|
+
shimmer.wrap(http.ServerResponse.prototype, 'write', wrapWrite)
|
|
26
|
+
shimmer.wrap(http.ServerResponse.prototype, 'end', wrapEnd)
|
|
23
27
|
return http
|
|
24
28
|
})
|
|
25
29
|
|
|
@@ -86,3 +90,97 @@ function wrapSetHeader (res) {
|
|
|
86
90
|
}
|
|
87
91
|
})
|
|
88
92
|
}
|
|
93
|
+
|
|
94
|
+
function wrapWriteHead (writeHead) {
|
|
95
|
+
return function wrappedWriteHead (statusCode, reason, obj) {
|
|
96
|
+
if (!startWriteHeadCh.hasSubscribers) {
|
|
97
|
+
return writeHead.apply(this, arguments)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const abortController = new AbortController()
|
|
101
|
+
|
|
102
|
+
if (typeof reason !== 'string') {
|
|
103
|
+
obj ??= reason
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// support writeHead(200, ['key1', 'val1', 'key2', 'val2'])
|
|
107
|
+
if (Array.isArray(obj)) {
|
|
108
|
+
const headers = {}
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < obj.length; i += 2) {
|
|
111
|
+
headers[obj[i]] = obj[i + 1]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
obj = headers
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// this doesn't support explicit duplicate headers, but it's an edge case
|
|
118
|
+
const responseHeaders = Object.assign(this.getHeaders(), obj)
|
|
119
|
+
|
|
120
|
+
startWriteHeadCh.publish({
|
|
121
|
+
req: this.req,
|
|
122
|
+
res: this,
|
|
123
|
+
abortController,
|
|
124
|
+
statusCode,
|
|
125
|
+
responseHeaders
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
if (abortController.signal.aborted) {
|
|
129
|
+
return this
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return writeHead.apply(this, arguments)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function wrapWrite (write) {
|
|
137
|
+
return function wrappedWrite () {
|
|
138
|
+
if (!startWriteHeadCh.hasSubscribers) {
|
|
139
|
+
return write.apply(this, arguments)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const abortController = new AbortController()
|
|
143
|
+
|
|
144
|
+
const responseHeaders = this.getHeaders()
|
|
145
|
+
|
|
146
|
+
startWriteHeadCh.publish({
|
|
147
|
+
req: this.req,
|
|
148
|
+
res: this,
|
|
149
|
+
abortController,
|
|
150
|
+
statusCode: this.statusCode,
|
|
151
|
+
responseHeaders
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
if (abortController.signal.aborted) {
|
|
155
|
+
return true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return write.apply(this, arguments)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function wrapEnd (end) {
|
|
163
|
+
return function wrappedEnd () {
|
|
164
|
+
if (!startWriteHeadCh.hasSubscribers) {
|
|
165
|
+
return end.apply(this, arguments)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const abortController = new AbortController()
|
|
169
|
+
|
|
170
|
+
const responseHeaders = this.getHeaders()
|
|
171
|
+
|
|
172
|
+
startWriteHeadCh.publish({
|
|
173
|
+
req: this.req,
|
|
174
|
+
res: this,
|
|
175
|
+
abortController,
|
|
176
|
+
statusCode: this.statusCode,
|
|
177
|
+
responseHeaders
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
if (abortController.signal.aborted) {
|
|
181
|
+
return this
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return end.apply(this, arguments)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
addHook
|
|
5
|
+
} = require('./helpers/instrument')
|
|
6
|
+
const shimmer = require('../../datadog-shimmer')
|
|
7
|
+
|
|
8
|
+
const tracingChannel = require('dc-polyfill').tracingChannel
|
|
9
|
+
const ch = tracingChannel('apm:undici:fetch')
|
|
10
|
+
|
|
11
|
+
const { createWrapFetch } = require('./helpers/fetch')
|
|
12
|
+
|
|
13
|
+
addHook({
|
|
14
|
+
name: 'undici',
|
|
15
|
+
versions: ['^4.4.1', '5', '>=6.0.0']
|
|
16
|
+
}, undici => {
|
|
17
|
+
return shimmer.wrap(undici, 'fetch', createWrapFetch(undici.Request, ch))
|
|
18
|
+
})
|
|
@@ -80,6 +80,10 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
80
80
|
// this will disable resolve subscribers if `config.depth` is set to 0
|
|
81
81
|
super.configure(config.depth === 0 ? false : config)
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
finish (finishTime) {
|
|
85
|
+
this.activeSpan.finish(finishTime)
|
|
86
|
+
}
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
// helpers
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const FetchPlugin = require('../../datadog-plugin-fetch/src/index.js')
|
|
4
|
+
|
|
5
|
+
class UndiciPlugin extends FetchPlugin {
|
|
6
|
+
static get id () { return 'undici' }
|
|
7
|
+
static get prefix () {
|
|
8
|
+
return 'tracing:apm:undici:fetch'
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = UndiciPlugin
|
|
@@ -111,6 +111,10 @@ function block (req, res, rootSpan, abortController, actionParameters) {
|
|
|
111
111
|
|
|
112
112
|
const { body, headers, statusCode } = getBlockingData(req, null, rootSpan, actionParameters)
|
|
113
113
|
|
|
114
|
+
for (const headerName of res.getHeaderNames()) {
|
|
115
|
+
res.removeHeader(headerName)
|
|
116
|
+
}
|
|
117
|
+
|
|
114
118
|
res.writeHead(statusCode, headers).end(body)
|
|
115
119
|
|
|
116
120
|
abortController?.abort()
|
|
@@ -18,5 +18,6 @@ module.exports = {
|
|
|
18
18
|
nextBodyParsed: dc.channel('apm:next:body-parsed'),
|
|
19
19
|
nextQueryParsed: dc.channel('apm:next:query-parsed'),
|
|
20
20
|
responseBody: dc.channel('datadog:express:response:json:start'),
|
|
21
|
+
responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'),
|
|
21
22
|
httpClientRequestStart: dc.channel('apm:http:client:request:start')
|
|
22
23
|
}
|
|
@@ -12,7 +12,8 @@ const {
|
|
|
12
12
|
queryParser,
|
|
13
13
|
nextBodyParsed,
|
|
14
14
|
nextQueryParsed,
|
|
15
|
-
responseBody
|
|
15
|
+
responseBody,
|
|
16
|
+
responseWriteHead
|
|
16
17
|
} = require('./channels')
|
|
17
18
|
const waf = require('./waf')
|
|
18
19
|
const addresses = require('./addresses')
|
|
@@ -60,6 +61,7 @@ function enable (_config) {
|
|
|
60
61
|
queryParser.subscribe(onRequestQueryParsed)
|
|
61
62
|
cookieParser.subscribe(onRequestCookieParser)
|
|
62
63
|
responseBody.subscribe(onResponseBody)
|
|
64
|
+
responseWriteHead.subscribe(onResponseWriteHead)
|
|
63
65
|
|
|
64
66
|
if (_config.appsec.eventTracking.enabled) {
|
|
65
67
|
passportVerify.subscribe(onPassportVerify)
|
|
@@ -110,14 +112,7 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
function incomingHttpEndTranslator ({ req, res }) {
|
|
113
|
-
|
|
114
|
-
const responseHeaders = Object.assign({}, res.getHeaders())
|
|
115
|
-
delete responseHeaders['set-cookie']
|
|
116
|
-
|
|
117
|
-
const persistent = {
|
|
118
|
-
[addresses.HTTP_INCOMING_RESPONSE_CODE]: '' + res.statusCode,
|
|
119
|
-
[addresses.HTTP_INCOMING_RESPONSE_HEADERS]: responseHeaders
|
|
120
|
-
}
|
|
115
|
+
const persistent = {}
|
|
121
116
|
|
|
122
117
|
// we need to keep this to support other body parsers
|
|
123
118
|
// TODO: no need to analyze it if it was already done by the body-parser hook
|
|
@@ -139,7 +134,9 @@ function incomingHttpEndTranslator ({ req, res }) {
|
|
|
139
134
|
persistent[addresses.HTTP_INCOMING_QUERY] = req.query
|
|
140
135
|
}
|
|
141
136
|
|
|
142
|
-
|
|
137
|
+
if (Object.keys(persistent).length) {
|
|
138
|
+
waf.run({ persistent }, req)
|
|
139
|
+
}
|
|
143
140
|
|
|
144
141
|
waf.disposeContext(req)
|
|
145
142
|
|
|
@@ -225,12 +222,48 @@ function onPassportVerify ({ credentials, user }) {
|
|
|
225
222
|
passportTrackEvent(credentials, user, rootSpan, config.appsec.eventTracking.mode)
|
|
226
223
|
}
|
|
227
224
|
|
|
225
|
+
const responseAnalyzedSet = new WeakSet()
|
|
226
|
+
const responseBlockedSet = new WeakSet()
|
|
227
|
+
|
|
228
|
+
function onResponseWriteHead ({ req, res, abortController, statusCode, responseHeaders }) {
|
|
229
|
+
// avoid "write after end" error
|
|
230
|
+
if (responseBlockedSet.has(res)) {
|
|
231
|
+
abortController?.abort()
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// avoid double waf call
|
|
236
|
+
if (responseAnalyzedSet.has(res)) {
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const rootSpan = web.root(req)
|
|
241
|
+
if (!rootSpan) return
|
|
242
|
+
|
|
243
|
+
responseHeaders = Object.assign({}, responseHeaders)
|
|
244
|
+
delete responseHeaders['set-cookie']
|
|
245
|
+
|
|
246
|
+
const results = waf.run({
|
|
247
|
+
persistent: {
|
|
248
|
+
[addresses.HTTP_INCOMING_RESPONSE_CODE]: '' + statusCode,
|
|
249
|
+
[addresses.HTTP_INCOMING_RESPONSE_HEADERS]: responseHeaders
|
|
250
|
+
}
|
|
251
|
+
}, req)
|
|
252
|
+
|
|
253
|
+
responseAnalyzedSet.add(res)
|
|
254
|
+
|
|
255
|
+
handleResults(results, req, res, rootSpan, abortController)
|
|
256
|
+
}
|
|
257
|
+
|
|
228
258
|
function handleResults (actions, req, res, rootSpan, abortController) {
|
|
229
259
|
if (!actions || !req || !res || !rootSpan || !abortController) return
|
|
230
260
|
|
|
231
261
|
const blockingAction = getBlockingAction(actions)
|
|
232
262
|
if (blockingAction) {
|
|
233
263
|
block(req, res, rootSpan, abortController, blockingAction)
|
|
264
|
+
if (!abortController.signal || abortController.signal.aborted) {
|
|
265
|
+
responseBlockedSet.add(res)
|
|
266
|
+
}
|
|
234
267
|
}
|
|
235
268
|
}
|
|
236
269
|
|
|
@@ -256,6 +289,7 @@ function disable () {
|
|
|
256
289
|
if (cookieParser.hasSubscribers) cookieParser.unsubscribe(onRequestCookieParser)
|
|
257
290
|
if (responseBody.hasSubscribers) responseBody.unsubscribe(onResponseBody)
|
|
258
291
|
if (passportVerify.hasSubscribers) passportVerify.unsubscribe(onPassportVerify)
|
|
292
|
+
if (responseWriteHead.hasSubscribers) responseWriteHead.unsubscribe(onResponseWriteHead)
|
|
259
293
|
}
|
|
260
294
|
|
|
261
295
|
module.exports = {
|
|
@@ -71,6 +71,7 @@ function enableWafUpdate (appsecConfig) {
|
|
|
71
71
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_DD_RULES, true)
|
|
72
72
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_EXCLUSIONS, true)
|
|
73
73
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_REQUEST_BLOCKING, true)
|
|
74
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RESPONSE_BLOCKING, true)
|
|
74
75
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_RULES, true)
|
|
75
76
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_BLOCKING_RESPONSE, true)
|
|
76
77
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRUSTED_IPS, true)
|
|
@@ -92,6 +93,7 @@ function disableWafUpdate () {
|
|
|
92
93
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_DD_RULES, false)
|
|
93
94
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_EXCLUSIONS, false)
|
|
94
95
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_REQUEST_BLOCKING, false)
|
|
96
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RESPONSE_BLOCKING, false)
|
|
95
97
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_RULES, false)
|
|
96
98
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_BLOCKING_RESPONSE, false)
|
|
97
99
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRUSTED_IPS, false)
|
|
@@ -607,6 +607,7 @@ class Config {
|
|
|
607
607
|
|
|
608
608
|
const tags = {}
|
|
609
609
|
const env = this._env = {}
|
|
610
|
+
this._envUnprocessed = {}
|
|
610
611
|
|
|
611
612
|
tagger.add(tags, OTEL_RESOURCE_ATTRIBUTES, true)
|
|
612
613
|
tagger.add(tags, DD_TAGS)
|
|
@@ -614,16 +615,20 @@ class Config {
|
|
|
614
615
|
tagger.add(tags, DD_TRACE_GLOBAL_TAGS)
|
|
615
616
|
|
|
616
617
|
this._setValue(env, 'appsec.blockedTemplateHtml', maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML))
|
|
618
|
+
this._envUnprocessed['appsec.blockedTemplateHtml'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML
|
|
617
619
|
this._setValue(env, 'appsec.blockedTemplateJson', maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON))
|
|
620
|
+
this._envUnprocessed['appsec.blockedTemplateJson'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON
|
|
618
621
|
this._setBoolean(env, 'appsec.enabled', DD_APPSEC_ENABLED)
|
|
619
622
|
this._setString(env, 'appsec.obfuscatorKeyRegex', DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP)
|
|
620
623
|
this._setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP)
|
|
621
624
|
this._setBoolean(env, 'appsec.rasp.enabled', DD_APPSEC_RASP_ENABLED)
|
|
622
625
|
this._setValue(env, 'appsec.rateLimit', maybeInt(DD_APPSEC_TRACE_RATE_LIMIT))
|
|
626
|
+
this._envUnprocessed['appsec.rateLimit'] = DD_APPSEC_TRACE_RATE_LIMIT
|
|
623
627
|
this._setString(env, 'appsec.rules', DD_APPSEC_RULES)
|
|
624
628
|
// DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend
|
|
625
629
|
this._setBoolean(env, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
|
|
626
630
|
this._setValue(env, 'appsec.wafTimeout', maybeInt(DD_APPSEC_WAF_TIMEOUT))
|
|
631
|
+
this._envUnprocessed['appsec.wafTimeout'] = DD_APPSEC_WAF_TIMEOUT
|
|
627
632
|
this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
|
|
628
633
|
this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER)
|
|
629
634
|
this._setString(env, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE)
|
|
@@ -636,13 +641,16 @@ class Config {
|
|
|
636
641
|
this._setBoolean(env, 'experimental.runtimeId', DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED)
|
|
637
642
|
if (AWS_LAMBDA_FUNCTION_NAME) this._setValue(env, 'flushInterval', 0)
|
|
638
643
|
this._setValue(env, 'flushMinSpans', maybeInt(DD_TRACE_PARTIAL_FLUSH_MIN_SPANS))
|
|
644
|
+
this._envUnprocessed.flushMinSpans = DD_TRACE_PARTIAL_FLUSH_MIN_SPANS
|
|
639
645
|
this._setBoolean(env, 'gitMetadataEnabled', DD_TRACE_GIT_METADATA_ENABLED)
|
|
640
646
|
this._setArray(env, 'headerTags', DD_TRACE_HEADER_TAGS)
|
|
641
647
|
this._setString(env, 'hostname', coalesce(DD_AGENT_HOST, DD_TRACE_AGENT_HOSTNAME))
|
|
642
648
|
this._setBoolean(env, 'iast.deduplicationEnabled', DD_IAST_DEDUPLICATION_ENABLED)
|
|
643
649
|
this._setBoolean(env, 'iast.enabled', DD_IAST_ENABLED)
|
|
644
650
|
this._setValue(env, 'iast.maxConcurrentRequests', maybeInt(DD_IAST_MAX_CONCURRENT_REQUESTS))
|
|
651
|
+
this._envUnprocessed['iast.maxConcurrentRequests'] = DD_IAST_MAX_CONCURRENT_REQUESTS
|
|
645
652
|
this._setValue(env, 'iast.maxContextOperations', maybeInt(DD_IAST_MAX_CONTEXT_OPERATIONS))
|
|
653
|
+
this._envUnprocessed['iast.maxContextOperations'] = DD_IAST_MAX_CONTEXT_OPERATIONS
|
|
646
654
|
this._setBoolean(env, 'iast.redactionEnabled', DD_IAST_REDACTION_ENABLED && !isFalse(DD_IAST_REDACTION_ENABLED))
|
|
647
655
|
this._setString(env, 'iast.redactionNamePattern', DD_IAST_REDACTION_NAME_PATTERN)
|
|
648
656
|
this._setString(env, 'iast.redactionValuePattern', DD_IAST_REDACTION_VALUE_PATTERN)
|
|
@@ -650,15 +658,18 @@ class Config {
|
|
|
650
658
|
if (iastRequestSampling > -1 && iastRequestSampling < 101) {
|
|
651
659
|
this._setValue(env, 'iast.requestSampling', iastRequestSampling)
|
|
652
660
|
}
|
|
661
|
+
this._envUnprocessed['iast.requestSampling'] = DD_IAST_REQUEST_SAMPLING
|
|
653
662
|
this._setString(env, 'iast.telemetryVerbosity', DD_IAST_TELEMETRY_VERBOSITY)
|
|
654
663
|
this._setBoolean(env, 'isGCPFunction', getIsGCPFunction())
|
|
655
664
|
this._setBoolean(env, 'logInjection', DD_LOGS_INJECTION)
|
|
656
665
|
this._setBoolean(env, 'openAiLogsEnabled', DD_OPENAI_LOGS_ENABLED)
|
|
657
666
|
this._setValue(env, 'openaiSpanCharLimit', maybeInt(DD_OPENAI_SPAN_CHAR_LIMIT))
|
|
667
|
+
this._envUnprocessed.openaiSpanCharLimit = DD_OPENAI_SPAN_CHAR_LIMIT
|
|
658
668
|
if (DD_TRACE_PEER_SERVICE_MAPPING) {
|
|
659
669
|
this._setValue(env, 'peerServiceMapping', fromEntries(
|
|
660
|
-
|
|
670
|
+
DD_TRACE_PEER_SERVICE_MAPPING.split(',').map(x => x.trim().split(':'))
|
|
661
671
|
))
|
|
672
|
+
this._envUnprocessed.peerServiceMapping = DD_TRACE_PEER_SERVICE_MAPPING
|
|
662
673
|
}
|
|
663
674
|
this._setString(env, 'port', DD_TRACE_AGENT_PORT)
|
|
664
675
|
this._setBoolean(env, 'profiling.enabled', coalesce(DD_EXPERIMENTAL_PROFILING_ENABLED, DD_PROFILING_ENABLED))
|
|
@@ -682,6 +693,7 @@ class Config {
|
|
|
682
693
|
!this._isInServerlessEnvironment()
|
|
683
694
|
))
|
|
684
695
|
this._setValue(env, 'remoteConfig.pollInterval', maybeFloat(DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS))
|
|
696
|
+
this._envUnprocessed['remoteConfig.pollInterval'] = DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS
|
|
685
697
|
this._setBoolean(env, 'reportHostname', DD_TRACE_REPORT_HOSTNAME)
|
|
686
698
|
// only used to explicitly set runtimeMetrics to false
|
|
687
699
|
const otelSetRuntimeMetrics = String(OTEL_METRICS_EXPORTER).toLowerCase() === 'none'
|
|
@@ -699,12 +711,14 @@ class Config {
|
|
|
699
711
|
}
|
|
700
712
|
this._setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE || OTEL_TRACES_SAMPLER_MAPPING[OTEL_TRACES_SAMPLER])
|
|
701
713
|
this._setValue(env, 'sampler.rateLimit', DD_TRACE_RATE_LIMIT)
|
|
702
|
-
this._setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES))
|
|
714
|
+
this._setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES))
|
|
715
|
+
this._envUnprocessed['sampler.rules'] = DD_TRACE_SAMPLING_RULES
|
|
703
716
|
this._setString(env, 'scope', DD_TRACE_SCOPE)
|
|
704
717
|
this._setString(env, 'service', DD_SERVICE || DD_SERVICE_NAME || tags.service || OTEL_SERVICE_NAME)
|
|
705
718
|
this._setString(env, 'site', DD_SITE)
|
|
706
719
|
if (DD_TRACE_SPAN_ATTRIBUTE_SCHEMA) {
|
|
707
720
|
this._setString(env, 'spanAttributeSchema', validateNamingVersion(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA))
|
|
721
|
+
this._envUnprocessed.spanAttributeSchema = DD_TRACE_SPAN_ATTRIBUTE_SCHEMA
|
|
708
722
|
}
|
|
709
723
|
this._setBoolean(env, 'spanRemoveIntegrationFromService', DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED)
|
|
710
724
|
this._setBoolean(env, 'startupLogs', DD_TRACE_STARTUP_LOGS)
|
|
@@ -719,6 +733,7 @@ class Config {
|
|
|
719
733
|
this._setBoolean(env, 'telemetry.debug', DD_TELEMETRY_DEBUG)
|
|
720
734
|
this._setBoolean(env, 'telemetry.dependencyCollection', DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED)
|
|
721
735
|
this._setValue(env, 'telemetry.heartbeatInterval', maybeInt(Math.floor(DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000)))
|
|
736
|
+
this._envUnprocessed['telemetry.heartbeatInterval'] = DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000
|
|
722
737
|
const hasTelemetryLogsUsingFeatures =
|
|
723
738
|
env['iast.enabled'] || env['profiling.enabled'] || env['profiling.heuristicsEnabled']
|
|
724
739
|
? true
|
|
@@ -735,20 +750,25 @@ class Config {
|
|
|
735
750
|
_applyOptions (options) {
|
|
736
751
|
const opts = this._options = this._options || {}
|
|
737
752
|
const tags = {}
|
|
753
|
+
this._optsUnprocessed = {}
|
|
738
754
|
|
|
739
755
|
options = this.options = Object.assign({ ingestion: {} }, options, opts)
|
|
740
756
|
|
|
741
757
|
tagger.add(tags, options.tags)
|
|
742
758
|
|
|
743
759
|
this._setValue(opts, 'appsec.blockedTemplateHtml', maybeFile(options.appsec.blockedTemplateHtml))
|
|
760
|
+
this._optsUnprocessed['appsec.blockedTemplateHtml'] = options.appsec.blockedTemplateHtml
|
|
744
761
|
this._setValue(opts, 'appsec.blockedTemplateJson', maybeFile(options.appsec.blockedTemplateJson))
|
|
762
|
+
this._optsUnprocessed['appsec.blockedTemplateJson'] = options.appsec.blockedTemplateJson
|
|
745
763
|
this._setBoolean(opts, 'appsec.enabled', options.appsec.enabled)
|
|
746
764
|
this._setString(opts, 'appsec.obfuscatorKeyRegex', options.appsec.obfuscatorKeyRegex)
|
|
747
765
|
this._setString(opts, 'appsec.obfuscatorValueRegex', options.appsec.obfuscatorValueRegex)
|
|
748
766
|
this._setBoolean(opts, 'appsec.rasp.enabled', options.appsec.rasp?.enabled)
|
|
749
767
|
this._setValue(opts, 'appsec.rateLimit', maybeInt(options.appsec.rateLimit))
|
|
768
|
+
this._optsUnprocessed['appsec.rateLimit'] = options.appsec.rateLimit
|
|
750
769
|
this._setString(opts, 'appsec.rules', options.appsec.rules)
|
|
751
770
|
this._setValue(opts, 'appsec.wafTimeout', maybeInt(options.appsec.wafTimeout))
|
|
771
|
+
this._optsUnprocessed['appsec.wafTimeout'] = options.appsec.wafTimeout
|
|
752
772
|
this._setBoolean(opts, 'clientIpEnabled', options.clientIpEnabled)
|
|
753
773
|
this._setString(opts, 'clientIpHeader', options.clientIpHeader)
|
|
754
774
|
this._setString(opts, 'dbmPropagationMode', options.dbmPropagationMode)
|
|
@@ -763,22 +783,26 @@ class Config {
|
|
|
763
783
|
this._setString(opts, 'experimental.exporter', options.experimental && options.experimental.exporter)
|
|
764
784
|
this._setBoolean(opts, 'experimental.runtimeId', options.experimental && options.experimental.runtimeId)
|
|
765
785
|
this._setValue(opts, 'flushInterval', maybeInt(options.flushInterval))
|
|
786
|
+
this._optsUnprocessed.flushInterval = options.flushInterval
|
|
766
787
|
this._setValue(opts, 'flushMinSpans', maybeInt(options.flushMinSpans))
|
|
788
|
+
this._optsUnprocessed.flushMinSpans = options.flushMinSpans
|
|
767
789
|
this._setArray(opts, 'headerTags', options.headerTags)
|
|
768
790
|
this._setString(opts, 'hostname', options.hostname)
|
|
769
791
|
this._setBoolean(opts, 'iast.deduplicationEnabled', options.iastOptions && options.iastOptions.deduplicationEnabled)
|
|
770
792
|
this._setBoolean(opts, 'iast.enabled',
|
|
771
793
|
options.iastOptions && (options.iastOptions === true || options.iastOptions.enabled === true))
|
|
772
|
-
const iastRequestSampling = maybeInt(options.iastOptions?.requestSampling)
|
|
773
794
|
this._setValue(opts, 'iast.maxConcurrentRequests',
|
|
774
795
|
maybeInt(options.iastOptions?.maxConcurrentRequests))
|
|
775
|
-
this.
|
|
776
|
-
|
|
777
|
-
this.
|
|
796
|
+
this._optsUnprocessed['iast.maxConcurrentRequests'] = options.iastOptions?.maxConcurrentRequests
|
|
797
|
+
this._setValue(opts, 'iast.maxContextOperations', maybeInt(options.iastOptions?.maxContextOperations))
|
|
798
|
+
this._optsUnprocessed['iast.maxContextOperations'] = options.iastOptions?.maxContextOperations
|
|
799
|
+
this._setBoolean(opts, 'iast.redactionEnabled', options.iastOptions?.redactionEnabled)
|
|
778
800
|
this._setString(opts, 'iast.redactionNamePattern', options.iastOptions?.redactionNamePattern)
|
|
779
801
|
this._setString(opts, 'iast.redactionValuePattern', options.iastOptions?.redactionValuePattern)
|
|
802
|
+
const iastRequestSampling = maybeInt(options.iastOptions?.requestSampling)
|
|
780
803
|
if (iastRequestSampling > -1 && iastRequestSampling < 101) {
|
|
781
804
|
this._setValue(opts, 'iast.requestSampling', iastRequestSampling)
|
|
805
|
+
this._optsUnprocessed['iast.requestSampling'] = options.iastOptions?.requestSampling
|
|
782
806
|
}
|
|
783
807
|
this._setString(opts, 'iast.telemetryVerbosity', options.iastOptions && options.iastOptions.telemetryVerbosity)
|
|
784
808
|
this._setBoolean(opts, 'isCiVisibility', options.isCiVisibility)
|
|
@@ -792,6 +816,7 @@ class Config {
|
|
|
792
816
|
this._setString(opts, 'protocolVersion', options.protocolVersion)
|
|
793
817
|
if (options.remoteConfig) {
|
|
794
818
|
this._setValue(opts, 'remoteConfig.pollInterval', maybeFloat(options.remoteConfig.pollInterval))
|
|
819
|
+
this._optsUnprocessed['remoteConfig.pollInterval'] = options.remoteConfig.pollInterval
|
|
795
820
|
}
|
|
796
821
|
this._setBoolean(opts, 'reportHostname', options.reportHostname)
|
|
797
822
|
this._setBoolean(opts, 'runtimeMetrics', options.runtimeMetrics)
|
|
@@ -803,6 +828,7 @@ class Config {
|
|
|
803
828
|
this._setString(opts, 'site', options.site)
|
|
804
829
|
if (options.spanAttributeSchema) {
|
|
805
830
|
this._setString(opts, 'spanAttributeSchema', validateNamingVersion(options.spanAttributeSchema))
|
|
831
|
+
this._optsUnprocessed.spanAttributeSchema = options.spanAttributeSchema
|
|
806
832
|
}
|
|
807
833
|
this._setBoolean(opts, 'spanRemoveIntegrationFromService', options.spanRemoveIntegrationFromService)
|
|
808
834
|
this._setBoolean(opts, 'startupLogs', options.startupLogs)
|
|
@@ -931,6 +957,7 @@ class Config {
|
|
|
931
957
|
|
|
932
958
|
_applyRemote (options) {
|
|
933
959
|
const opts = this._remote = this._remote || {}
|
|
960
|
+
this._remoteUnprocessed = {}
|
|
934
961
|
const tags = {}
|
|
935
962
|
const headerTags = options.tracing_header_tags
|
|
936
963
|
? options.tracing_header_tags.map(tag => {
|
|
@@ -947,7 +974,8 @@ class Config {
|
|
|
947
974
|
this._setTags(opts, 'tags', tags)
|
|
948
975
|
this._setBoolean(opts, 'tracing', options.tracing_enabled)
|
|
949
976
|
// ignore tags for now since rc sampling rule tags format is not supported
|
|
950
|
-
this._setSamplingRule(opts, 'sampler.rules', this._ignoreTags(options.
|
|
977
|
+
this._setSamplingRule(opts, 'sampler.rules', this._ignoreTags(options.tracing_sampling_rules))
|
|
978
|
+
this._remoteUnprocessed['sampler.rules'] = options.tracing_sampling_rules
|
|
951
979
|
}
|
|
952
980
|
|
|
953
981
|
_ignoreTags (samplingRules) {
|
|
@@ -1040,18 +1068,21 @@ class Config {
|
|
|
1040
1068
|
_merge () {
|
|
1041
1069
|
const containers = [this._remote, this._options, this._env, this._calculated, this._defaults]
|
|
1042
1070
|
const origins = ['remote_config', 'code', 'env_var', 'calculated', 'default']
|
|
1071
|
+
const unprocessedValues = [this._remoteUnprocessed, this._optsUnprocessed, this._envUnprocessed, {}, {}]
|
|
1043
1072
|
const changes = []
|
|
1044
1073
|
|
|
1045
1074
|
for (const name in this._defaults) {
|
|
1046
1075
|
for (let i = 0; i < containers.length; i++) {
|
|
1047
1076
|
const container = containers[i]
|
|
1048
1077
|
const origin = origins[i]
|
|
1078
|
+
const unprocessed = unprocessedValues[i]
|
|
1049
1079
|
|
|
1050
1080
|
if ((container[name] !== null && container[name] !== undefined) || container === this._defaults) {
|
|
1051
1081
|
if (get(this, name) === container[name] && has(this, name)) break
|
|
1052
1082
|
|
|
1053
|
-
|
|
1083
|
+
let value = container[name]
|
|
1054
1084
|
set(this, name, value)
|
|
1085
|
+
value = unprocessed[name] || value
|
|
1055
1086
|
|
|
1056
1087
|
changes.push({ name, value, origin })
|
|
1057
1088
|
|
|
@@ -1069,6 +1100,7 @@ function maybeInt (number) {
|
|
|
1069
1100
|
const parsed = parseInt(number)
|
|
1070
1101
|
return isNaN(parsed) ? undefined : parsed
|
|
1071
1102
|
}
|
|
1103
|
+
|
|
1072
1104
|
function maybeFloat (number) {
|
|
1073
1105
|
const parsed = parseFloat(number)
|
|
1074
1106
|
return isNaN(parsed) ? undefined : parsed
|
|
@@ -15,6 +15,8 @@ module.exports = {
|
|
|
15
15
|
SAMPLING_MECHANISM_MANUAL: 4,
|
|
16
16
|
SAMPLING_MECHANISM_APPSEC: 5,
|
|
17
17
|
SAMPLING_MECHANISM_SPAN: 8,
|
|
18
|
+
SAMPLING_MECHANISM_REMOTE_USER: 11,
|
|
19
|
+
SAMPLING_MECHANISM_REMOTE_DYNAMIC: 12,
|
|
18
20
|
SPAN_SAMPLING_MECHANISM: '_dd.span_sampling.mechanism',
|
|
19
21
|
SPAN_SAMPLING_RULE_RATE: '_dd.span_sampling.rule_rate',
|
|
20
22
|
SPAN_SAMPLING_MAX_PER_SECOND: '_dd.span_sampling.max_per_second',
|
|
@@ -34,6 +34,7 @@ function format (span) {
|
|
|
34
34
|
const formatted = formatSpan(span)
|
|
35
35
|
|
|
36
36
|
extractSpanLinks(formatted, span)
|
|
37
|
+
extractSpanEvents(formatted, span)
|
|
37
38
|
extractRootTags(formatted, span)
|
|
38
39
|
extractChunkTags(formatted, span)
|
|
39
40
|
extractTags(formatted, span)
|
|
@@ -88,6 +89,22 @@ function extractSpanLinks (trace, span) {
|
|
|
88
89
|
if (links.length > 0) { trace.meta['_dd.span_links'] = JSON.stringify(links) }
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
function extractSpanEvents (trace, span) {
|
|
93
|
+
const events = []
|
|
94
|
+
if (span._events) {
|
|
95
|
+
for (const event of span._events) {
|
|
96
|
+
const formattedEvent = {
|
|
97
|
+
name: event.name,
|
|
98
|
+
time_unix_nano: Math.round(event.startTime * 1e6),
|
|
99
|
+
attributes: event.attributes && Object.keys(event.attributes).length > 0 ? event.attributes : undefined
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
events.push(formattedEvent)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (events.length > 0) { trace.meta.events = JSON.stringify(events) }
|
|
106
|
+
}
|
|
107
|
+
|
|
91
108
|
function extractTags (trace, span) {
|
|
92
109
|
const context = span.context()
|
|
93
110
|
const origin = context._trace.origin
|
|
@@ -134,7 +151,10 @@ function extractTags (trace, span) {
|
|
|
134
151
|
case ERROR_STACK:
|
|
135
152
|
// HACK: remove when implemented in the backend
|
|
136
153
|
if (context._name !== 'fs.operation') {
|
|
137
|
-
trace.error
|
|
154
|
+
// HACK: to ensure otel.recordException does not influence trace.error
|
|
155
|
+
if (tags.setTraceError) {
|
|
156
|
+
trace.error = 1
|
|
157
|
+
}
|
|
138
158
|
} else {
|
|
139
159
|
break
|
|
140
160
|
}
|
|
@@ -142,7 +162,6 @@ function extractTags (trace, span) {
|
|
|
142
162
|
addTag(trace.meta, trace.metrics, tag, tags[tag])
|
|
143
163
|
}
|
|
144
164
|
}
|
|
145
|
-
|
|
146
165
|
setSingleSpanIngestionTags(trace, context._spanSampling)
|
|
147
166
|
|
|
148
167
|
addTag(trace.meta, trace.metrics, 'language', 'javascript')
|
|
@@ -20,6 +20,20 @@ function hrTimeToMilliseconds (time) {
|
|
|
20
20
|
return time[0] * 1e3 + time[1] / 1e6
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function isTimeInput (startTime) {
|
|
24
|
+
if (typeof startTime === 'number') {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
if (startTime instanceof Date) {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(startTime) && startTime.length === 2 &&
|
|
31
|
+
typeof startTime[0] === 'number' && typeof startTime[1] === 'number') {
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
const spanKindNames = {
|
|
24
38
|
[api.SpanKind.INTERNAL]: kinds.INTERNAL,
|
|
25
39
|
[api.SpanKind.SERVER]: kinds.SERVER,
|
|
@@ -179,17 +193,20 @@ class Span {
|
|
|
179
193
|
}
|
|
180
194
|
|
|
181
195
|
setAttribute (key, value) {
|
|
196
|
+
if (key === 'http.response.status_code') {
|
|
197
|
+
this._ddSpan.setTag('http.status_code', value.toString())
|
|
198
|
+
}
|
|
199
|
+
|
|
182
200
|
this._ddSpan.setTag(key, value)
|
|
183
201
|
return this
|
|
184
202
|
}
|
|
185
203
|
|
|
186
204
|
setAttributes (attributes) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
205
|
+
if ('http.response.status_code' in attributes) {
|
|
206
|
+
attributes['http.status_code'] = attributes['http.response.status_code'].toString()
|
|
207
|
+
}
|
|
190
208
|
|
|
191
|
-
|
|
192
|
-
api.diag.warn('Events not supported')
|
|
209
|
+
this._ddSpan.addTags(attributes)
|
|
193
210
|
return this
|
|
194
211
|
}
|
|
195
212
|
|
|
@@ -236,12 +253,29 @@ class Span {
|
|
|
236
253
|
return this.ended === false
|
|
237
254
|
}
|
|
238
255
|
|
|
239
|
-
|
|
256
|
+
addEvent (name, attributesOrStartTime, startTime) {
|
|
257
|
+
startTime = attributesOrStartTime && isTimeInput(attributesOrStartTime) ? attributesOrStartTime : startTime
|
|
258
|
+
const hrStartTime = timeInputToHrTime(startTime || (performance.now() + timeOrigin))
|
|
259
|
+
startTime = hrTimeToMilliseconds(hrStartTime)
|
|
260
|
+
|
|
261
|
+
this._ddSpan.addEvent(name, attributesOrStartTime, startTime)
|
|
262
|
+
return this
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
recordException (exception, timeInput) {
|
|
266
|
+
// HACK: identifier is added so that trace.error remains unchanged after a call to otel.recordException
|
|
240
267
|
this._ddSpan.addTags({
|
|
241
268
|
[ERROR_TYPE]: exception.name,
|
|
242
269
|
[ERROR_MESSAGE]: exception.message,
|
|
243
|
-
[ERROR_STACK]: exception.stack
|
|
270
|
+
[ERROR_STACK]: exception.stack,
|
|
271
|
+
doNotSetTraceError: true
|
|
244
272
|
})
|
|
273
|
+
const attributes = {}
|
|
274
|
+
if (exception.message) attributes['exception.message'] = exception.message
|
|
275
|
+
if (exception.type) attributes['exception.type'] = exception.type
|
|
276
|
+
if (exception.escaped) attributes['exception.escaped'] = exception.escaped
|
|
277
|
+
if (exception.stack) attributes['exception.stacktrace'] = exception.stack
|
|
278
|
+
this.addEvent(exception.name, attributes, timeInput)
|
|
245
279
|
}
|
|
246
280
|
|
|
247
281
|
get duration () {
|
|
@@ -67,6 +67,8 @@ class DatadogSpan {
|
|
|
67
67
|
this._store = storage.getStore()
|
|
68
68
|
this._duration = undefined
|
|
69
69
|
|
|
70
|
+
this._events = []
|
|
71
|
+
|
|
70
72
|
// For internal use only. You probably want `context()._name`.
|
|
71
73
|
// This name property is not updated when the span name changes.
|
|
72
74
|
// This is necessary for span count metrics.
|
|
@@ -163,6 +165,19 @@ class DatadogSpan {
|
|
|
163
165
|
})
|
|
164
166
|
}
|
|
165
167
|
|
|
168
|
+
addEvent (name, attributesOrStartTime, startTime) {
|
|
169
|
+
const event = { name }
|
|
170
|
+
if (attributesOrStartTime) {
|
|
171
|
+
if (typeof attributesOrStartTime === 'object') {
|
|
172
|
+
event.attributes = this._sanitizeEventAttributes(attributesOrStartTime)
|
|
173
|
+
} else {
|
|
174
|
+
startTime = attributesOrStartTime
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
event.startTime = startTime || this._getTime()
|
|
178
|
+
this._events.push(event)
|
|
179
|
+
}
|
|
180
|
+
|
|
166
181
|
finish (finishTime) {
|
|
167
182
|
if (this._duration !== undefined) {
|
|
168
183
|
return
|
|
@@ -221,7 +236,30 @@ class DatadogSpan {
|
|
|
221
236
|
const [key, value] = entry
|
|
222
237
|
addArrayOrScalarAttributes(key, value)
|
|
223
238
|
})
|
|
239
|
+
return sanitizedAttributes
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
_sanitizeEventAttributes (attributes = {}) {
|
|
243
|
+
const sanitizedAttributes = {}
|
|
224
244
|
|
|
245
|
+
for (const key in attributes) {
|
|
246
|
+
const value = attributes[key]
|
|
247
|
+
if (Array.isArray(value)) {
|
|
248
|
+
const newArray = []
|
|
249
|
+
for (const subkey in value) {
|
|
250
|
+
if (ALLOWED.includes(typeof value[subkey])) {
|
|
251
|
+
newArray.push(value[subkey])
|
|
252
|
+
} else {
|
|
253
|
+
log.warn('Dropping span event attribute. It is not of an allowed type')
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
sanitizedAttributes[key] = newArray
|
|
257
|
+
} else if (ALLOWED.includes(typeof value)) {
|
|
258
|
+
sanitizedAttributes[key] = value
|
|
259
|
+
} else {
|
|
260
|
+
log.warn('Dropping span event attribute. It is not of an allowed type')
|
|
261
|
+
}
|
|
262
|
+
}
|
|
225
263
|
return sanitizedAttributes
|
|
226
264
|
}
|
|
227
265
|
|
|
@@ -81,5 +81,6 @@ module.exports = {
|
|
|
81
81
|
get 'selenium-webdriver' () { return require('../../../datadog-plugin-selenium/src') },
|
|
82
82
|
get sharedb () { return require('../../../datadog-plugin-sharedb/src') },
|
|
83
83
|
get tedious () { return require('../../../datadog-plugin-tedious/src') },
|
|
84
|
+
get undici () { return require('../../../datadog-plugin-undici/src') },
|
|
84
85
|
get winston () { return require('../../../datadog-plugin-winston/src') }
|
|
85
86
|
}
|
|
@@ -10,6 +10,8 @@ const {
|
|
|
10
10
|
SAMPLING_MECHANISM_AGENT,
|
|
11
11
|
SAMPLING_MECHANISM_RULE,
|
|
12
12
|
SAMPLING_MECHANISM_MANUAL,
|
|
13
|
+
SAMPLING_MECHANISM_REMOTE_USER,
|
|
14
|
+
SAMPLING_MECHANISM_REMOTE_DYNAMIC,
|
|
13
15
|
SAMPLING_RULE_DECISION,
|
|
14
16
|
SAMPLING_LIMIT_DECISION,
|
|
15
17
|
SAMPLING_AGENT_DECISION,
|
|
@@ -41,9 +43,9 @@ class PrioritySampler {
|
|
|
41
43
|
this.update({})
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
configure (env, { sampleRate, rateLimit = 100, rules = [] } = {}) {
|
|
46
|
+
configure (env, { sampleRate, provenance = undefined, rateLimit = 100, rules = [] } = {}) {
|
|
45
47
|
this._env = env
|
|
46
|
-
this._rules = this._normalizeRules(rules, sampleRate, rateLimit)
|
|
48
|
+
this._rules = this._normalizeRules(rules, sampleRate, rateLimit, provenance)
|
|
47
49
|
this._limiter = new RateLimiter(rateLimit)
|
|
48
50
|
|
|
49
51
|
setSamplingRules(this._rules)
|
|
@@ -137,6 +139,8 @@ class PrioritySampler {
|
|
|
137
139
|
_getPriorityByRule (context, rule) {
|
|
138
140
|
context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
|
|
139
141
|
context._sampling.mechanism = SAMPLING_MECHANISM_RULE
|
|
142
|
+
if (rule.provenance === 'customer') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_USER
|
|
143
|
+
if (rule.provenance === 'dynamic') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_DYNAMIC
|
|
140
144
|
|
|
141
145
|
return rule.sample() && this._isSampledByRateLimit(context)
|
|
142
146
|
? USER_KEEP
|
|
@@ -181,11 +185,11 @@ class PrioritySampler {
|
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
|
|
184
|
-
_normalizeRules (rules, sampleRate, rateLimit) {
|
|
188
|
+
_normalizeRules (rules, sampleRate, rateLimit, provenance) {
|
|
185
189
|
rules = [].concat(rules || [])
|
|
186
190
|
|
|
187
191
|
return rules
|
|
188
|
-
.concat({ sampleRate, maxPerSecond: rateLimit })
|
|
192
|
+
.concat({ sampleRate, maxPerSecond: rateLimit, provenance })
|
|
189
193
|
.map(rule => ({ ...rule, sampleRate: parseFloat(rule.sampleRate) }))
|
|
190
194
|
.filter(rule => !isNaN(rule.sampleRate))
|
|
191
195
|
.map(SamplingRule.from)
|
|
@@ -64,7 +64,7 @@ function serviceLocator (span) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
class SamplingRule {
|
|
67
|
-
constructor ({ name, service, resource, tags, sampleRate = 1.0, maxPerSecond } = {}) {
|
|
67
|
+
constructor ({ name, service, resource, tags, sampleRate = 1.0, provenance = undefined, maxPerSecond } = {}) {
|
|
68
68
|
this.matchers = []
|
|
69
69
|
|
|
70
70
|
if (name) {
|
|
@@ -82,6 +82,7 @@ class SamplingRule {
|
|
|
82
82
|
|
|
83
83
|
this._sampler = new Sampler(sampleRate)
|
|
84
84
|
this._limiter = undefined
|
|
85
|
+
this.provenance = provenance
|
|
85
86
|
|
|
86
87
|
if (Number.isFinite(maxPerSecond)) {
|
|
87
88
|
this._limiter = new RateLimiter(maxPerSecond)
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const constants = require('./constants')
|
|
3
4
|
const log = require('./log')
|
|
5
|
+
const ERROR_MESSAGE = constants.ERROR_MESSAGE
|
|
6
|
+
const ERROR_STACK = constants.ERROR_STACK
|
|
7
|
+
const ERROR_TYPE = constants.ERROR_TYPE
|
|
4
8
|
|
|
5
9
|
const otelTagMap = {
|
|
6
10
|
'deployment.environment': 'env',
|
|
@@ -14,7 +18,6 @@ function add (carrier, keyValuePairs, parseOtelTags = false) {
|
|
|
14
18
|
if (Array.isArray(keyValuePairs)) {
|
|
15
19
|
return keyValuePairs.forEach(tags => add(carrier, tags))
|
|
16
20
|
}
|
|
17
|
-
|
|
18
21
|
try {
|
|
19
22
|
if (typeof keyValuePairs === 'string') {
|
|
20
23
|
const segments = keyValuePairs.split(',')
|
|
@@ -32,6 +35,12 @@ function add (carrier, keyValuePairs, parseOtelTags = false) {
|
|
|
32
35
|
carrier[key.trim()] = value.trim()
|
|
33
36
|
}
|
|
34
37
|
} else {
|
|
38
|
+
// HACK: to ensure otel.recordException does not influence trace.error
|
|
39
|
+
if (ERROR_MESSAGE in keyValuePairs || ERROR_STACK in keyValuePairs || ERROR_TYPE in keyValuePairs) {
|
|
40
|
+
if (!('doNotSetTraceError' in keyValuePairs)) {
|
|
41
|
+
carrier.setTraceError = true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
35
44
|
Object.assign(carrier, keyValuePairs)
|
|
36
45
|
}
|
|
37
46
|
} catch (e) {
|