dd-trace 5.64.0 → 5.66.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 (44) hide show
  1. package/package.json +8 -8
  2. package/packages/datadog-instrumentations/src/express.js +3 -7
  3. package/packages/datadog-instrumentations/src/graphql.js +10 -6
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  5. package/packages/datadog-instrumentations/src/helpers/register.js +10 -2
  6. package/packages/datadog-instrumentations/src/playwright.js +25 -9
  7. package/packages/datadog-instrumentations/src/prisma.js +8 -10
  8. package/packages/datadog-instrumentations/src/ws.js +136 -0
  9. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +30 -3
  10. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +9 -6
  11. package/packages/datadog-plugin-aws-sdk/src/util.js +61 -1
  12. package/packages/datadog-plugin-express/src/code_origin.js +11 -0
  13. package/packages/datadog-plugin-graphql/src/index.js +3 -0
  14. package/packages/datadog-plugin-ws/src/close.js +69 -0
  15. package/packages/datadog-plugin-ws/src/index.js +26 -0
  16. package/packages/datadog-plugin-ws/src/producer.js +60 -0
  17. package/packages/datadog-plugin-ws/src/receiver.js +70 -0
  18. package/packages/datadog-plugin-ws/src/server.js +79 -0
  19. package/packages/datadog-shimmer/src/shimmer.js +11 -2
  20. package/packages/dd-trace/src/appsec/blocking.js +29 -0
  21. package/packages/dd-trace/src/appsec/channels.js +4 -2
  22. package/packages/dd-trace/src/appsec/index.js +7 -2
  23. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +1 -0
  24. package/packages/dd-trace/src/appsec/rasp/index.js +25 -7
  25. package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
  26. package/packages/dd-trace/src/appsec/rasp/utils.js +13 -2
  27. package/packages/dd-trace/src/azure_metadata.js +5 -4
  28. package/packages/dd-trace/src/config.js +12 -0
  29. package/packages/dd-trace/src/datastreams/pathway.js +1 -1
  30. package/packages/dd-trace/src/dogstatsd.js +17 -20
  31. package/packages/dd-trace/src/guardrails/index.js +11 -3
  32. package/packages/dd-trace/src/guardrails/telemetry.js +15 -16
  33. package/packages/dd-trace/src/llmobs/tagger.js +2 -1
  34. package/packages/dd-trace/src/log/writer.js +1 -1
  35. package/packages/dd-trace/src/plugin_manager.js +8 -2
  36. package/packages/dd-trace/src/plugins/index.js +2 -1
  37. package/packages/dd-trace/src/plugins/util/ip_extractor.js +48 -45
  38. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
  39. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +175 -126
  40. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
  41. package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +30 -0
  42. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
  43. package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +30 -0
  44. package/packages/dd-trace/src/supported-configurations.json +3 -1
@@ -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
 
@@ -76,10 +76,11 @@ function buildMetadata () {
76
76
  }
77
77
 
78
78
  function getAzureAppMetadata () {
79
- // DD_AZURE_APP_SERVICES is an environment variable introduced by the .NET APM team and is set automatically for
80
- // anyone using the Datadog APM Extensions (.NET, Java, or Node) for Windows Azure App Services
81
- // See: https://github.com/DataDog/datadog-aas-extension/blob/01f94b5c28b7fa7a9ab264ca28bd4e03be603900/node/src/applicationHost.xdt#L20-L21
82
- if (getEnvironmentVariable('DD_AZURE_APP_SERVICES') !== undefined) {
79
+ // WEBSITE_SITE_NAME is the unique name of the website instance within Azure App Services. Its
80
+ // presence is used to determine if we are running in Azure App Service
81
+ // See equivalent in dd-trace-dotnet:
82
+ // https://github.com/DataDog/dd-trace-dotnet/blob/37030168b2996e549ba23231ae732874b53a37e6/tracer/src/Datadog.Trace/Util/EnvironmentHelpers.cs#L99-L155
83
+ if (getEnvironmentVariable('WEBSITE_SITE_NAME') !== undefined) {
83
84
  return buildMetadata()
84
85
  }
85
86
  }
@@ -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)
@@ -17,7 +17,7 @@ const CONTEXT_PROPAGATION_KEY_BASE64 = 'dd-pathway-ctx-base64'
17
17
  const logKeys = [CONTEXT_PROPAGATION_KEY, CONTEXT_PROPAGATION_KEY_BASE64]
18
18
 
19
19
  function shaHash (checkpointString) {
20
- const hash = crypto.createHash('md5').update(checkpointString).digest('hex').slice(0, 16)
20
+ const hash = crypto.createHash('sha256').update(checkpointString).digest('hex').slice(0, 16)
21
21
  return Buffer.from(hash, 'hex')
22
22
  }
23
23
 
@@ -156,23 +156,19 @@ class DogStatsDClient {
156
156
  return socket
157
157
  }
158
158
 
159
- static generateClientConfig (config = {}) {
159
+ static generateClientConfig (config) {
160
160
  const tags = []
161
161
 
162
162
  if (config.tags) {
163
- Object.keys(config.tags)
164
- .filter(key => typeof config.tags[key] === 'string')
165
- .filter(key => {
166
- // Skip runtime-id unless enabled as cardinality may be too high
167
- if (key !== 'runtime-id') return true
168
- return config.runtimeMetricsRuntimeId
169
- })
170
- .forEach(key => {
163
+ for (const [key, value] of Object.entries(config.tags)) {
164
+ // Skip runtime-id unless enabled as cardinality may be too high
165
+ if (typeof value === 'string' && (key !== 'runtime-id' || config.runtimeMetricsRuntimeId)) {
171
166
  // https://docs.datadoghq.com/tagging/#defining-tags
172
- const value = config.tags[key].replaceAll(/[^a-z0-9_:./-]/ig, '_')
167
+ const valueStripped = value.replaceAll(/[^a-z0-9_:./-]/ig, '_')
173
168
 
174
- tags.push(`${key}:${value}`)
175
- })
169
+ tags.push(`${key}:${valueStripped}`)
170
+ }
171
+ }
176
172
  }
177
173
 
178
174
  const clientConfig = {
@@ -216,7 +212,7 @@ class MetricsAggregationClient {
216
212
  this._histograms = new Map()
217
213
  }
218
214
 
219
- // TODO: Aggerate with a histogram and send the buckets to the client.
215
+ // TODO: Aggregate with a histogram and send the buckets to the client.
220
216
  distribution (name, value, tags) {
221
217
  this._client.distribution(name, value, tags)
222
218
  }
@@ -352,9 +348,10 @@ class MetricsAggregationClient {
352
348
  * @implements {DogStatsD}
353
349
  */
354
350
  class CustomMetrics {
351
+ #client
355
352
  constructor (config) {
356
353
  const clientConfig = DogStatsDClient.generateClientConfig(config)
357
- this._client = new MetricsAggregationClient(new DogStatsDClient(clientConfig))
354
+ this.#client = new MetricsAggregationClient(new DogStatsDClient(clientConfig))
358
355
 
359
356
  const flush = this.flush.bind(this)
360
357
 
@@ -365,27 +362,27 @@ class CustomMetrics {
365
362
  }
366
363
 
367
364
  increment (stat, value = 1, tags) {
368
- this._client.increment(stat, value, CustomMetrics.tagTranslator(tags))
365
+ this.#client.increment(stat, value, CustomMetrics.tagTranslator(tags))
369
366
  }
370
367
 
371
368
  decrement (stat, value = 1, tags) {
372
- this._client.decrement(stat, value, CustomMetrics.tagTranslator(tags))
369
+ this.#client.decrement(stat, value, CustomMetrics.tagTranslator(tags))
373
370
  }
374
371
 
375
372
  gauge (stat, value, tags) {
376
- this._client.gauge(stat, value, CustomMetrics.tagTranslator(tags))
373
+ this.#client.gauge(stat, value, CustomMetrics.tagTranslator(tags))
377
374
  }
378
375
 
379
376
  distribution (stat, value, tags) {
380
- this._client.distribution(stat, value, CustomMetrics.tagTranslator(tags))
377
+ this.#client.distribution(stat, value, CustomMetrics.tagTranslator(tags))
381
378
  }
382
379
 
383
380
  histogram (stat, value, tags) {
384
- this._client.histogram(stat, value, CustomMetrics.tagTranslator(tags))
381
+ this.#client.histogram(stat, value, CustomMetrics.tagTranslator(tags))
385
382
  }
386
383
 
387
384
  flush () {
388
- return this._client.flush()
385
+ return this.#client.flush()
389
386
  }
390
387
 
391
388
  /**