dd-trace 5.16.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-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/format.js +21 -2
- package/packages/dd-trace/src/opentelemetry/span.js +33 -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/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
|
+
})
|
|
@@ -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)
|
|
@@ -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,
|
|
@@ -196,11 +210,6 @@ class Span {
|
|
|
196
210
|
return this
|
|
197
211
|
}
|
|
198
212
|
|
|
199
|
-
addEvent (name, attributesOrStartTime, startTime) {
|
|
200
|
-
api.diag.warn('Events not supported')
|
|
201
|
-
return this
|
|
202
|
-
}
|
|
203
|
-
|
|
204
213
|
addLink (context, attributes) {
|
|
205
214
|
// extract dd context
|
|
206
215
|
const ddSpanContext = context._ddContext
|
|
@@ -244,12 +253,29 @@ class Span {
|
|
|
244
253
|
return this.ended === false
|
|
245
254
|
}
|
|
246
255
|
|
|
247
|
-
|
|
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
|
|
248
267
|
this._ddSpan.addTags({
|
|
249
268
|
[ERROR_TYPE]: exception.name,
|
|
250
269
|
[ERROR_MESSAGE]: exception.message,
|
|
251
|
-
[ERROR_STACK]: exception.stack
|
|
270
|
+
[ERROR_STACK]: exception.stack,
|
|
271
|
+
doNotSetTraceError: true
|
|
252
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)
|
|
253
279
|
}
|
|
254
280
|
|
|
255
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
|
}
|
|
@@ -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) {
|