newrelic 9.7.0 → 9.7.2

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 (37) hide show
  1. package/NEWS.md +42 -18
  2. package/THIRD_PARTY_NOTICES.md +203 -2
  3. package/api.js +155 -103
  4. package/lib/agent.js +29 -82
  5. package/lib/collector/api.js +234 -201
  6. package/lib/config/attribute-filter.js +35 -25
  7. package/lib/config/default.js +45 -19
  8. package/lib/config/index.js +260 -199
  9. package/lib/environment.js +16 -12
  10. package/lib/errors/error-collector.js +124 -55
  11. package/lib/errors/index.js +59 -49
  12. package/lib/instrumentation/@hapi/hapi.js +56 -50
  13. package/lib/instrumentation/@node-redis/client.js +50 -41
  14. package/lib/instrumentation/amqplib.js +116 -151
  15. package/lib/instrumentation/core/{async_hooks.js → async-hooks.js} +26 -11
  16. package/lib/instrumentation/core/globals.js +1 -1
  17. package/lib/instrumentation/core/http-outbound.js +193 -78
  18. package/lib/instrumentation/core/timers.js +106 -59
  19. package/lib/instrumentation/grpc-js/grpc.js +20 -23
  20. package/lib/instrumentation/mongodb/common.js +87 -85
  21. package/lib/instrumentation/redis.js +112 -90
  22. package/lib/instrumentation/undici.js +204 -192
  23. package/lib/instrumentation/{when.js → when/constants.js} +13 -10
  24. package/lib/instrumentation/when/contextualizer.js +168 -0
  25. package/lib/instrumentation/when/index.js +354 -0
  26. package/lib/instrumentation/when/nr-hooks.js +15 -0
  27. package/lib/instrumentations.js +1 -1
  28. package/lib/shim/shim.js +2 -0
  29. package/lib/shim/webframework-shim.js +19 -0
  30. package/lib/symbols.js +1 -0
  31. package/lib/system-info.js +240 -163
  32. package/lib/util/async-each-limit.js +30 -0
  33. package/lib/util/attributes.js +159 -0
  34. package/lib/util/code-level-metrics.js +58 -0
  35. package/lib/util/deep-equal.js +11 -144
  36. package/package.json +5 -4
  37. package/lib/instrumentation/promise.js +0 -572
@@ -0,0 +1,354 @@
1
+ /*
2
+ * Copyright 2020 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const util = require('util')
9
+ const properties = require('../../util/properties')
10
+ const shimmer = require('../../shimmer')
11
+ const symbols = require('../../symbols')
12
+ const ANONYMOUS = '<anonymous>'
13
+ const { WHEN_SPEC } = require('./constants')
14
+ const Contextualizer = require('./contextualizer')
15
+
16
+ /**
17
+ * Instruments when.js
18
+ *
19
+ * @param {Shim} shim instance of shim
20
+ * @param {Function} when the exported when.js library.
21
+ */
22
+ module.exports = function initialize(shim, when) {
23
+ const agent = shim.agent
24
+ const contextManager = agent._contextManager
25
+ const spec = WHEN_SPEC
26
+
27
+ // Wrap library-level methods.
28
+ wrapStaticMethods(when, spec.name, spec.$library)
29
+
30
+ // Wrap prototype methods.
31
+ const Promise = when[spec.constructor]
32
+ wrapPrototype(Promise.prototype)
33
+ wrapStaticMethods(Promise, spec.constructor, spec.$static)
34
+
35
+ // See if we are wrapping the class itself.
36
+ shimmer.wrapMethod(when, spec.name, spec.constructor, wrapPromise)
37
+
38
+ /**
39
+ * Wraps every method of when.js and also defines properties on
40
+ * the $passThrough methods.
41
+ *
42
+ * @returns {Function} our wrapped promise
43
+ */
44
+ function wrapPromise() {
45
+ spec.$static.$copy.forEach(function copyKeys(key) {
46
+ if (!wrappedPromise[key]) {
47
+ wrappedPromise[key] = Promise[key]
48
+ }
49
+ })
50
+
51
+ spec.$static.$passThrough.forEach(function assignProxy(proxyProp) {
52
+ if (!properties.hasOwn(wrappedPromise, proxyProp)) {
53
+ Object.defineProperty(wrappedPromise, proxyProp, {
54
+ enumerable: true,
55
+ configurable: true,
56
+ get: function getOriginal() {
57
+ return Promise[proxyProp]
58
+ },
59
+ set: function setOriginal(newValue) {
60
+ Promise[proxyProp] = newValue
61
+ }
62
+ })
63
+ }
64
+ })
65
+
66
+ // Inherit to pass `instanceof` checks.
67
+ util.inherits(wrappedPromise, Promise)
68
+
69
+ // Make the wrapper.
70
+ return wrappedPromise
71
+ }
72
+
73
+ /**
74
+ * Wraps the promise handler and binds to the
75
+ * agent async context manager
76
+ *
77
+ * @param {Function} executor promise handler
78
+ * @returns {Function} wrapped handler
79
+ */
80
+ function wrappedPromise(executor) {
81
+ if (!(this instanceof wrappedPromise)) {
82
+ return Promise(executor) // eslint-disable-line new-cap
83
+ }
84
+
85
+ const parent = contextManager.getContext()
86
+ let promise = null
87
+ if (
88
+ !parent ||
89
+ !parent.transaction.isActive() ||
90
+ typeof executor !== 'function' ||
91
+ arguments.length !== 1
92
+ ) {
93
+ // We are expecting one function argument for executor, anything else is
94
+ // non-standard, do not attempt to wrap. Also do not attempt to wrap if we
95
+ // are not in a transaction.
96
+ const cnstrctArgs = agent.tracer.slice(arguments)
97
+ cnstrctArgs.unshift(Promise) // `unshift` === `push_front`
98
+ promise = new (Promise.bind.apply(Promise, cnstrctArgs))()
99
+ } else {
100
+ const segmentName = 'Promise ' + (executor.name || ANONYMOUS)
101
+ const context = {
102
+ promise: null,
103
+ self: null,
104
+ args: null
105
+ }
106
+ promise = new Promise(wrapExecutorContext(context))
107
+ context.promise = promise
108
+ const segment = _createSegment(segmentName)
109
+ Contextualizer.link(null, promise, segment)
110
+
111
+ segment.start()
112
+ try {
113
+ // Must run after promise is defined so that `__NR_wrapper` can be set.
114
+ contextManager.runInContext(segment, executor, context.self, context.args)
115
+ } catch (e) {
116
+ context.args[1](e)
117
+ } finally {
118
+ segment.touch()
119
+ }
120
+ }
121
+
122
+ // The Promise must be created using the "real" Promise constructor (using
123
+ // normal Promise.apply(this) method does not work). But the prototype
124
+ // chain must include the wrappedPromise.prototype, V8's promise
125
+ // implementation uses promise.constructor to create new Promises for
126
+ // calls to `then`, `chain` and `catch` which allows these Promises to
127
+ // also be instrumented.
128
+ promise.__proto__ = wrappedPromise.prototype // eslint-disable-line no-proto
129
+
130
+ return promise
131
+ }
132
+
133
+ /**
134
+ * Wraps then and catch on the when.js prototype
135
+ *
136
+ * @param {Function} PromiseProto when.js prototype
137
+ * @returns {void}
138
+ */
139
+ function wrapPrototype(PromiseProto) {
140
+ const name = spec.constructor + '.prototype'
141
+
142
+ // Wrap up instance methods.
143
+ _safeWrap(PromiseProto, name, spec.$proto.then, wrapThen)
144
+ _safeWrap(PromiseProto, name, spec.$proto.catch, wrapCatch)
145
+ }
146
+
147
+ /**
148
+ * Wraps all the static methods on when.js
149
+ * See: constants.STATIC_PROMISE_METHODS
150
+ *
151
+ * @param {Function} lib when.Promise
152
+ * @param {string} name `Promise`
153
+ * @param {object} staticSpec see WHEN_SPEC.$static
154
+ * @returns {void}
155
+ */
156
+ function wrapStaticMethods(lib, name, staticSpec) {
157
+ _safeWrap(lib, name, staticSpec.cast, wrapCast)
158
+ }
159
+
160
+ /**
161
+ * Creates a function which will export the context and arguments of its
162
+ * execution.
163
+ *
164
+ * @param {object} context - The object to export the execution context with.
165
+ * @returns {Function} A function which, when executed, will add its context
166
+ * and arguments to the `context` parameter.
167
+ */
168
+ function wrapExecutorContext(context) {
169
+ return function contextExporter(resolve, reject) {
170
+ context.self = this
171
+ context.args = [].slice.call(arguments)
172
+ context.args[0] = wrapResolver(context, resolve)
173
+ context.args[1] = wrapResolver(context, reject)
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Wraps the resolve/reject functions of a when.js Promise
179
+ *
180
+ * @param {object} context object to update execution context
181
+ * @param {Function} fn function reference `resolve` or `rejct`
182
+ * @returns {Function} wrapped function
183
+ */
184
+ function wrapResolver(context, fn) {
185
+ return function wrappedResolveReject(val) {
186
+ const promise = context.promise
187
+ if (promise && promise[symbols.context]) {
188
+ promise[symbols.context].getSegment().touch()
189
+ }
190
+ fn(val)
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Creates a wrapper for `Promise#then` that extends the transaction context.
196
+ *
197
+ * @param {Function} then function reference to instrument
198
+ * @param {string} name `then`
199
+ * @returns {Function} A wrapped version of `Promise#then`.
200
+ */
201
+ function wrapThen(then, name) {
202
+ return _wrapThen(then, name, true)
203
+ }
204
+
205
+ /**
206
+ * Creates a wrapper for `Promise#catch` that extends the transaction context.
207
+ *
208
+ * @param {Function} catchMethod function reference to instrument
209
+ * @param {string} name `catch`
210
+ * @returns {Function} A wrapped version of `Promise#catch`.
211
+ */
212
+ function wrapCatch(catchMethod, name) {
213
+ return _wrapThen(catchMethod, name, false)
214
+ }
215
+
216
+ /**
217
+ * Creates a wrapper for promise chain extending methods.
218
+ *
219
+ * @param {Function} then
220
+ * The function we are to wrap as a chain extender.
221
+ * @param {string} name name of function being wrapped
222
+ * @param {boolean} useAllParams
223
+ * When true, all parameters which are functions will be wrapped. Otherwise,
224
+ * only the last parameter will be wrapped.
225
+ * @returns {Function} A wrapped version of the function.
226
+ */
227
+ function _wrapThen(then, name, useAllParams) {
228
+ // Don't wrap non-functions.
229
+ if (typeof then !== 'function' || then.name === '__NR_wrappedThen') {
230
+ return then
231
+ }
232
+
233
+ // eslint-disable-next-line camelcase
234
+ return function __NR_wrappedThen() {
235
+ if (!(this instanceof Promise)) {
236
+ return then.apply(this, arguments)
237
+ }
238
+
239
+ const segmentNamePrefix = 'Promise#' + name + ' '
240
+ const thenSegment = agent.tracer.getSegment()
241
+ const promise = this
242
+ const ctx = { next: undefined, useAllParams, isWrapped: false, segmentNamePrefix }
243
+
244
+ // Wrap up the arguments and execute the real then.
245
+ const args = [].map.call(arguments, wrapHandler.bind(this, ctx))
246
+ ctx.next = then.apply(this, args)
247
+
248
+ // If we got a promise (which we should have), link the parent's context.
249
+ if (!ctx.isWrapped && ctx.next instanceof Promise && ctx.next !== promise) {
250
+ Contextualizer.link(promise, ctx.next, thenSegment)
251
+ }
252
+ return ctx.next
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Wraps every function passed to .then
258
+ *
259
+ * @param {object} ctx context to pass data from caller to callee and back
260
+ * @param {Function} fn function reference
261
+ * @param {number} i position of function in .then handler
262
+ * @param {Array} arr all args passed to .then
263
+ * @returns {Function} wraps every function pass to then
264
+ */
265
+ function wrapHandler(ctx, fn, i, arr) {
266
+ if (
267
+ typeof fn !== 'function' || // Not a function
268
+ fn.name === '__NR_wrappedThenHandler' || // Already wrapped
269
+ (!ctx.useAllParams && i !== arr.length - 1) // Don't want all and not last
270
+ ) {
271
+ ctx.isWrapped = fn && fn.name === '__NR_wrappedThenHandler'
272
+ return fn
273
+ }
274
+
275
+ // eslint-disable-next-line camelcase
276
+ return function __NR_wrappedThenHandler() {
277
+ if (!ctx.next || !ctx.next[symbols.context]) {
278
+ return fn.apply(this, arguments)
279
+ }
280
+
281
+ let promSegment = ctx.next[symbols.context].getSegment()
282
+ const segmentName = ctx.segmentNamePrefix + (fn.name || ANONYMOUS)
283
+ const segment = _createSegment(segmentName, promSegment)
284
+ if (segment && segment !== promSegment) {
285
+ ctx.next[symbols.context].setSegment(segment)
286
+ promSegment = segment
287
+ }
288
+
289
+ let ret = null
290
+ try {
291
+ ret = agent.tracer.bindFunction(fn, promSegment, true).apply(this, arguments)
292
+ } finally {
293
+ if (ret && typeof ret.then === 'function') {
294
+ ret = ctx.next[symbols.context].continue(ret)
295
+ }
296
+ }
297
+ return ret
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Creates a wrapper around the static `Promise` factory method.
303
+ *
304
+ * @param {Function} cast reference of function to wrap
305
+ * @param {string} name name of the function being wrapped
306
+ * @returns {Function} wrapped function
307
+ */
308
+ function wrapCast(cast, name) {
309
+ if (typeof cast !== 'function' || cast.name === '__NR_wrappedCast') {
310
+ return cast
311
+ }
312
+
313
+ const CAST_SEGMENT_NAME = 'Promise.' + name
314
+ // eslint-disable-next-line camelcase
315
+ return function __NR_wrappedCast() {
316
+ const segment = _createSegment(CAST_SEGMENT_NAME)
317
+ const prom = cast.apply(this, arguments)
318
+ if (segment) {
319
+ Contextualizer.link(null, prom, segment)
320
+ }
321
+ return prom
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Creates a segment for a given handler in promise chain
327
+ * if `config.feature_flag.promise_segments` is true
328
+ * Otherwise it just returns the current if existing or gets the current
329
+ *
330
+ * @param {string} name name of segment to create
331
+ * @param {object} parent current parent segment
332
+ * @returns {object} segment
333
+ */
334
+ function _createSegment(name, parent) {
335
+ return agent.config.feature_flag.promise_segments === true
336
+ ? agent.tracer.createSegment(name, null, parent)
337
+ : parent || agent.tracer.getSegment()
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Performs a `wrapMethod` if and only if `methods` is truthy and has a length
343
+ * greater than zero.
344
+ *
345
+ * @param {object} obj - The source of the methods to wrap.
346
+ * @param {string} name - The name of this source.
347
+ * @param {string | Array} methods - The names of the methods to wrap.
348
+ * @param {Function} wrapper - The function which wraps the methods.
349
+ */
350
+ function _safeWrap(obj, name, methods, wrapper) {
351
+ if (methods && methods.length) {
352
+ shimmer.wrapMethod(obj, name, methods, wrapper)
353
+ }
354
+ }
@@ -0,0 +1,15 @@
1
+ /*
2
+ * Copyright 2022 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const instrumentation = require('./index')
8
+
9
+ module.exports = [
10
+ {
11
+ type: null,
12
+ moduleName: 'when',
13
+ onRequire: instrumentation
14
+ }
15
+ ]
@@ -37,7 +37,7 @@ module.exports = function instrumentations() {
37
37
  'superagent': { module: '@newrelic/superagent' },
38
38
  'undici': { type: MODULE_TYPE.TRANSACTION },
39
39
  '@hapi/vision': { type: MODULE_TYPE.WEB_FRAMEWORK },
40
- 'when': { type: null },
40
+ 'when': { module: './instrumentation/when' },
41
41
  'winston': { type: MODULE_TYPE.GENERIC }
42
42
  }
43
43
  }
package/lib/shim/shim.js CHANGED
@@ -14,6 +14,7 @@ const path = require('path')
14
14
  const specs = require('./specs')
15
15
  const util = require('util')
16
16
  const symbols = require('../symbols')
17
+ const maybeAddCLMAttributes = require('../util/code-level-metrics')
17
18
 
18
19
  // Some modules do terrible things, like change the prototype of functions. To
19
20
  // avoid crashing things we'll use a cached copy of apply everywhere.
@@ -863,6 +864,7 @@ function record(nodule, properties, recordNamer) {
863
864
  )
864
865
 
865
866
  const segment = shouldCreateSegment ? _rawCreateSegment(shim, segDesc) : parent
867
+ maybeAddCLMAttributes(fn, segment)
866
868
 
867
869
  return _doRecord.call(this, segment, args, segDesc, shouldCreateSegment)
868
870
  }
@@ -677,6 +677,23 @@ function _defaultResponsePredicate() {
677
677
  return false
678
678
  }
679
679
 
680
+ /**
681
+ * Attaches a flag on function indicating that
682
+ * CLM attributes need to be associated with the middleware
683
+ * span during record
684
+ *
685
+ * Note: director middleware instrumentation passes in a string as the "function" so we have to check if the middleware is actually a function
686
+ * to avoid crashing.
687
+ *
688
+ * @param {Shim} shim instance of shim
689
+ * @param {Function} middleware function to apply clm symbol
690
+ */
691
+ function assignCLMSymbol(shim, middleware) {
692
+ if (shim.isFunction(middleware) && shim.agent.config.code_level_metrics.enabled) {
693
+ middleware[symbols.clm] = true
694
+ }
695
+ }
696
+
680
697
  /**
681
698
  * Wraps the given function in a middleware recorder function.
682
699
  *
@@ -735,6 +752,8 @@ function _recordMiddleware(shim, middleware, spec) {
735
752
  const isErrorWare = spec.type === MIDDLEWARE_TYPE_NAMES.ERRORWARE
736
753
  const getReq = shim.isFunction(spec.req) ? spec.req : _makeGetReq(shim, spec.req)
737
754
 
755
+ assignCLMSymbol(shim, middleware)
756
+
738
757
  return shim.record(
739
758
  middleware,
740
759
  spec.promise ? middlewareWithPromiseRecorder : middlewareWithCallbackRecorder
package/lib/symbols.js CHANGED
@@ -7,6 +7,7 @@
7
7
 
8
8
  module.exports = {
9
9
  cache: Symbol('cache'),
10
+ clm: Symbol('codeLevelMetrics'),
10
11
  context: Symbol('context'),
11
12
  databaseName: Symbol('databaseName'),
12
13
  disableDT: Symbol('Disable distributed tracing'), // description for backwards compatibility