dd-trace 5.65.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 (40) 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/config.js +12 -0
  28. package/packages/dd-trace/src/guardrails/index.js +11 -3
  29. package/packages/dd-trace/src/guardrails/telemetry.js +15 -16
  30. package/packages/dd-trace/src/llmobs/tagger.js +2 -1
  31. package/packages/dd-trace/src/log/writer.js +1 -1
  32. package/packages/dd-trace/src/plugin_manager.js +8 -2
  33. package/packages/dd-trace/src/plugins/index.js +2 -1
  34. package/packages/dd-trace/src/plugins/util/ip_extractor.js +48 -45
  35. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
  36. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
  37. package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +30 -0
  38. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
  39. package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +30 -0
  40. package/packages/dd-trace/src/supported-configurations.json +3 -0
@@ -145,7 +145,10 @@ module.exports = class PluginManager {
145
145
  ciVisAgentlessLogSubmissionEnabled,
146
146
  isTestDynamicInstrumentationEnabled,
147
147
  isServiceUserProvided,
148
- middlewareTracingEnabled
148
+ middlewareTracingEnabled,
149
+ traceWebsocketMessagesEnabled,
150
+ traceWebsocketMessagesInheritSampling,
151
+ traceWebsocketMessagesSeparateTraces
149
152
  } = this._tracerConfig
150
153
 
151
154
  const sharedConfig = {
@@ -160,7 +163,10 @@ module.exports = class PluginManager {
160
163
  ciVisibilityTestSessionName,
161
164
  ciVisAgentlessLogSubmissionEnabled,
162
165
  isTestDynamicInstrumentationEnabled,
163
- isServiceUserProvided
166
+ isServiceUserProvided,
167
+ traceWebsocketMessagesEnabled,
168
+ traceWebsocketMessagesInheritSampling,
169
+ traceWebsocketMessagesSeparateTraces
164
170
  }
165
171
 
166
172
  if (logInjection !== undefined) {
@@ -102,5 +102,6 @@ module.exports = {
102
102
  get sharedb () { return require('../../../datadog-plugin-sharedb/src') },
103
103
  get tedious () { return require('../../../datadog-plugin-tedious/src') },
104
104
  get undici () { return require('../../../datadog-plugin-undici/src') },
105
- get winston () { return require('../../../datadog-plugin-winston/src') }
105
+ get winston () { return require('../../../datadog-plugin-winston/src') },
106
+ get ws () { return require('../../../datadog-plugin-ws/src') }
106
107
  }
@@ -1,6 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { BlockList } = require('net')
4
3
  const net = require('net')
5
4
 
6
5
  const FORWARED_HEADER_NAME = 'forwarded'
@@ -32,7 +31,7 @@ const privateCIDRs = [
32
31
  'fd00::/8'
33
32
  ]
34
33
 
35
- const privateIPMatcher = new BlockList()
34
+ const privateIPMatcher = new net.BlockList()
36
35
 
37
36
  for (const cidr of privateCIDRs) {
38
37
  const [address, prefix] = cidr.split('/')
@@ -45,7 +44,11 @@ function extractIp (config, req) {
45
44
  if (config.clientIpHeader) {
46
45
  if (!headers) return
47
46
 
48
- const ip = findFirstIp(headers[config.clientIpHeader])
47
+ const ipHeaderName = config.clientIpHeader
48
+ const header = headers[ipHeaderName]
49
+ if (typeof header !== 'string') return
50
+
51
+ const ip = findFirstIp(header, ipHeaderName === FORWARED_HEADER_NAME)
49
52
  return ip.public || ip.private
50
53
  }
51
54
 
@@ -53,12 +56,14 @@ function extractIp (config, req) {
53
56
  if (headers) {
54
57
  for (const ipHeaderName of ipHeaderList) {
55
58
  const header = headers[ipHeaderName]
56
- const firstIp = ipHeaderName === FORWARED_HEADER_NAME ? findFirstIpForwardedFormat(header) : findFirstIp(header)
59
+ if (typeof header !== 'string') continue
60
+
61
+ const ip = findFirstIp(header, ipHeaderName === FORWARED_HEADER_NAME)
57
62
 
58
- if (firstIp.public) {
59
- return firstIp.public
60
- } else if (!firstPrivateIp && firstIp.private) {
61
- firstPrivateIp = firstIp.private
63
+ if (ip.public) {
64
+ return ip.public
65
+ } else if (!firstPrivateIp && ip.private) {
66
+ firstPrivateIp = ip.private
62
67
  }
63
68
  }
64
69
  }
@@ -66,25 +71,39 @@ function extractIp (config, req) {
66
71
  return firstPrivateIp || req.socket?.remoteAddress
67
72
  }
68
73
 
69
- function isPublicIp (ip, type) {
70
- return !privateIPMatcher.check(ip, type === 6 ? 'ipv6' : 'ipv4')
71
- }
72
-
73
- function findFirstIp (str) {
74
+ function findFirstIp (str, isForwardedHeader) {
74
75
  const result = {}
75
76
  if (!str) return result
76
77
 
77
78
  const splitted = str.split(',')
78
79
 
79
- for (const part of splitted) {
80
- const chunk = part.trim()
80
+ for (let chunk of splitted) {
81
+ if (isForwardedHeader) {
82
+ // find "for" directive
83
+ const forDirective = chunk.split(';').find(subchunk => subchunk.trim().toLowerCase().startsWith('for='))
84
+
85
+ // if found remove the "for=" prefix
86
+ // else keep going as is
87
+ if (forDirective) {
88
+ chunk = forDirective.slice(4)
89
+ }
90
+ }
91
+
92
+ chunk = chunk.trim()
93
+
94
+ // trim potential double quotes
95
+ if (chunk.startsWith('"') && chunk.endsWith('"')) {
96
+ chunk = chunk.slice(1, -1).trim()
97
+ }
81
98
 
82
- // TODO: strip port and interface data ?
99
+ // TODO: when min node support is v24 we can instead use net.SocketAddress.parse()
100
+ chunk = cleanIp(chunk)
101
+ if (!chunk) continue
83
102
 
84
103
  const type = net.isIP(chunk)
85
104
  if (!type) continue
86
105
 
87
- if (isPublicIp(chunk, type)) {
106
+ if (!privateIPMatcher.check(chunk, type === 6 ? 'ipv6' : 'ipv4')) {
88
107
  // it's public, return it immediately
89
108
  result.public = chunk
90
109
  return result
@@ -97,37 +116,21 @@ function findFirstIp (str) {
97
116
  return result
98
117
  }
99
118
 
100
- const forwardedForRegexp = /for="?\[?(([0-9]+\.)+[0-9]+|[0-9a-f:]*:[0-9a-f]*)/i
101
- const forwardedByRegexp = /by="?\[?(([0-9]+\.)+[0-9]+|[0-9a-f:]*:[0-9a-f]*)/i
102
- const forwardedRegexps = [forwardedForRegexp, forwardedByRegexp]
103
-
104
- function findFirstIpForwardedFormat (str) {
105
- const result = {}
106
- if (!str) return result
107
-
108
- const splitted = str.split(',')
109
-
110
- for (const part of splitted) {
111
- const chunk = part.trim()
112
-
113
- for (const regex of forwardedRegexps) {
114
- const ip = regex.exec(chunk)?.[1]
115
-
116
- const type = net.isIP(ip)
117
- if (!type) continue
118
-
119
- if (isPublicIp(ip, type)) {
120
- // it's public, return it immediately
121
- result.public = ip
122
- return result
123
- }
119
+ function cleanIp (input) {
120
+ const colonIndex = input.indexOf(':')
121
+ if (colonIndex !== -1 && input.includes('.')) {
122
+ // treat it as ipv4 with port
123
+ return input.slice(0, colonIndex).trim()
124
+ }
124
125
 
125
- // it's private, only save the first one found
126
- if (!result.private) result.private = ip
127
- }
126
+ const closingBracketIndex = input.indexOf(']')
127
+ if (closingBracketIndex !== -1 && input.startsWith('[')) {
128
+ // treat as ipv6 with brackets
129
+ return input.slice(1, closingBracketIndex).trim()
128
130
  }
129
131
 
130
- return result
132
+ // no need to clean it
133
+ return input
131
134
  }
132
135
 
133
136
  module.exports = {
@@ -13,6 +13,7 @@ const {
13
13
  getThreadLabels,
14
14
  encodeProfileAsync
15
15
  } = require('./shared')
16
+ const TRACE_ENDPOINT_LABEL = 'trace endpoint'
16
17
 
17
18
  const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require('../webspan-utils')
18
19
 
@@ -251,7 +252,12 @@ class NativeWallProfiler {
251
252
  this._enter()
252
253
  this._lastSampleCount = 0
253
254
  }
254
- const profile = this._pprof.time.stop(restart, this._generateLabels)
255
+
256
+ // Mark thread labels and trace endpoint label as good deduplication candidates
257
+ const lowCardinalityLabels = Object.keys(getThreadLabels())
258
+ lowCardinalityLabels.push(TRACE_ENDPOINT_LABEL)
259
+
260
+ const profile = this._pprof.time.stop(restart, this._generateLabels, lowCardinalityLabels)
255
261
 
256
262
  if (restart) {
257
263
  const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
@@ -312,10 +318,10 @@ class NativeWallProfiler {
312
318
  labels[LOCAL_ROOT_SPAN_ID_LABEL] = rootSpanId
313
319
  }
314
320
  if (webTags !== undefined && Object.keys(webTags).length !== 0) {
315
- labels['trace endpoint'] = endpointNameFromTags(webTags)
321
+ labels[TRACE_ENDPOINT_LABEL] = endpointNameFromTags(webTags)
316
322
  } else if (endpoint) {
317
323
  // fallback to endpoint computed when sample was taken
318
- labels['trace endpoint'] = endpoint
324
+ labels[TRACE_ENDPOINT_LABEL] = endpoint
319
325
  }
320
326
 
321
327
  return labels
@@ -6,5 +6,6 @@ const storage = require('./storage')
6
6
  const graphql = require('./graphql')
7
7
  const web = require('./web')
8
8
  const serverless = require('./serverless')
9
+ const websocket = require('./websocket')
9
10
 
10
- module.exports = new SchemaDefinition({ messaging, storage, web, graphql, serverless })
11
+ module.exports = new SchemaDefinition({ messaging, storage, web, graphql, serverless, websocket })
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ const websocket = {
4
+ request: {
5
+ ws: {
6
+ opName: () => 'web.request',
7
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
8
+ }
9
+ },
10
+ producer: {
11
+ ws: {
12
+ opName: () => 'websocket.send',
13
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
14
+ }
15
+ },
16
+ consumer: {
17
+ ws: {
18
+ opName: () => 'websocket.receive',
19
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
20
+ }
21
+ },
22
+ close: {
23
+ ws: {
24
+ opName: () => 'websocket.close',
25
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
26
+ }
27
+ }
28
+ }
29
+
30
+ module.exports = websocket
@@ -6,5 +6,6 @@ const storage = require('./storage')
6
6
  const graphql = require('./graphql')
7
7
  const web = require('./web')
8
8
  const serverless = require('./serverless')
9
+ const websocket = require('./websocket')
9
10
 
10
- module.exports = new SchemaDefinition({ messaging, storage, web, graphql, serverless })
11
+ module.exports = new SchemaDefinition({ messaging, storage, web, graphql, serverless, websocket })
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ const websocket = {
4
+ request: {
5
+ ws: {
6
+ opName: () => 'web.request',
7
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
8
+ }
9
+ },
10
+ producer: {
11
+ ws: {
12
+ opName: () => 'websocket.send',
13
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
14
+ }
15
+ },
16
+ consumer: {
17
+ ws: {
18
+ opName: () => 'websocket.receive',
19
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
20
+ }
21
+ },
22
+ close: {
23
+ ws: {
24
+ opName: () => 'websocket.close',
25
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
26
+ }
27
+ }
28
+ }
29
+
30
+ module.exports = websocket
@@ -414,6 +414,9 @@
414
414
  "DD_TRACE_VITEST_ENABLED": ["A"],
415
415
  "DD_TRACE_VITEST_RUNNER_ENABLED": ["A"],
416
416
  "DD_TRACE_VM_ENABLED": ["A"],
417
+ "DD_TRACE_WEBSOCKET_MESSAGES_ENABLED":["A"],
418
+ "DD_TRACE_WEBSOCKET_MESSAGES_INHERIT_SAMPLING": ["A"],
419
+ "DD_TRACE_WEBSOCKET_MESSAGES_SEPARATE_TRACES":["A"],
417
420
  "DD_TRACE_WHEN_ENABLED": ["A"],
418
421
  "DD_TRACE_WINSTON_ENABLED": ["A"],
419
422
  "DD_TRACE_WORKERPOOL_ENABLED": ["A"],