newrelic 8.4.0 → 8.6.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.
@@ -1,19 +1,43 @@
1
1
  /*
2
- * Copyright 2020 New Relic Corporation. All rights reserved.
2
+ * Copyright 2021 New Relic Corporation. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
6
  'use strict'
7
7
 
8
- const getRawRequestFromFastifyRequest = (shim, fn, fnName, args) => {
9
- const request = args[0]
8
+ /**
9
+ * Retrieves the IncomingMessage from a Fastify request. Depending on the
10
+ * context of this function it either exists on `request.raw` or just `request`
11
+ *
12
+ * @param {WebFrameworkShim} shim
13
+ * @param {Function} fn middleware function executing
14
+ * @param {string} fnName name of middleware executing
15
+ * @param {args} args that are passed to middleware
16
+ * @returns {IncomingMessage}
17
+ */
18
+ const getRequestFromFastify = (shim, fn, fnName, args) => {
19
+ const [request] = args
20
+
21
+ // request is Fastify request
22
+ // object, get IncomingMessage from .raw
10
23
  if (request && request.raw) {
11
24
  return request.raw
12
25
  }
26
+
27
+ return request
13
28
  }
14
29
 
30
+ /**
31
+ * Retrieves the params from the Fastify request.
32
+ *
33
+ * @param {WebFrameworkShim} shim
34
+ * @param {Function} fn middleware function executing
35
+ * @param {string} fnName name of middleware executing
36
+ * @param {args} args that are passed to middleware
37
+ * @returns {object} URL params on a Fastify request
38
+ */
15
39
  const getParamsFromFastifyRequest = (shim, fn, fnName, args) => {
16
- const req = args[0]
40
+ const [req] = args
17
41
  return req && req.params
18
42
  }
19
43
 
@@ -21,32 +45,17 @@ const getParamsFromFastifyRequest = (shim, fn, fnName, args) => {
21
45
  * Builds the recordMiddleware Spec for the route handler
22
46
  *
23
47
  * A spec is basically a specification -- or a list
24
- * of insrtuctions to the recordMiddleware function
48
+ * of instructions to the recordMiddleware function
25
49
  * that provide it with the information it needs to
26
50
  * do its job. You could also think of it as a
27
51
  * mini-DSL
52
+ *
53
+ * @param {WebFrameworkShim} shim
54
+ * @param {string} path URL route being executed
55
+ * @returns {object} spec for Fastify route handler
28
56
  */
29
57
  function buildMiddlewareSpecForRouteHandler(shim, path) {
30
58
  return {
31
- /**
32
- * The path to use for transaction naming
33
- */
34
- route: path,
35
-
36
- /**
37
- * A function the returns the NodeJS Request Object
38
- *
39
- * The job of the `req` callback is to return the current NodeJS
40
- * IncomingMessage object for this particular handler. Most NodeJS
41
- * frameworks will pass the request to each handler -- sometimes (as
42
- * is the case here) wrapped by another object.
43
- *
44
- * @param {any} shim the Webframework Shim
45
- * @param {any} fn the handler function passed to buildMiddlewareSpec
46
- * @param {any} fnName the handler function's name
47
- * @param {any} args the arguments passed to the handler function
48
- */
49
- req: getRawRequestFromFastifyRequest,
50
59
  /**
51
60
  * A function where we can wrap next, reply send, etc. methods
52
61
  *
@@ -66,6 +75,12 @@ function buildMiddlewareSpecForRouteHandler(shim, path) {
66
75
  * The isFinal param determines whether or not a path is appended for
67
76
  * this particular piece of middleware. (i.e. if this is the final handler
68
77
  * that is actually handling the request, the path is actually left on)
78
+ *
79
+ * @param shim
80
+ * @param fn
81
+ * @param fnName
82
+ * @param args
83
+ * @param bindSegment
69
84
  */
70
85
  next: function wrapNext(shim, fn, fnName, args, bindSegment) {
71
86
  const reply = args[1]
@@ -75,33 +90,28 @@ function buildMiddlewareSpecForRouteHandler(shim, path) {
75
90
  const isFinal = true
76
91
  bindSegment(reply, 'send', isFinal)
77
92
  },
78
-
79
- /**
80
- * A function that returns the request paramates
81
- *
82
- * @param {any} shim the Webframework Shim
83
- * @param {any} fn the handler function passed to buildMiddlewareSpec
84
- * @param {any} fnName the handler function's name
85
- * @param {any} args the arguments passed to the handler function
86
- */
87
- params: getParamsFromFastifyRequest
93
+ params: getParamsFromFastifyRequest,
94
+ req: getRequestFromFastify,
95
+ route: path
88
96
  }
89
97
  }
90
98
 
91
- function buildMiddlewareSpecForMiddlewareFunction() {
99
+ /**
100
+ * Spec for all Fastify middleware(excluding route handlers)
101
+ *
102
+ * @param {WebFrameworkShim} shim
103
+ * @param {string} name metric name for middleware being executed
104
+ * @param route
105
+ * @returns {object} spec for Fastify middleware
106
+ */
107
+ function buildMiddlewareSpecForMiddlewareFunction(shim, name, route) {
92
108
  return {
93
- req: getRawRequestFromFastifyRequest,
94
-
95
- next: function wrapNext(shim, fn, fnName, args, bindSegment) {
96
- const next = args[2]
97
- if (!shim.isFunction(next)) {
98
- return
99
- }
100
- const isFinal = false
101
- bindSegment(next, null, isFinal)
102
- },
103
-
104
- params: getParamsFromFastifyRequest
109
+ name,
110
+ route,
111
+ next: shim.LAST,
112
+ params: getParamsFromFastifyRequest,
113
+ req: getRequestFromFastify,
114
+ type: shim.MIDDLEWARE
105
115
  }
106
116
  }
107
117
 
@@ -4,11 +4,33 @@
4
4
  */
5
5
 
6
6
  'use strict'
7
+
7
8
  const semver = require('semver')
8
9
  const {
9
10
  buildMiddlewareSpecForRouteHandler,
10
11
  buildMiddlewareSpecForMiddlewareFunction
11
12
  } = require('./fastify/spec-builders')
13
+
14
+ /**
15
+ * These are the events that occur during a fastify
16
+ * request
17
+ * see: https://www.fastify.io/docs/latest/Lifecycle/
18
+ *
19
+ * Note: preSerialization and onSend happen after the route handler
20
+ * executes. `onResponse` does not execute until after the client
21
+ * sends the response so it'll never be in scope of the transaction
22
+ */
23
+ const REQUEST_HOOKS = [
24
+ 'onRequest',
25
+ 'preParsing',
26
+ 'preValidation',
27
+ 'preHandler',
28
+ 'preSerialization',
29
+ 'onSend',
30
+ 'onResponse',
31
+ 'onError'
32
+ ]
33
+
12
34
  /**
13
35
  * Sets up fastify route handler
14
36
  *
@@ -20,6 +42,9 @@ const {
20
42
  * those private implementations from access, and getting
21
43
  * at them would require a lot of gymnastics and hard to
22
44
  * maintain code
45
+ *
46
+ * @param shim
47
+ * @param fastify
23
48
  */
24
49
  const setupRouteHandler = (shim, fastify) => {
25
50
  fastify.addHook('onRoute', (routeOptions) => {
@@ -27,10 +52,10 @@ const setupRouteHandler = (shim, fastify) => {
27
52
  return
28
53
  }
29
54
  /**
30
- * recordMiddlware handler call
55
+ * recordMiddleware handler call
31
56
  *
32
57
  * The WebFramework shim treats the main route handler like any other
33
- * i.e. dont be confused by the call to recordMiddlware -- we don't
58
+ * i.e. dont be confused by the call to recordMiddleware -- we don't
34
59
  * have a recordRouteHandler, everything goes through recordMiddleware
35
60
  */
36
61
  const newRouteHandler = shim.recordMiddleware(
@@ -40,29 +65,25 @@ const setupRouteHandler = (shim, fastify) => {
40
65
 
41
66
  routeOptions.handler = newRouteHandler
42
67
  })
43
- }
44
68
 
45
- const setupMiddlewareHandlers = (shim, fastify) => {
46
- shim.wrap(fastify, 'use', function wrapFastifyUse(shim, fn) {
47
- return function wrappedFastifyUser() {
69
+ shim.wrap(fastify, 'addHook', function addWrapHook(shim, fn) {
70
+ return function wrappedAddHook() {
48
71
  const args = shim.argsToArray.apply(shim, arguments)
49
- const middlewareFunction = args[0]
50
- const newMiddlewareFunction = shim.recordMiddleware(
51
- middlewareFunction,
52
- buildMiddlewareSpecForMiddlewareFunction(shim)
53
- )
54
- // replace original function with our function
55
- args[0] = newMiddlewareFunction
72
+ const hookName = args[0]
73
+ if (REQUEST_HOOKS.includes(hookName)) {
74
+ const middlewareFunction = args[1]
75
+ const name = `${hookName}/${shim.getName(middlewareFunction)}`
76
+ const middlewareSpec = buildMiddlewareSpecForMiddlewareFunction(shim, name)
77
+ const newMiddlewareFunction = shim.recordMiddleware(middlewareFunction, middlewareSpec)
56
78
 
79
+ args[1] = newMiddlewareFunction
80
+ }
57
81
  return fn.apply(this, args)
58
82
  }
59
83
  })
60
84
  }
61
85
 
62
86
  module.exports = function initialize(agent, fastify, moduleName, shim) {
63
- if (!agent.config.feature_flag.fastify_instrumentation) {
64
- return
65
- }
66
87
  shim.setFramework(shim.FASTIFY)
67
88
 
68
89
  const fastifyVersion = shim.require('./package.json').version
@@ -73,18 +94,12 @@ module.exports = function initialize(agent, fastify, moduleName, shim) {
73
94
  */
74
95
  const wrappedExport = shim.wrapExport(fastify, function wrapFastifyModule(shim, fn) {
75
96
  return function wrappedFastifyModule() {
76
- // normalize arguments
77
- const args = shim.argsToArray.apply(shim, arguments)
78
-
79
97
  // call original function get get fastify object (which is singleton-ish)
80
- const fastifyForWrapping = fn.apply(this, args)
98
+ const fastifyForWrapping = fn.apply(this, arguments)
81
99
 
82
100
  setupRouteHandler(shim, fastifyForWrapping)
83
101
 
84
- // Don't wrap use() in fastify v3+
85
- if (!isv3Plus) {
86
- setupMiddlewareHandlers(shim, fastifyForWrapping)
87
- }
102
+ setupMiddlewareHandlers(shim, fastifyForWrapping, isv3Plus)
88
103
 
89
104
  return fastifyForWrapping
90
105
  }
@@ -95,10 +110,56 @@ module.exports = function initialize(agent, fastify, moduleName, shim) {
95
110
  }
96
111
  }
97
112
 
113
+ function setupMiddlewareHandlers(shim, fastify, isv3Plus) {
114
+ const mounterSpec = {
115
+ route: shim.FIRST,
116
+ wrapper: wrapMiddleware
117
+ }
118
+
119
+ if (isv3Plus) {
120
+ // Fastify v3+ does not ship with traditional Node.js middleware mounting.
121
+ // This style is accomplished leveraging decorators. Both middie (which was built-in in v2)
122
+ // and fastify-express mount a 'use' function for mounting middleware.
123
+ shim.wrap(fastify, 'decorate', function wrapDecorate(shim, fn) {
124
+ return function wrappedDecorate() {
125
+ const name = arguments[0]
126
+ if (name !== 'use') {
127
+ return fn.apply(this, arguments)
128
+ }
129
+
130
+ const args = shim.argsToArray.apply(shim, arguments)
131
+ args[1] = shim.wrapMiddlewareMounter(args[1], mounterSpec)
132
+
133
+ return fn.apply(this, args)
134
+ }
135
+ })
136
+ } else {
137
+ shim.wrapMiddlewareMounter(fastify, 'use', mounterSpec)
138
+ }
139
+ }
140
+
141
+ function wrapMiddleware(shim, middleware, name, route) {
142
+ if (shim.isWrapped(middleware)) {
143
+ return middleware
144
+ }
145
+
146
+ // prefixing the segment name for middleware execution
147
+ // with the Fastify lifecycle hook
148
+ const segmentName = `onRequest/${name}`
149
+ const spec = buildMiddlewareSpecForMiddlewareFunction(shim, segmentName, route)
150
+
151
+ const newMiddlewareFunction = shim.recordMiddleware(middleware, spec)
152
+
153
+ return newMiddlewareFunction
154
+ }
155
+
98
156
  /**
99
157
  * module.exports = fastify
100
158
  * module.exports.fastify = fastify
101
159
  * module.exports.default = fastify
160
+ *
161
+ * @param original
162
+ * @param wrappedExport
102
163
  */
103
164
  function setupExports(original, wrappedExport) {
104
165
  wrappedExport.fastify = original.fastify
@@ -47,9 +47,11 @@ common.instrumentCollection = function instrumentCollection(shim, Collection) {
47
47
  * Instruments all methods from constants.DB_OPS on
48
48
  * the Db class.
49
49
  *
50
- * @param {Object} params
50
+ * @param {object} params
51
51
  * @param {Shim} params.shim
52
52
  * @param {Db} params.Db
53
+ * @param shim
54
+ * @param Db
53
55
  */
54
56
  common.instrumentDb = function instrumentDb(shim, Db) {
55
57
  if (Db && Db.prototype) {
@@ -62,6 +64,7 @@ common.instrumentDb = function instrumentDb(shim, Db) {
62
64
 
63
65
  /**
64
66
  * Sets up the desc for all instrumented query methods
67
+ *
65
68
  * @param {Shim} shim
66
69
  * @param {string} methodName
67
70
  */
@@ -123,7 +126,7 @@ common.captureAttributesOnStarted = function captureAttributesOnStarted(shim, in
123
126
  * @param {Shim} shim
124
127
  * @param {string} connStr
125
128
  * @param {string} db database name
126
- * @param {Object} client mongo client instance
129
+ * @param {object} client mongo client instance
127
130
  */
128
131
  function setHostPort(shim, connStr, db, client) {
129
132
  const parts = connStr.split(':')
@@ -153,6 +156,9 @@ function setHostPort(shim, connStr, db, client) {
153
156
  * for the query segment. v4 refactored where the toplogy is stored.
154
157
  * You can now get the details via the client obj that's deeply nested
155
158
  * See: https://github.com/mongodb/node-mongodb-native/pull/2594/files#diff-1d214e57ddda9095d296e5700ebce701333bfefcf417e234c584d14091b2f50dR168
159
+ *
160
+ * @param shim
161
+ * @param obj
156
162
  */
157
163
  function getInstanceAttributeParameters(shim, obj) {
158
164
  if (obj.s && obj.s.db && obj.s.topology) {
@@ -197,8 +203,10 @@ function getInstanceAttributeParameters(shim, obj) {
197
203
  }
198
204
 
199
205
  function doCapture(conf, database) {
200
- let host = null
201
- let port = null
206
+ // in older versions of 3.x the host/port
207
+ // lived directly on the topology.s.options
208
+ let host = conf.host
209
+ let port = conf.port
202
210
 
203
211
  // servers is an array but we will always pull the first for consistency
204
212
  if (conf.servers && conf.servers.length) {
@@ -11,28 +11,22 @@ const shimmer = require('../shimmer')
11
11
 
12
12
  /**
13
13
  * @namespace Library.Spec
14
- *
15
14
  * @property {string} name
16
15
  * The name of this promise library.
17
- *
18
16
  * @property {?string} constructor
19
17
  * Optional. The name of the property that is the Promise constructor. Default
20
18
  * is to use the library itself as the Promise constructor.
21
- *
22
19
  * @property {?bool} executor
23
20
  * Optional. If true, the Promise constructor itself will be wrapped for the
24
21
  * executor. If false then `_proto`, `_static`, or `_library` must have an
25
22
  * `executor` field whose value is the name of the executor function. Default
26
23
  * is false.
27
- *
28
24
  * @property {Library.Spec.Mapping} $proto
29
25
  * The mapping for Promise instance method concepts (i.e. `then`). These are
30
26
  * mapped on the Promise class' prototype.
31
- *
32
27
  * @property {Library.Spec.Mapping} $static
33
28
  * The mapping for Promise static method concepts (i.e. `all`, `race`). These
34
29
  * are mapped on the Promise class itself.
35
- *
36
30
  * @property {?Library.Spec.Mapping} $library
37
31
  * The mapping for library-level static method concepts (i.e. `fcall`, `when`).
38
32
  * These are mapped on the library containing the Promise class. NOTE: in most
@@ -42,35 +36,23 @@ const shimmer = require('../shimmer')
42
36
 
43
37
  /**
44
38
  * @namespace Library.Spec.Mapping
45
- *
46
- * @desc
39
+ * @description
47
40
  * A mapping of promise concepts (i.e. `then`) to this library's implementation
48
41
  * name(s) (i.e. `["then", "chain"]`). Each value can by either a single string
49
42
  * or an array of strings if the concept exists under multiple keys. If any
50
43
  * given concept doesn't exist in this library, it is simply skipped.
51
- *
52
- * @property {array} $copy
44
+ * @property {Array} $copy
53
45
  * An array of properties or methods to just directly copy without wrapping.
54
46
  * This field only matters when `Library.Spec.executor` is `true`.
55
- *
56
- * @property {string|array} executor
57
- *
58
- *
59
- * @property {string|array} then
60
- *
61
- *
62
- * @property {string|array} all
63
- *
64
- *
65
- * @property {string|array} race
66
- *
67
- *
68
- * @property {string|array} resolve
47
+ * @property {string | Array} executor
48
+ * @property {string | Array} then
49
+ * @property {string | Array} all
50
+ * @property {string | Array} race
51
+ * @property {string | Array} resolve
69
52
  * Indicates methods to wrap which are resolve factories. This method only
70
53
  * requires wrapping if the library doesn't use an executor internally to
71
54
  * implement it.
72
- *
73
- * @property {string|array} reject
55
+ * @property {string | Array} reject
74
56
  * Indicates methods to wrap which are reject factories. Like `resolve`, this
75
57
  * method only requires wrapping if the library doesn't use an executor
76
58
  * internally to implement it.
@@ -80,11 +62,13 @@ const shimmer = require('../shimmer')
80
62
  * Instruments a promise library.
81
63
  *
82
64
  * @param {Agent} agent - The New Relic APM agent.
83
- * @param {function} library - The promise library.
65
+ * @param {Function} library - The promise library.
84
66
  * @param {?Library.Spec} spec - Spec for this promise library mapping.
85
67
  */
86
68
  /* eslint-disable camelcase */
87
69
  module.exports = function initialize(agent, library, spec) {
70
+ const contextManager = agent._contextManager
71
+
88
72
  if (spec.useFinally == null) {
89
73
  spec.useFinally = true
90
74
  }
@@ -144,7 +128,7 @@ module.exports = function initialize(agent, library, spec) {
144
128
  return Promise(executor) // eslint-disable-line new-cap
145
129
  }
146
130
 
147
- const parent = agent.tracer.segment
131
+ const parent = contextManager.getContext()
148
132
  let promise = null
149
133
  if (
150
134
  !parent ||
@@ -170,15 +154,13 @@ module.exports = function initialize(agent, library, spec) {
170
154
  const segment = _createSegment(segmentName)
171
155
  Contextualizer.link(null, promise, segment, spec.useFinally)
172
156
 
173
- agent.tracer.segment = segment
174
157
  segment.start()
175
158
  try {
176
159
  // Must run after promise is defined so that `__NR_wrapper` can be set.
177
- executor.apply(context.self, context.args)
160
+ contextManager.runInContext(segment, executor, context.self, context.args)
178
161
  } catch (e) {
179
162
  context.args[1](e)
180
163
  } finally {
181
- agent.tracer.segment = parent
182
164
  segment.touch()
183
165
  }
184
166
  }
@@ -259,8 +241,7 @@ module.exports = function initialize(agent, library, spec) {
259
241
  * execution.
260
242
  *
261
243
  * @param {object} context - The object to export the execution context with.
262
- *
263
- * @return {function} A function which, when executed, will add its context
244
+ * @returns {Function} A function which, when executed, will add its context
264
245
  * and arguments to the `context` parameter.
265
246
  */
266
247
  function wrapExecutorContext(context) {
@@ -285,7 +266,9 @@ module.exports = function initialize(agent, library, spec) {
285
266
  /**
286
267
  * Creates a wrapper for `Promise#then` that extends the transaction context.
287
268
  *
288
- * @return {function} A wrapped version of `Promise#then`.
269
+ * @param then
270
+ * @param name
271
+ * @returns {Function} A wrapped version of `Promise#then`.
289
272
  */
290
273
  function wrapThen(then, name) {
291
274
  return _wrapThen(then, name, true)
@@ -294,7 +277,9 @@ module.exports = function initialize(agent, library, spec) {
294
277
  /**
295
278
  * Creates a wrapper for `Promise#catch` that extends the transaction context.
296
279
  *
297
- * @return {function} A wrapped version of `Promise#catch`.
280
+ * @param cach
281
+ * @param name
282
+ * @returns {Function} A wrapped version of `Promise#catch`.
298
283
  */
299
284
  function wrapCatch(cach, name) {
300
285
  return _wrapThen(cach, name, false)
@@ -303,14 +288,13 @@ module.exports = function initialize(agent, library, spec) {
303
288
  /**
304
289
  * Creates a wrapper for promise chain extending methods.
305
290
  *
306
- * @param {function} then
291
+ * @param {Function} then
307
292
  * The function we are to wrap as a chain extender.
308
- *
293
+ * @param name
309
294
  * @param {bool} useAllParams
310
295
  * When true, all parameters which are functions will be wrapped. Otherwise,
311
296
  * only the last parameter will be wrapped.
312
- *
313
- * @return {function} A wrapped version of the function.
297
+ * @returns {Function} A wrapped version of the function.
314
298
  */
315
299
  function _wrapThen(then, name, useAllParams) {
316
300
  // Don't wrap non-functions.
@@ -377,6 +361,9 @@ module.exports = function initialize(agent, library, spec) {
377
361
 
378
362
  /**
379
363
  * Creates a wrapper around the static `Promise` factory method.
364
+ *
365
+ * @param cast
366
+ * @param name
380
367
  */
381
368
  function wrapCast(cast, name) {
382
369
  if (typeof cast !== 'function' || cast.name === '__NR_wrappedCast') {
@@ -437,8 +424,8 @@ module.exports = function initialize(agent, library, spec) {
437
424
  *
438
425
  * @param {object} obj - The source of the methods to wrap.
439
426
  * @param {string} name - The name of this source.
440
- * @param {string|array} methods - The names of the methods to wrap.
441
- * @param {function} wrapper - The function which wraps the methods.
427
+ * @param {string | Array} methods - The names of the methods to wrap.
428
+ * @param {Function} wrapper - The function which wraps the methods.
442
429
  */
443
430
  function _safeWrap(obj, name, methods, wrapper) {
444
431
  if (methods && methods.length) {