dd-trace 5.65.0 → 5.67.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.
Files changed (45) hide show
  1. package/index.d.ts +38 -0
  2. package/package.json +8 -8
  3. package/packages/datadog-instrumentations/src/express.js +3 -7
  4. package/packages/datadog-instrumentations/src/graphql.js +10 -6
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  6. package/packages/datadog-instrumentations/src/helpers/register.js +10 -2
  7. package/packages/datadog-instrumentations/src/playwright.js +25 -9
  8. package/packages/datadog-instrumentations/src/prisma.js +8 -10
  9. package/packages/datadog-instrumentations/src/ws.js +136 -0
  10. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +30 -3
  11. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +9 -6
  12. package/packages/datadog-plugin-aws-sdk/src/util.js +61 -1
  13. package/packages/datadog-plugin-express/src/code_origin.js +11 -0
  14. package/packages/datadog-plugin-graphql/src/index.js +3 -0
  15. package/packages/datadog-plugin-ws/src/close.js +69 -0
  16. package/packages/datadog-plugin-ws/src/index.js +26 -0
  17. package/packages/datadog-plugin-ws/src/producer.js +60 -0
  18. package/packages/datadog-plugin-ws/src/receiver.js +70 -0
  19. package/packages/datadog-plugin-ws/src/server.js +79 -0
  20. package/packages/datadog-shimmer/src/shimmer.js +11 -2
  21. package/packages/dd-trace/src/appsec/blocking.js +29 -0
  22. package/packages/dd-trace/src/appsec/channels.js +4 -2
  23. package/packages/dd-trace/src/appsec/index.js +7 -2
  24. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +1 -0
  25. package/packages/dd-trace/src/appsec/rasp/index.js +25 -7
  26. package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
  27. package/packages/dd-trace/src/appsec/rasp/utils.js +13 -2
  28. package/packages/dd-trace/src/config.js +12 -0
  29. package/packages/dd-trace/src/guardrails/index.js +11 -3
  30. package/packages/dd-trace/src/guardrails/telemetry.js +15 -16
  31. package/packages/dd-trace/src/llmobs/index.js +7 -0
  32. package/packages/dd-trace/src/llmobs/sdk.js +28 -0
  33. package/packages/dd-trace/src/llmobs/span_processor.js +124 -28
  34. package/packages/dd-trace/src/llmobs/tagger.js +2 -1
  35. package/packages/dd-trace/src/llmobs/telemetry.js +7 -1
  36. package/packages/dd-trace/src/log/writer.js +1 -1
  37. package/packages/dd-trace/src/plugin_manager.js +8 -2
  38. package/packages/dd-trace/src/plugins/index.js +2 -1
  39. package/packages/dd-trace/src/plugins/util/ip_extractor.js +48 -45
  40. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
  41. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
  42. package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +30 -0
  43. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
  44. package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +30 -0
  45. package/packages/dd-trace/src/supported-configurations.json +3 -0
@@ -0,0 +1,69 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing.js')
4
+
5
+ class WSClosePlugin extends TracingPlugin {
6
+ static get id () { return 'ws' }
7
+ static get prefix () { return 'tracing:ws:close' }
8
+ static get type () { return 'websocket' }
9
+ static get kind () { return 'close' }
10
+
11
+ bindStart (ctx) {
12
+ const {
13
+ traceWebsocketMessagesEnabled,
14
+ traceWebsocketMessagesInheritSampling,
15
+ traceWebsocketMessagesSeparateTraces
16
+ } = this.config
17
+ if (!traceWebsocketMessagesEnabled) return
18
+
19
+ const { code, data, socket, isPeerClose } = ctx
20
+ if (!socket.spanContext) return
21
+
22
+ const spanKind = isPeerClose ? 'consumer' : 'producer'
23
+ const spanTags = socket.spanContext.spanTags
24
+ const path = spanTags['resource.name'].split(' ')[1]
25
+ const service = this.serviceName({ pluginConfig: this.config })
26
+ const span = this.startSpan(this.operationName(), {
27
+ service,
28
+ meta: {
29
+ 'resource.name': `websocket ${path}`,
30
+ 'span.type': 'websocket',
31
+ 'span.kind': spanKind,
32
+ 'websocket.close.code': code
33
+
34
+ }
35
+ }, ctx)
36
+
37
+ if (data?.toString().length > 0) {
38
+ span.setTag('websocket.close.reason', data.toString())
39
+ }
40
+
41
+ if (isPeerClose && traceWebsocketMessagesInheritSampling && traceWebsocketMessagesSeparateTraces) {
42
+ span.setTag('_dd.dm.service', spanTags['service.name'] || service)
43
+ span.setTag('_dd.dm.resource', spanTags['resource.name'] || `websocket ${path}`)
44
+ span.setTag('_dd.dm.inherited', 1)
45
+ }
46
+
47
+ ctx.span = span
48
+ return ctx.currentStore
49
+ }
50
+
51
+ bindAsyncStart (ctx) {
52
+ if (!ctx.isPeerClose) ctx.span.finish()
53
+ return ctx.parentStore
54
+ }
55
+
56
+ asyncStart (ctx) {
57
+ ctx.span.finish()
58
+ }
59
+
60
+ end (ctx) {
61
+ if (!Object.hasOwn(ctx, 'result')) return
62
+
63
+ if (ctx.socket.spanContext) ctx.span.addLink(ctx.socket.spanContext)
64
+
65
+ ctx.span.finish()
66
+ }
67
+ }
68
+
69
+ module.exports = WSClosePlugin
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
4
+
5
+ const WSServerPlugin = require('./server')
6
+ const WSProducerPlugin = require('./producer')
7
+ const WSReceiverPlugin = require('./receiver')
8
+ const WSClosePlugin = require('./close')
9
+
10
+ class WSPlugin extends CompositePlugin {
11
+ static get id () { return 'ws' }
12
+ static get plugins () {
13
+ return {
14
+ server: WSServerPlugin,
15
+ producer: WSProducerPlugin,
16
+ receiver: WSReceiverPlugin,
17
+ close: WSClosePlugin
18
+ }
19
+ }
20
+
21
+ configure (config) {
22
+ return super.configure(config)
23
+ }
24
+ }
25
+
26
+ module.exports = WSPlugin
@@ -0,0 +1,60 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing.js')
4
+
5
+ class WSProducerPlugin extends TracingPlugin {
6
+ static get id () { return 'ws' }
7
+ static get prefix () { return 'tracing:ws:send' }
8
+ static get type () { return 'websocket' }
9
+ static get kind () { return 'producer' }
10
+
11
+ bindStart (ctx) {
12
+ const messagesEnabled = this.config.traceWebsocketMessagesEnabled
13
+ if (!messagesEnabled) return
14
+
15
+ const { byteLength, socket, binary } = ctx
16
+ if (!socket.spanContext) return
17
+
18
+ const spanTags = socket.spanContext.spanTags
19
+ const path = spanTags['resource.name'].split(' ')[1]
20
+ const opCode = binary ? 'binary' : 'text'
21
+ const service = this.serviceName({ pluginConfig: this.config })
22
+ const span = this.startSpan(this.operationName(), {
23
+ service,
24
+ meta: {
25
+ 'span.type': 'websocket',
26
+ 'span.kind': 'producer',
27
+ 'resource.name': `websocket ${path}`,
28
+ 'websocket.message.type': opCode,
29
+
30
+ },
31
+ metrics: {
32
+ 'websocket.message.length': byteLength
33
+ }
34
+
35
+ }, ctx)
36
+
37
+ ctx.span = span
38
+ return ctx.currentStore
39
+ }
40
+
41
+ bindAsyncStart (ctx) {
42
+ ctx.span.finish()
43
+ return ctx.parentStore
44
+ }
45
+
46
+ asyncStart (ctx) {
47
+ ctx.span.finish()
48
+ }
49
+
50
+ end (ctx) {
51
+ if (!Object.hasOwn(ctx, 'result')) return
52
+
53
+ if (ctx.socket.spanContext) ctx.span.addLink(ctx.socket.spanContext, { 'dd.kind': 'resuming' })
54
+
55
+ ctx.span.finish()
56
+ return ctx.parentStore
57
+ }
58
+ }
59
+
60
+ module.exports = WSProducerPlugin
@@ -0,0 +1,70 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing.js')
4
+
5
+ class WSReceiverPlugin extends TracingPlugin {
6
+ static get id () { return 'ws' }
7
+ static get prefix () { return 'tracing:ws:receive' }
8
+ static get type () { return 'websocket' }
9
+ static get kind () { return 'consumer' }
10
+
11
+ bindStart (ctx) {
12
+ const {
13
+ traceWebsocketMessagesEnabled,
14
+ traceWebsocketMessagesInheritSampling,
15
+ traceWebsocketMessagesSeparateTraces
16
+ } = this.config
17
+ if (!traceWebsocketMessagesEnabled) return
18
+
19
+ const { byteLength, socket, binary } = ctx
20
+ if (!socket.spanContext) return
21
+
22
+ const spanTags = socket.spanContext.spanTags
23
+ const path = spanTags['resource.name'].split(' ')[1]
24
+ const opCode = binary ? 'binary' : 'text'
25
+
26
+ const service = this.serviceName({ pluginConfig: this.config })
27
+ const span = this.startSpan(this.operationName(), {
28
+ service,
29
+ meta: {
30
+ 'span.type': 'websocket',
31
+ 'span.kind': 'consumer',
32
+ 'resource.name': `websocket ${path}`,
33
+ 'websocket.duration.style': 'handler',
34
+ 'websocket.message.type': opCode,
35
+ },
36
+ metrics: {
37
+ 'websocket.message.length': byteLength,
38
+ }
39
+
40
+ }, ctx)
41
+
42
+ if (traceWebsocketMessagesInheritSampling && traceWebsocketMessagesSeparateTraces) {
43
+ span.setTag('_dd.dm.service', spanTags['service.name'] || service)
44
+ span.setTag('_dd.dm.resource', spanTags['resource.name'] || `websocket ${path}`)
45
+ span.setTag('_dd.dm.inherited', 1)
46
+ }
47
+
48
+ ctx.span = span
49
+ return ctx.currentStore
50
+ }
51
+
52
+ bindAsyncStart (ctx) {
53
+ return ctx.parentStore
54
+ }
55
+
56
+ asyncStart (ctx) {
57
+ ctx.span.finish()
58
+ }
59
+
60
+ end (ctx) {
61
+ if (!Object.hasOwn(ctx, 'result')) return
62
+
63
+ if (ctx.socket.spanContext) ctx.span.addLink(ctx.socket.spanContext, { 'dd.kind': 'executed_by' })
64
+
65
+ ctx.span.finish()
66
+ return ctx.parentStore
67
+ }
68
+ }
69
+
70
+ module.exports = WSReceiverPlugin
@@ -0,0 +1,79 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing.js')
4
+ const tags = require('../../../ext/tags.js')
5
+
6
+ const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE
7
+
8
+ class WSServerPlugin extends TracingPlugin {
9
+ static get id () { return 'ws' }
10
+ static get prefix () { return 'tracing:ws:server:connect' }
11
+ static get type () { return 'websocket' }
12
+ static get kind () { return 'request' }
13
+
14
+ bindStart (ctx) {
15
+ const req = ctx.req
16
+
17
+ const options = {}
18
+ const headers = Object.entries(req.headers)
19
+ options.headers = Object.fromEntries(headers)
20
+ options.method = req.method
21
+
22
+ const protocol = `${getRequestProtocol(req)}:`
23
+ const host = options.headers.host
24
+ const path = req.url
25
+ const uri = `${protocol}//${host}${path}`
26
+
27
+ ctx.args = { options }
28
+
29
+ const service = this.serviceName({ pluginConfig: this.config })
30
+ const span = this.startSpan(this.operationName(), {
31
+ service,
32
+ meta: {
33
+ 'span.type': 'websocket',
34
+ 'http.upgraded': 'websocket',
35
+ 'http.method': options.method,
36
+ 'http.url': uri,
37
+ 'resource.name': `${options.method} ${path}`,
38
+ 'span.kind': 'server'
39
+
40
+ }
41
+
42
+ }, ctx)
43
+ ctx.span = span
44
+
45
+ ctx.socket.spanContext = ctx.span._spanContext
46
+ ctx.socket.spanContext.spanTags = ctx.span._spanContext._tags
47
+
48
+ return ctx.currentStore
49
+ }
50
+
51
+ bindAsyncStart (ctx) {
52
+ ctx.span.setTag(HTTP_STATUS_CODE, ctx.req.resStatus)
53
+
54
+ return ctx.parentStore
55
+ }
56
+
57
+ asyncStart (ctx) {
58
+ ctx.span.finish()
59
+ }
60
+ }
61
+
62
+ function getRequestProtocol (req, fallback = 'ws') {
63
+ // 1. Check if the underlying TLS socket has 'encrypted'
64
+ if (req.socket && req.socket.encrypted) {
65
+ return 'wss'
66
+ }
67
+
68
+ // 2. Check for a trusted header set by a proxy
69
+ if (req.headers && req.headers['x-forwarded-proto']) {
70
+ const proto = req.headers['x-forwarded-proto'].split(',')[0].trim()
71
+ if (proto === 'https') return 'wss'
72
+ if (proto === 'http') return 'ws'
73
+ }
74
+
75
+ // 3. Fallback to ws
76
+ return fallback
77
+ }
78
+
79
+ module.exports = WSServerPlugin
@@ -86,7 +86,7 @@ function wrapFunction (original, wrapper) {
86
86
  /**
87
87
  * Wraps a method of an object with a wrapper function.
88
88
  *
89
- * @param {Record<string | symbol, unknown> | Function} target - The target
89
+ * @param {Record<string | symbol, unknown> | Function | undefined} target - The target
90
90
  * object.
91
91
  * @param {string | symbol} name - The property key of the method to wrap.
92
92
  * @param {(original: Function) => (...args) => any} wrapper - The wrapper function.
@@ -95,7 +95,7 @@ function wrapFunction (original, wrapper) {
95
95
  * returns the earlier retrieved value. Use with care! This may only be done in
96
96
  * case the getter absolutely has no side effect and no setter is defined for the
97
97
  * property.
98
- * @returns {Record<string | symbol, unknown> | Function} The target object with
98
+ * @returns {Record<string | symbol, unknown> | Function | undefined} The target object with
99
99
  * the wrapped method.
100
100
  */
101
101
  function wrap (target, name, wrapper, options) {
@@ -103,6 +103,15 @@ function wrap (target, name, wrapper, options) {
103
103
  throw new TypeError(wrapper ? 'Target is not a function' : 'No function provided')
104
104
  }
105
105
 
106
+ if (target == null) {
107
+ // TODO: Add logging. This is an indicator that the part of a module that we
108
+ // try to instrument changed.
109
+ // Accessing the properties directly is in itself also unsafe, so we could just
110
+ // pass through an array of properties that should be accessed and automatically
111
+ // handle the access in here.
112
+ return
113
+ }
114
+
106
115
  // No descriptor means original was on the prototype. This is not totally
107
116
  // safe, since we define the property on the target. That could have an impact
108
117
  // in case e.g., the own keys are checks.
@@ -14,6 +14,8 @@ let defaultBlockingActionParameters
14
14
 
15
15
  const responseBlockedSet = new WeakSet()
16
16
 
17
+ const blockDelegations = new WeakMap()
18
+
17
19
  const specificBlockingTypes = {
18
20
  GRAPHQL: 'graphql'
19
21
  }
@@ -99,6 +101,9 @@ function getBlockingData (req, specificType, actionParameters) {
99
101
  }
100
102
 
101
103
  function block (req, res, rootSpan, abortController, actionParameters = defaultBlockingActionParameters) {
104
+ // synchronous blocking overrides previously created delegations
105
+ blockDelegations.delete(res)
106
+
102
107
  try {
103
108
  if (res.headersSent) {
104
109
  log.warn('[ASM] Cannot send blocking response when headers have already been sent')
@@ -127,11 +132,33 @@ function block (req, res, rootSpan, abortController, actionParameters = defaultB
127
132
  rootSpan?.setTag('_dd.appsec.block.failed', 1)
128
133
  log.error('[ASM] Blocking error', err)
129
134
 
135
+ // TODO: if blocking fails, then the response will never be sent ?
136
+
130
137
  updateBlockFailureMetric(req)
131
138
  return false
132
139
  }
133
140
  }
134
141
 
142
+ function registerBlockDelegation (req, res) {
143
+ const args = arguments
144
+
145
+ return new Promise((resolve) => {
146
+ // ignore subsequent delegations by never calling their resolve()
147
+ if (blockDelegations.has(res)) return
148
+
149
+ blockDelegations.set(res, { args, resolve })
150
+ })
151
+ }
152
+
153
+ function callBlockDelegation (res) {
154
+ const delegation = blockDelegations.get(res)
155
+ if (delegation) {
156
+ const result = block.apply(this, delegation.args)
157
+ delegation.resolve(result)
158
+ return result
159
+ }
160
+ }
161
+
135
162
  function getBlockingAction (actions) {
136
163
  // waf only returns one action, but it prioritizes redirect over block
137
164
  return actions?.redirect_request || actions?.block_request
@@ -158,6 +185,8 @@ function setDefaultBlockingActionParameters (actions) {
158
185
  module.exports = {
159
186
  addSpecificEndpoint,
160
187
  block,
188
+ registerBlockDelegation,
189
+ callBlockDelegation,
161
190
  specificBlockingTypes,
162
191
  getBlockingData,
163
192
  getBlockingAction,
@@ -14,10 +14,11 @@ module.exports = {
14
14
  expressProcessParams: dc.channel('datadog:express:process_params:start'),
15
15
  expressSession: dc.channel('datadog:express-session:middleware:finish'),
16
16
  fastifyBodyParser: dc.channel('datadog:fastify:body-parser:finish'),
17
- fastifyResponseChannel: dc.channel('datadog:fastify:response:finish'),
18
- fastifyQueryParams: dc.channel('datadog:fastify:query-params:finish'),
19
17
  fastifyCookieParser: dc.channel('datadog:fastify-cookie:read:finish'),
18
+ fastifyMiddlewareError: dc.channel('apm:fastify:middleware:error'),
20
19
  fastifyPathParams: dc.channel('datadog:fastify:path-params:finish'),
20
+ fastifyQueryParams: dc.channel('datadog:fastify:query-params:finish'),
21
+ fastifyResponseChannel: dc.channel('datadog:fastify:response:finish'),
21
22
  fsOperationStart: dc.channel('apm:fs:operation:start'),
22
23
  graphqlMiddlewareChannel: dc.tracingChannel('datadog:apollo:middleware'),
23
24
  httpClientRequestStart: dc.channel('apm:http:client:request:start'),
@@ -36,6 +37,7 @@ module.exports = {
36
37
  responseSetHeader: dc.channel('datadog:http:server:response:set-header:start'),
37
38
  responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'),
38
39
  routerParam: dc.channel('datadog:router:param:start'),
40
+ routerMiddlewareError: dc.channel('apm:router:middleware:error'),
39
41
  setCookieChannel: dc.channel('datadog:iast:set-cookie'),
40
42
  setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start'),
41
43
  startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
@@ -34,7 +34,7 @@ const apiSecuritySampler = require('./api_security_sampler')
34
34
  const web = require('../plugins/util/web')
35
35
  const { extractIp } = require('../plugins/util/ip_extractor')
36
36
  const { HTTP_CLIENT_IP } = require('../../../../ext/tags')
37
- const { isBlocked, block, setTemplates, getBlockingAction } = require('./blocking')
37
+ const { isBlocked, block, callBlockDelegation, setTemplates, getBlockingAction } = require('./blocking')
38
38
  const UserTracking = require('./user_tracking')
39
39
  const { storage } = require('../../../datadog-core')
40
40
  const graphql = require('./graphql')
@@ -306,8 +306,13 @@ function onResponseWriteHead ({ req, res, abortController, statusCode, responseH
306
306
  storedResponseHeaders.set(req, responseHeaders)
307
307
  }
308
308
 
309
+ // TODO: do not call waf if inside block()
310
+ // if (isBlocking()) {
311
+ // return
312
+ // }
313
+
309
314
  // avoid "write after end" error
310
- if (isBlocked(res)) {
315
+ if (isBlocked(res) || callBlockDelegation(res)) {
311
316
  abortController?.abort()
312
317
  return
313
318
  }
@@ -35,6 +35,7 @@ class AppsecFsPlugin extends Plugin {
35
35
  this.addBind('apm:fs:operation:finish', this._onFsOperationFinishOrRenderEnd)
36
36
  this.addBind('tracing:datadog:express:response:render:start', this._onResponseRenderStart)
37
37
  this.addBind('tracing:datadog:express:response:render:end', this._onFsOperationFinishOrRenderEnd)
38
+ // We might have to add the same subscribers for fastify later
38
39
 
39
40
  super.configure(true)
40
41
  }
@@ -1,8 +1,13 @@
1
1
  'use strict'
2
2
 
3
3
  const web = require('../../plugins/util/web')
4
- const { setUncaughtExceptionCaptureCallbackStart, expressMiddlewareError } = require('../channels')
5
- const { block, isBlocked } = require('../blocking')
4
+ const {
5
+ setUncaughtExceptionCaptureCallbackStart,
6
+ expressMiddlewareError,
7
+ fastifyMiddlewareError,
8
+ routerMiddlewareError
9
+ } = require('../channels')
10
+ const { block, registerBlockDelegation, isBlocked } = require('../blocking')
6
11
  const ssrf = require('./ssrf')
7
12
  const sqli = require('./sql_injection')
8
13
  const lfi = require('./lfi')
@@ -39,7 +44,7 @@ function findDatadogRaspAbortError (err, deep = 10) {
39
44
  }
40
45
 
41
46
  function handleUncaughtExceptionMonitor (error) {
42
- if (!blockOnDatadogRaspAbortError({ error })) return
47
+ if (!blockOnDatadogRaspAbortError({ error, isTopLevel: true })) return
43
48
 
44
49
  if (process.hasUncaughtExceptionCaptureCallback()) {
45
50
  // uncaughtException event is not executed when hasUncaughtExceptionCaptureCallback is true
@@ -81,15 +86,22 @@ function handleUncaughtExceptionMonitor (error) {
81
86
  }
82
87
  }
83
88
 
84
- function blockOnDatadogRaspAbortError ({ error }) {
89
+ function blockOnDatadogRaspAbortError ({ error, isTopLevel }) {
85
90
  const abortError = findDatadogRaspAbortError(error)
86
91
  if (!abortError) return false
87
92
 
88
93
  const { req, res, blockingAction, raspRule, ruleTriggered } = abortError
89
94
  if (!isBlocked(res)) {
90
- const blocked = block(req, res, web.root(req), null, blockingAction)
95
+ const blockFn = isTopLevel ? block : registerBlockDelegation
96
+ const blocked = blockFn(req, res, web.root(req), null, blockingAction)
91
97
  if (ruleTriggered) {
92
- updateRaspRuleMatchMetricTags(req, raspRule, true, blocked)
98
+ // block() returns a bool, and registerBlockDelegation() returns a promise
99
+ // we use Promise.resolve() to handle both cases
100
+ Promise.resolve(blocked).then(blocked => {
101
+ // TODO: bug: this metric is not called when the raspAbortError is caught by user
102
+ // or on subsequent blockDelegations
103
+ updateRaspRuleMatchMetricTags(req, raspRule, true, blocked)
104
+ })
93
105
  }
94
106
  }
95
107
 
@@ -103,7 +115,10 @@ function enable (config) {
103
115
  cmdi.enable(config)
104
116
 
105
117
  process.on('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
118
+
106
119
  expressMiddlewareError.subscribe(blockOnDatadogRaspAbortError)
120
+ fastifyMiddlewareError.subscribe(blockOnDatadogRaspAbortError)
121
+ routerMiddlewareError.subscribe(blockOnDatadogRaspAbortError)
107
122
  }
108
123
 
109
124
  function disable () {
@@ -113,7 +128,10 @@ function disable () {
113
128
  cmdi.disable()
114
129
 
115
130
  process.off('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
116
- if (expressMiddlewareError.hasSubscribers) expressMiddlewareError.unsubscribe(blockOnDatadogRaspAbortError)
131
+
132
+ expressMiddlewareError.unsubscribe(blockOnDatadogRaspAbortError)
133
+ fastifyMiddlewareError.unsubscribe(blockOnDatadogRaspAbortError)
134
+ routerMiddlewareError.unsubscribe(blockOnDatadogRaspAbortError)
117
135
  }
118
136
 
119
137
  module.exports = {
@@ -33,7 +33,7 @@ function disable () {
33
33
  }
34
34
 
35
35
  function onFirstReceivedRequest () {
36
- // nodejs unsubscribe during publish bug: https://github.com/nodejs/node/pull/55116
36
+ // Node.js unsubscribe during publish bug: https://github.com/nodejs/node/pull/55116
37
37
  process.nextTick(() => {
38
38
  incomingHttpRequestStart.unsubscribe(onFirstReceivedRequest)
39
39
  })
@@ -19,6 +19,11 @@ const RULE_TYPES = {
19
19
  SSRF: 'ssrf'
20
20
  }
21
21
 
22
+ const ALLOWED_ROOTSPAN_NAMES = new Set([
23
+ 'express.request',
24
+ 'fastify.request'
25
+ ])
26
+
22
27
  class DatadogRaspAbortError extends Error {
23
28
  constructor (req, res, blockingAction, raspRule, ruleTriggered) {
24
29
  super('DatadogRaspAbortError')
@@ -28,6 +33,12 @@ class DatadogRaspAbortError extends Error {
28
33
  this.blockingAction = blockingAction
29
34
  this.raspRule = raspRule
30
35
  this.ruleTriggered = ruleTriggered
36
+
37
+ // hide these props to not pollute app logs
38
+ Object.defineProperties(this, {
39
+ req: { enumerable: false },
40
+ res: { enumerable: false }
41
+ })
31
42
  }
32
43
  }
33
44
 
@@ -52,9 +63,9 @@ function handleResult (result, req, res, abortController, config, raspRule) {
52
63
 
53
64
  if (abortController && !abortOnUncaughtException) {
54
65
  const blockingAction = getBlockingAction(result?.actions)
66
+ const rootSpanName = rootSpan?.context?.()?._name
55
67
 
56
- // Should block only in express
57
- if (blockingAction && rootSpan?.context()._name === 'express.request') {
68
+ if (blockingAction && ALLOWED_ROOTSPAN_NAMES.has(rootSpanName)) {
58
69
  const abortError = new DatadogRaspAbortError(req, res, blockingAction, raspRule, ruleTriggered)
59
70
  abortController.abort(abortError)
60
71
 
@@ -630,6 +630,9 @@ class Config {
630
630
  defaults['tracePropagationStyle.inject'] = ['datadog', 'tracecontext', 'baggage']
631
631
  defaults['tracePropagationStyle.extract'] = ['datadog', 'tracecontext', 'baggage']
632
632
  defaults['tracePropagationStyle.otelPropagators'] = false
633
+ defaults.traceWebsocketMessagesEnabled = true
634
+ defaults.traceWebsocketMessagesInheritSampling = true
635
+ defaults.traceWebsocketMessagesSeparateTraces = true
633
636
  defaults.tracing = true
634
637
  defaults.url = undefined
635
638
  defaults.version = pkg.version
@@ -812,6 +815,9 @@ class Config {
812
815
  DD_TRACE_SPAN_LEAK_DEBUG,
813
816
  DD_TRACE_STARTUP_LOGS,
814
817
  DD_TRACE_TAGS,
818
+ DD_TRACE_WEBSOCKET_MESSAGES_ENABLED,
819
+ DD_TRACE_WEBSOCKET_MESSAGES_INHERIT_SAMPLING,
820
+ DD_TRACE_WEBSOCKET_MESSAGES_SEPARATE_TRACES,
815
821
  DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH,
816
822
  DD_TRACING_ENABLED,
817
823
  DD_VERSION,
@@ -1050,6 +1056,9 @@ class Config {
1050
1056
  DD_TRACE_PROPAGATION_STYLE_EXTRACT
1051
1057
  ? false
1052
1058
  : !!OTEL_PROPAGATORS)
1059
+ this._setBoolean(env, 'traceWebsocketMessagesEnabled', DD_TRACE_WEBSOCKET_MESSAGES_ENABLED)
1060
+ this._setBoolean(env, 'traceWebsocketMessagesInheritSampling', DD_TRACE_WEBSOCKET_MESSAGES_INHERIT_SAMPLING)
1061
+ this._setBoolean(env, 'traceWebsocketMessagesSeparateTraces', DD_TRACE_WEBSOCKET_MESSAGES_SEPARATE_TRACES)
1053
1062
  this._setBoolean(env, 'tracing', DD_TRACING_ENABLED)
1054
1063
  this._setString(env, 'version', DD_VERSION || tags.version)
1055
1064
  this._setBoolean(env, 'inferredProxyServicesEnabled', DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED)
@@ -1215,6 +1224,9 @@ class Config {
1215
1224
  this._setTags(opts, 'tags', tags)
1216
1225
  this._setBoolean(opts, 'traceId128BitGenerationEnabled', options.traceId128BitGenerationEnabled)
1217
1226
  this._setBoolean(opts, 'traceId128BitLoggingEnabled', options.traceId128BitLoggingEnabled)
1227
+ this._setBoolean(opts, 'traceWebsocketMessagesEnabled', options.traceWebsocketMessagesEnabled)
1228
+ this._setBoolean(opts, 'traceWebsocketMessagesInheritSampling', options.traceWebsocketMessagesInheritSampling)
1229
+ this._setBoolean(opts, 'traceWebsocketMessagesSeparateTraces', options.traceWebsocketMessagesSeparateTraces)
1218
1230
  this._setString(opts, 'version', options.version || tags.version)
1219
1231
  this._setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled)
1220
1232
  this._setBoolean(opts, 'graphqlErrorExtensions', options.graphqlErrorExtensions)
@@ -45,9 +45,13 @@ function guard (fn) {
45
45
  telemetry([
46
46
  { name: 'abort', tags: ['reason:incompatible_runtime'] },
47
47
  { name: 'abort.runtime', tags: [] }
48
- ])
48
+ ], undefined, {
49
+ result: 'abort',
50
+ result_class: 'incompatible_runtime',
51
+ result_reason: 'Incompatible runtime Node.js ' + version + ', supported runtimes: Node.js ' + engines.node
52
+ })
49
53
  log.info('Aborting application instrumentation due to incompatible_runtime.')
50
- log.info('Found incompatible runtime nodejs %s, Supported runtimes: nodejs %s.', version, engines.node)
54
+ log.info('Found incompatible runtime Node.js %s, Supported runtimes: Node.js %s.', version, engines.node)
51
55
  if (forced) {
52
56
  log.info('DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing.')
53
57
  }
@@ -56,7 +60,11 @@ function guard (fn) {
56
60
  if (!clobberBailout && (!initBailout || forced)) {
57
61
  // Ensure the instrumentation source is set for the current process and potential child processes.
58
62
  var result = fn()
59
- telemetry('complete', ['injection_forced:' + (forced && initBailout ? 'true' : 'false')])
63
+ telemetry('complete', ['injection_forced:' + (forced && initBailout ? 'true' : 'false')], {
64
+ result: 'success',
65
+ result_class: 'success',
66
+ result_reason: 'Successfully configured ddtrace package'
67
+ })
60
68
  log.info('Application instrumentation bootstrapping complete')
61
69
  return result
62
70
  }