dd-trace 4.12.0 → 4.13.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/README.md +4 -9
- package/package.json +2 -2
- package/packages/datadog-esbuild/index.js +20 -6
- package/packages/datadog-instrumentations/src/next.js +86 -2
- package/packages/datadog-instrumentations/src/openai.js +1 -1
- package/packages/datadog-plugin-graphql/src/index.js +3 -3
- package/packages/dd-trace/src/exporters/common/request.js +13 -4
- package/packages/dd-trace/src/profiling/config.js +3 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +16 -2
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/dd-trace)
|
|
4
4
|
[](https://www.npmjs.com/package/dd-trace/v/latest-node12)
|
|
5
|
-
[](https://www.npmjs.com/package/dd-trace/v/latest-node12)
|
|
6
|
-
[](https://www.npmjs.com/package/dd-trace/v/dev)
|
|
7
5
|
[](https://codecov.io/gh/DataDog/dd-trace-js)
|
|
8
6
|
|
|
9
7
|
<img align="right" src="https://user-images.githubusercontent.com/551402/208212084-1d0c07e2-4135-4c61-b2da-8f2fddbc66ed.png" alt="Bits the dog JavaScript" width="200px"/>
|
|
@@ -28,12 +26,12 @@ Most of the documentation for `dd-trace` is available on these webpages:
|
|
|
28
26
|
| Release Line | Latest Version | Node.js | Status |Initial Release | End of Life |
|
|
29
27
|
| :---: | :---: | :---: | :---: | :---: | :---: |
|
|
30
28
|
| [`v1`](https://github.com/DataDog/dd-trace-js/tree/v1.x) |  | `>= v12` | **End of Life** | 2021-07-13 | 2022-02-25 |
|
|
31
|
-
| [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) |  | `>= v12` | **
|
|
29
|
+
| [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) |  | `>= v12` | **End of Life** | 2022-01-28 | 2023-08-15 |
|
|
32
30
|
| [`v3`](https://github.com/DataDog/dd-trace-js/tree/v3.x) |  | `>= v14` | **Maintenance** | 2022-08-15 | 2024-05-15 |
|
|
33
31
|
| [`v4`](https://github.com/DataDog/dd-trace-js/tree/v4.x) |  | `>= v16` | **Current** | 2023-05-12 | Unknown |
|
|
34
32
|
|
|
35
|
-
We currently maintain
|
|
36
|
-
Features and bug fixes that are merged are released to the `v4` line and, if appropriate, also the `
|
|
33
|
+
We currently maintain two release lines, namely `v3` and `v4`.
|
|
34
|
+
Features and bug fixes that are merged are released to the `v4` line and, if appropriate, also the `v3` line.
|
|
37
35
|
|
|
38
36
|
For any new projects it is recommended to use the `v4` release line:
|
|
39
37
|
|
|
@@ -42,15 +40,12 @@ $ npm install dd-trace
|
|
|
42
40
|
$ yarn add dd-trace
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
However, existing projects that already use the `
|
|
43
|
+
However, existing projects that already use the `v3` release line, or projects that need to support EOL versions of Node.js, may continue to use these release lines.
|
|
46
44
|
This is done by specifying the version when installing the package.
|
|
47
|
-
Note that we also publish to npm using a `latest-node12` and `latest-node14` tag that can also be used for install:
|
|
48
45
|
|
|
49
46
|
```sh
|
|
50
47
|
$ npm install dd-trace@3
|
|
51
48
|
$ yarn add dd-trace@3
|
|
52
|
-
$ npm install dd-trace@latest-node14
|
|
53
|
-
$ yarn add dd-trace@latest-node14
|
|
54
49
|
```
|
|
55
50
|
|
|
56
51
|
Any backwards-breaking functionality that is introduced into the library will result in an increase of the major version of the library and therefore a new release line.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@datadog/native-iast-rewriter": "2.0.1",
|
|
72
72
|
"@datadog/native-iast-taint-tracking": "1.5.0",
|
|
73
73
|
"@datadog/native-metrics": "^2.0.0",
|
|
74
|
-
"@datadog/pprof": "3.
|
|
74
|
+
"@datadog/pprof": "3.2.0",
|
|
75
75
|
"@datadog/sketches-js": "^2.1.0",
|
|
76
76
|
"@opentelemetry/api": "^1.0.0",
|
|
77
77
|
"@opentelemetry/core": "^1.14.0",
|
|
@@ -53,10 +53,25 @@ module.exports.setup = function (build) {
|
|
|
53
53
|
const externalModules = new Set(build.initialOptions.external || [])
|
|
54
54
|
build.onResolve({ filter: /.*/ }, args => {
|
|
55
55
|
if (externalModules.has(args.path)) {
|
|
56
|
+
// Internal Node.js packages will still be instrumented via require()
|
|
56
57
|
if (DEBUG) console.log(`EXTERNAL: ${args.path}`)
|
|
57
58
|
return
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
// TODO: Should this also check for namespace === 'file'?
|
|
62
|
+
if (args.path.startsWith('.') && !args.importer.includes('node_modules/')) {
|
|
63
|
+
// This is local application code, not an instrumented package
|
|
64
|
+
if (DEBUG) console.log(`LOCAL: ${args.path}`)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// TODO: Should this also check for namespace === 'file'?
|
|
69
|
+
if (args.path.startsWith('@') && !args.importer.includes('node_modules/')) {
|
|
70
|
+
// This is the Next.js convention for loading local files
|
|
71
|
+
if (DEBUG) console.log(`@LOCAL: ${args.path}`)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
let fullPathToModule
|
|
61
76
|
try {
|
|
62
77
|
fullPathToModule = dotFriendlyResolve(args.path, args.resolveDir)
|
|
@@ -65,12 +80,11 @@ module.exports.setup = function (build) {
|
|
|
65
80
|
return
|
|
66
81
|
}
|
|
67
82
|
const extracted = extractPackageAndModulePath(fullPathToModule)
|
|
68
|
-
const packageName = args.path
|
|
69
83
|
|
|
70
84
|
const internal = builtins.has(args.path)
|
|
71
85
|
|
|
72
86
|
if (args.namespace === 'file' && (
|
|
73
|
-
modulesOfInterest.has(
|
|
87
|
+
modulesOfInterest.has(args.path) || modulesOfInterest.has(`${extracted.pkg}/${extracted.path}`))
|
|
74
88
|
) {
|
|
75
89
|
// The file namespace is used when requiring files from disk in userland
|
|
76
90
|
|
|
@@ -90,7 +104,7 @@ module.exports.setup = function (build) {
|
|
|
90
104
|
|
|
91
105
|
const packageJson = require(pathToPackageJson)
|
|
92
106
|
|
|
93
|
-
if (DEBUG) console.log(`RESOLVE: ${
|
|
107
|
+
if (DEBUG) console.log(`RESOLVE: ${args.path}@${packageJson.version}`)
|
|
94
108
|
|
|
95
109
|
// https://esbuild.github.io/plugins/#on-resolve-arguments
|
|
96
110
|
return {
|
|
@@ -101,17 +115,17 @@ module.exports.setup = function (build) {
|
|
|
101
115
|
pkg: extracted.pkg,
|
|
102
116
|
path: extracted.path,
|
|
103
117
|
full: fullPathToModule,
|
|
104
|
-
raw:
|
|
118
|
+
raw: args.path,
|
|
105
119
|
internal
|
|
106
120
|
}
|
|
107
121
|
}
|
|
108
122
|
} else if (args.namespace === NAMESPACE) {
|
|
109
123
|
// The datadog namespace is used when requiring files that are injected during the onLoad stage
|
|
110
124
|
|
|
111
|
-
if (builtins.has(
|
|
125
|
+
if (builtins.has(args.path)) return
|
|
112
126
|
|
|
113
127
|
return {
|
|
114
|
-
path: require.resolve(
|
|
128
|
+
path: require.resolve(args.path, { paths: [ args.resolveDir ] }),
|
|
115
129
|
namespace: 'file'
|
|
116
130
|
}
|
|
117
131
|
}
|
|
@@ -12,6 +12,7 @@ const errorChannel = channel('apm:next:request:error')
|
|
|
12
12
|
const pageLoadChannel = channel('apm:next:page:load')
|
|
13
13
|
|
|
14
14
|
const requests = new WeakSet()
|
|
15
|
+
const requestToNextjsPagePath = new WeakMap()
|
|
15
16
|
|
|
16
17
|
function wrapHandleRequest (handleRequest) {
|
|
17
18
|
return function (req, res, pathname, query) {
|
|
@@ -132,6 +133,57 @@ function instrument (req, res, handler) {
|
|
|
132
133
|
})
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
function wrapSetupServerWorker (setupServerWorker) {
|
|
137
|
+
return function (requestHandler) {
|
|
138
|
+
arguments[0] = shimmer.wrap(requestHandler, wrapRequestHandler(requestHandler))
|
|
139
|
+
return setupServerWorker.apply(this, arguments)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function wrapInitialize (initialize) {
|
|
144
|
+
return async function () {
|
|
145
|
+
const result = await initialize.apply(this, arguments)
|
|
146
|
+
if (Array.isArray(result)) {
|
|
147
|
+
const requestHandler = result[0]
|
|
148
|
+
result[0] = shimmer.wrap(requestHandler, wrapRequestHandler(requestHandler))
|
|
149
|
+
}
|
|
150
|
+
return result
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function wrapRequestHandler (requestHandler) {
|
|
155
|
+
return function (req, res) {
|
|
156
|
+
return instrument(req, res, async () => {
|
|
157
|
+
const result = await requestHandler.apply(this, arguments) // apply here first to get page path association
|
|
158
|
+
|
|
159
|
+
const page = requestToNextjsPagePath.get(req)
|
|
160
|
+
if (page && pageLoadChannel.hasSubscribers) pageLoadChannel.publish({ page })
|
|
161
|
+
|
|
162
|
+
return result
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// these two functions make sure we get path groups for routes in standalone,
|
|
168
|
+
// as it doesn't route through `next-server`/`base-server`
|
|
169
|
+
function wrapGetResolveRoutes (getResolveRoutes) {
|
|
170
|
+
return function () {
|
|
171
|
+
const result = getResolveRoutes.apply(this, arguments)
|
|
172
|
+
return shimmer.wrap(result, wrapResolveRoutes(result))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function wrapResolveRoutes (resolveRoutes) {
|
|
177
|
+
return async function (req) {
|
|
178
|
+
const result = await resolveRoutes.apply(this, arguments)
|
|
179
|
+
if (result && result.matchedOutput) {
|
|
180
|
+
const path = result.matchedOutput.itemPath
|
|
181
|
+
requestToNextjsPagePath.set(req, path)
|
|
182
|
+
}
|
|
183
|
+
return result
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
135
187
|
function finish (ctx, result, err) {
|
|
136
188
|
if (err) {
|
|
137
189
|
ctx.error = err
|
|
@@ -147,11 +199,27 @@ function finish (ctx, result, err) {
|
|
|
147
199
|
return result
|
|
148
200
|
}
|
|
149
201
|
|
|
202
|
+
addHook({
|
|
203
|
+
name: 'next',
|
|
204
|
+
versions: ['>=13.4.13'],
|
|
205
|
+
file: 'dist/server/lib/router-utils/resolve-routes.js'
|
|
206
|
+
}, resolveRoutesModule => shimmer.wrap(resolveRoutesModule, 'getResolveRoutes', wrapGetResolveRoutes))
|
|
207
|
+
|
|
208
|
+
addHook({
|
|
209
|
+
name: 'next',
|
|
210
|
+
versions: ['13.4.13'],
|
|
211
|
+
file: 'dist/server/lib/setup-server-worker.js'
|
|
212
|
+
}, setupServerWorker => shimmer.wrap(setupServerWorker, 'initializeServerWorker', wrapSetupServerWorker))
|
|
213
|
+
|
|
214
|
+
addHook({
|
|
215
|
+
name: 'next',
|
|
216
|
+
versions: ['>=13.4.15'],
|
|
217
|
+
file: 'dist/server/lib/router-server.js'
|
|
218
|
+
}, routerServer => shimmer.wrap(routerServer, 'initialize', wrapInitialize))
|
|
219
|
+
|
|
150
220
|
addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js' }, nextServer => {
|
|
151
221
|
const Server = nextServer.default
|
|
152
222
|
|
|
153
|
-
shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
|
|
154
|
-
shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
|
|
155
223
|
shimmer.wrap(Server.prototype, 'renderToResponse', wrapRenderToResponse)
|
|
156
224
|
shimmer.wrap(Server.prototype, 'renderErrorToResponse', wrapRenderErrorToResponse)
|
|
157
225
|
shimmer.wrap(Server.prototype, 'findPageComponents', wrapFindPageComponents)
|
|
@@ -159,6 +227,22 @@ addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js'
|
|
|
159
227
|
return nextServer
|
|
160
228
|
})
|
|
161
229
|
|
|
230
|
+
// these functions wrapped in all versions above 13.2 except:
|
|
231
|
+
// 13.4.13 due to tests failing when these functions are wrapped
|
|
232
|
+
// 13.4.14 due to it not being in the NPM registry/officially released
|
|
233
|
+
addHook({
|
|
234
|
+
name: 'next',
|
|
235
|
+
versions: ['>=13.2 <13.4.13', '>=13.4.15'],
|
|
236
|
+
file: 'dist/server/next-server.js'
|
|
237
|
+
}, nextServer => {
|
|
238
|
+
const Server = nextServer.default
|
|
239
|
+
|
|
240
|
+
shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
|
|
241
|
+
shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
|
|
242
|
+
|
|
243
|
+
return nextServer
|
|
244
|
+
})
|
|
245
|
+
|
|
162
246
|
addHook({ name: 'next', versions: ['>=11.1 <13.2'], file: 'dist/server/next-server.js' }, nextServer => {
|
|
163
247
|
const Server = nextServer.default
|
|
164
248
|
|
|
@@ -10,7 +10,7 @@ const startCh = channel('apm:openai:request:start')
|
|
|
10
10
|
const finishCh = channel('apm:openai:request:finish')
|
|
11
11
|
const errorCh = channel('apm:openai:request:error')
|
|
12
12
|
|
|
13
|
-
addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0'] }, exports => {
|
|
13
|
+
addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0 <4'] }, exports => {
|
|
14
14
|
const methodNames = Object.getOwnPropertyNames(exports.OpenAIApi.prototype)
|
|
15
15
|
methodNames.shift() // remove leading 'constructor' method
|
|
16
16
|
|
|
@@ -56,9 +56,9 @@ function getVariablesFilter (config) {
|
|
|
56
56
|
|
|
57
57
|
function getHooks (config) {
|
|
58
58
|
const noop = () => { }
|
|
59
|
-
const execute =
|
|
60
|
-
const parse =
|
|
61
|
-
const validate =
|
|
59
|
+
const execute = config.hooks?.execute || noop
|
|
60
|
+
const parse = config.hooks?.parse || noop
|
|
61
|
+
const validate = config.hooks?.validate || noop
|
|
62
62
|
|
|
63
63
|
return { execute, parse, validate }
|
|
64
64
|
}
|
|
@@ -42,10 +42,19 @@ function urlToOptions (url) {
|
|
|
42
42
|
return options
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function fromUrlString (
|
|
46
|
-
|
|
47
|
-
? urlToOptions(new URL(
|
|
48
|
-
: urlParse(
|
|
45
|
+
function fromUrlString (urlString) {
|
|
46
|
+
const url = typeof urlToHttpOptions === 'function'
|
|
47
|
+
? urlToOptions(new URL(urlString))
|
|
48
|
+
: urlParse(urlString)
|
|
49
|
+
|
|
50
|
+
// Add the 'hostname' back if we're using named pipes
|
|
51
|
+
if (url.protocol === 'unix:' && url.host === '.') {
|
|
52
|
+
const udsPath = urlString.replace(/^unix:/, '')
|
|
53
|
+
url.path = udsPath
|
|
54
|
+
url.pathname = udsPath
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return url
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
function request (data, options, callback) {
|
|
@@ -31,6 +31,7 @@ class Config {
|
|
|
31
31
|
DD_PROFILING_UPLOAD_PERIOD,
|
|
32
32
|
DD_PROFILING_PPROF_PREFIX,
|
|
33
33
|
DD_PROFILING_HEAP_ENABLED,
|
|
34
|
+
DD_PROFILING_V8_PROFILER_BUG_WORKAROUND,
|
|
34
35
|
DD_PROFILING_WALLTIME_ENABLED,
|
|
35
36
|
DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED,
|
|
36
37
|
DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
|
|
@@ -76,7 +77,8 @@ class Config {
|
|
|
76
77
|
this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
|
|
77
78
|
this.endpointCollectionEnabled = endpointCollectionEnabled
|
|
78
79
|
this.pprofPrefix = pprofPrefix
|
|
79
|
-
|
|
80
|
+
this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
|
|
81
|
+
DD_PROFILING_V8_PROFILER_BUG_WORKAROUND, true))
|
|
80
82
|
const hostname = coalesce(options.hostname, DD_AGENT_HOST) || 'localhost'
|
|
81
83
|
const port = coalesce(options.port, DD_TRACE_AGENT_PORT) || 8126
|
|
82
84
|
this.url = new URL(coalesce(options.url, DD_TRACE_AGENT_URL, format({
|
|
@@ -5,6 +5,7 @@ const { storage } = require('../../../../datadog-core')
|
|
|
5
5
|
const dc = require('../../../../diagnostics_channel')
|
|
6
6
|
const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
|
|
7
7
|
const { WEB } = require('../../../../../ext/types')
|
|
8
|
+
const runtimeMetrics = require('../../runtime_metrics')
|
|
8
9
|
|
|
9
10
|
const beforeCh = dc.channel('dd-trace:storage:before')
|
|
10
11
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
@@ -83,6 +84,7 @@ class NativeWallProfiler {
|
|
|
83
84
|
this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
|
|
84
85
|
this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
|
|
85
86
|
this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
|
|
87
|
+
this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
|
|
86
88
|
this._mapper = undefined
|
|
87
89
|
this._pprof = undefined
|
|
88
90
|
|
|
@@ -122,7 +124,8 @@ class NativeWallProfiler {
|
|
|
122
124
|
durationMillis: this._flushIntervalMillis,
|
|
123
125
|
sourceMapper: this._mapper,
|
|
124
126
|
withContexts: this._codeHotspotsEnabled,
|
|
125
|
-
lineNumbers: false
|
|
127
|
+
lineNumbers: false,
|
|
128
|
+
workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
|
|
126
129
|
})
|
|
127
130
|
|
|
128
131
|
if (this._codeHotspotsEnabled) {
|
|
@@ -172,7 +175,18 @@ class NativeWallProfiler {
|
|
|
172
175
|
this._enter()
|
|
173
176
|
this._lastSampleCount = 0
|
|
174
177
|
}
|
|
175
|
-
|
|
178
|
+
const profile = this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
|
|
179
|
+
if (restart) {
|
|
180
|
+
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
|
|
181
|
+
if (v8BugDetected === 1) {
|
|
182
|
+
this._logger?.warn('Wall profiler: possible v8 profiler stuck event loop detected.')
|
|
183
|
+
runtimeMetrics.increment('runtime.node.profiler.v8_cpu_profiler_maybe_stuck_event_loop', undefined, true)
|
|
184
|
+
} else if (v8BugDetected === 2) {
|
|
185
|
+
this._logger?.warn('Wall profiler: v8 profiler stuck event loop detected.')
|
|
186
|
+
runtimeMetrics.increment('runtime.node.profiler.v8_cpu_profiler_stuck_event_loop', undefined, true)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return profile
|
|
176
190
|
}
|
|
177
191
|
|
|
178
192
|
profile () {
|