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 CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  [![npm v4](https://img.shields.io/npm/v/dd-trace/latest?color=blue&label=dd-trace%40v4&logo=npm)](https://www.npmjs.com/package/dd-trace)
4
4
  [![npm v3](https://img.shields.io/npm/v/dd-trace/latest-node14?color=blue&label=dd-trace%40v3&logo=npm)](https://www.npmjs.com/package/dd-trace/v/latest-node12)
5
- [![npm v2](https://img.shields.io/npm/v/dd-trace/latest-node12?color=blue&label=dd-trace%40v2&logo=npm)](https://www.npmjs.com/package/dd-trace/v/latest-node12)
6
- [![npm dev](https://img.shields.io/npm/v/dd-trace/dev?color=orange&label=dd-trace%40dev&logo=npm)](https://www.npmjs.com/package/dd-trace/v/dev)
7
5
  [![codecov](https://codecov.io/gh/DataDog/dd-trace-js/branch/master/graph/badge.svg)](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) | ![npm v1](https://img.shields.io/npm/v/dd-trace/legacy-v1?color=white&label=%20&style=flat-square) | `>= v12` | **End of Life** | 2021-07-13 | 2022-02-25 |
31
- | [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) | ![npm v2](https://img.shields.io/npm/v/dd-trace/latest-node12?color=white&label=%20&style=flat-square) | `>= v12` | **Maintenance** | 2022-01-28 | 2023-08-15 |
29
+ | [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) | ![npm v2](https://img.shields.io/npm/v/dd-trace/latest-node12?color=white&label=%20&style=flat-square) | `>= v12` | **End of Life** | 2022-01-28 | 2023-08-15 |
32
30
  | [`v3`](https://github.com/DataDog/dd-trace-js/tree/v3.x) | ![npm v3](https://img.shields.io/npm/v/dd-trace/latest-node14?color=white&label=%20&style=flat-square) | `>= v14` | **Maintenance** | 2022-08-15 | 2024-05-15 |
33
31
  | [`v4`](https://github.com/DataDog/dd-trace-js/tree/v4.x) | ![npm v4](https://img.shields.io/npm/v/dd-trace/latest?color=white&label=%20&style=flat-square) | `>= v16` | **Current** | 2023-05-12 | Unknown |
34
32
 
35
- We currently maintain three release lines, namely `v2`, `v3` and `v4`.
36
- Features and bug fixes that are merged are released to the `v4` line and, if appropriate, also the `v2` and `v3` line.
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 `v2` or `v3` release lines, or projects that need to support EOL versions of Node.js, may continue to use these release lines.
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.12.0",
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.1.0",
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(packageName) || modulesOfInterest.has(`${extracted.pkg}/${extracted.path}`))
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: ${packageName}@${packageJson.version}`)
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: packageName,
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(packageName)) return
125
+ if (builtins.has(args.path)) return
112
126
 
113
127
  return {
114
- path: require.resolve(packageName, { paths: [ args.resolveDir ] }),
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 = (config.hooks && config.hooks.execute) || noop
60
- const parse = (config.hooks && config.hooks.parse) || noop
61
- const validate = (config.hooks && config.hooks.validate) || noop
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 (url) {
46
- return typeof urlToHttpOptions === 'function'
47
- ? urlToOptions(new URL(url))
48
- : urlParse(url)
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
- return this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
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 () {