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.
- package/package.json +8 -8
- package/packages/datadog-instrumentations/src/express.js +3 -7
- package/packages/datadog-instrumentations/src/graphql.js +10 -6
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -2
- package/packages/datadog-instrumentations/src/playwright.js +25 -9
- package/packages/datadog-instrumentations/src/prisma.js +8 -10
- package/packages/datadog-instrumentations/src/ws.js +136 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +30 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +9 -6
- package/packages/datadog-plugin-aws-sdk/src/util.js +61 -1
- package/packages/datadog-plugin-express/src/code_origin.js +11 -0
- package/packages/datadog-plugin-graphql/src/index.js +3 -0
- package/packages/datadog-plugin-ws/src/close.js +69 -0
- package/packages/datadog-plugin-ws/src/index.js +26 -0
- package/packages/datadog-plugin-ws/src/producer.js +60 -0
- package/packages/datadog-plugin-ws/src/receiver.js +70 -0
- package/packages/datadog-plugin-ws/src/server.js +79 -0
- package/packages/datadog-shimmer/src/shimmer.js +11 -2
- package/packages/dd-trace/src/appsec/blocking.js +29 -0
- package/packages/dd-trace/src/appsec/channels.js +4 -2
- package/packages/dd-trace/src/appsec/index.js +7 -2
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +1 -0
- package/packages/dd-trace/src/appsec/rasp/index.js +25 -7
- package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/utils.js +13 -2
- package/packages/dd-trace/src/azure_metadata.js +5 -4
- package/packages/dd-trace/src/config.js +12 -0
- package/packages/dd-trace/src/datastreams/pathway.js +1 -1
- package/packages/dd-trace/src/dogstatsd.js +17 -20
- package/packages/dd-trace/src/guardrails/index.js +11 -3
- package/packages/dd-trace/src/guardrails/telemetry.js +15 -16
- package/packages/dd-trace/src/llmobs/tagger.js +2 -1
- package/packages/dd-trace/src/log/writer.js +1 -1
- package/packages/dd-trace/src/plugin_manager.js +8 -2
- package/packages/dd-trace/src/plugins/index.js +2 -1
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +48 -45
- package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +175 -126
- package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +30 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +30 -0
- package/packages/dd-trace/src/supported-configurations.json +3 -1
|
@@ -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
|
|
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
|
}
|
|
@@ -47,7 +47,7 @@ function shouldSend (point) {
|
|
|
47
47
|
return true
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
function sendTelemetry (name, tags) {
|
|
50
|
+
function sendTelemetry (name, tags, resultMetadata) {
|
|
51
51
|
var points = name
|
|
52
52
|
if (typeof name === 'string') {
|
|
53
53
|
points = [{ name: name, tags: tags || [] }]
|
|
@@ -62,32 +62,31 @@ function sendTelemetry (name, tags) {
|
|
|
62
62
|
if (points.length === 0) {
|
|
63
63
|
return
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
// Update metadata with provided result metadata
|
|
67
|
+
var currentMetadata = {}
|
|
68
|
+
for (var key in metadata) {
|
|
69
|
+
currentMetadata[key] = metadata[key]
|
|
70
|
+
}
|
|
71
|
+
if (resultMetadata) {
|
|
72
|
+
for (var resultKey in resultMetadata) {
|
|
73
|
+
currentMetadata[resultKey] = resultMetadata[resultKey]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
var proc = spawn(process.env.DD_TELEMETRY_FORWARDER_PATH, ['library_entrypoint'], {
|
|
66
78
|
stdio: 'pipe'
|
|
67
79
|
})
|
|
68
80
|
proc.on('error', function () {
|
|
69
81
|
log.error('Failed to spawn telemetry forwarder')
|
|
70
|
-
metadata.result = 'error'
|
|
71
|
-
metadata.result_class = 'internal_error'
|
|
72
|
-
metadata.result_reason = 'Failed to spawn telemetry forwarder'
|
|
73
82
|
})
|
|
74
83
|
proc.on('exit', function (code) {
|
|
75
|
-
if (code
|
|
76
|
-
metadata.result = 'success'
|
|
77
|
-
metadata.result_class = 'success'
|
|
78
|
-
metadata.result_reason = 'Successfully configured ddtrace package'
|
|
79
|
-
} else {
|
|
84
|
+
if (code !== 0) {
|
|
80
85
|
log.error('Telemetry forwarder exited with code', code)
|
|
81
|
-
metadata.result = 'error'
|
|
82
|
-
metadata.result_class = 'internal_error'
|
|
83
|
-
metadata.result_reason = 'Telemetry forwarder exited with code ' + code
|
|
84
86
|
}
|
|
85
87
|
})
|
|
86
88
|
proc.stdin.on('error', function () {
|
|
87
89
|
log.error('Failed to write telemetry data to telemetry forwarder')
|
|
88
|
-
metadata.result = 'error'
|
|
89
|
-
metadata.result_class = 'internal_error'
|
|
90
|
-
metadata.result_reason = 'Failed to write telemetry data to telemetry forwarder'
|
|
91
90
|
})
|
|
92
|
-
proc.stdin.end(JSON.stringify({ metadata:
|
|
91
|
+
proc.stdin.end(JSON.stringify({ metadata: currentMetadata, points: points }))
|
|
93
92
|
}
|
|
@@ -65,7 +65,8 @@ class LLMObsTagger {
|
|
|
65
65
|
mlApp ||
|
|
66
66
|
registry.get(parent)?.[ML_APP] ||
|
|
67
67
|
span.context()._trace.tags[PROPAGATED_ML_APP_KEY] ||
|
|
68
|
-
this._config.llmobs.mlApp
|
|
68
|
+
this._config.llmobs.mlApp ||
|
|
69
|
+
this._config.service // this should always have a default
|
|
69
70
|
|
|
70
71
|
if (!spanMlApp) {
|
|
71
72
|
throw new Error(
|
|
@@ -71,7 +71,7 @@ function setStackTraceLimitFunction (fn) {
|
|
|
71
71
|
function onError (err) {
|
|
72
72
|
const { formatted, cause } = getErrorLog(err)
|
|
73
73
|
|
|
74
|
-
// calling twice logger.error() because Error cause is only available in
|
|
74
|
+
// calling twice logger.error() because Error cause is only available in Node.js v16.9.0
|
|
75
75
|
// TODO: replace it with Error(message, { cause }) when cause has broad support
|
|
76
76
|
if (formatted) {
|
|
77
77
|
withNoop(() => {
|
|
@@ -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
|
|
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
|
-
|
|
59
|
+
if (typeof header !== 'string') continue
|
|
60
|
+
|
|
61
|
+
const ip = findFirstIp(header, ipHeaderName === FORWARED_HEADER_NAME)
|
|
57
62
|
|
|
58
|
-
if (
|
|
59
|
-
return
|
|
60
|
-
} else if (!firstPrivateIp &&
|
|
61
|
-
firstPrivateIp =
|
|
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
|
|
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 (
|
|
80
|
-
|
|
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:
|
|
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 (
|
|
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
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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[
|
|
321
|
+
labels[TRACE_ENDPOINT_LABEL] = endpointNameFromTags(webTags)
|
|
316
322
|
} else if (endpoint) {
|
|
317
323
|
// fallback to endpoint computed when sample was taken
|
|
318
|
-
labels[
|
|
324
|
+
labels[TRACE_ENDPOINT_LABEL] = endpoint
|
|
319
325
|
}
|
|
320
326
|
|
|
321
327
|
return labels
|