newrelic 8.5.2 → 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.
@@ -16,15 +16,13 @@ const TRANSACTION_TYPES_SET = Transaction.TYPES_SET
16
16
  /**
17
17
  * Constructs a transaction managing shim.
18
18
  *
19
- * @constructor
20
- * @extends Shim
19
+ * @class
20
+ * @augments Shim
21
21
  * @classdesc
22
22
  * A helper class for working with transactions.
23
- *
24
23
  * @param {Agent} agent - The agent the shim will use.
25
24
  * @param {string} moduleName - The name of the module being instrumented.
26
25
  * @param {string} resolvedName - The full path to the loaded module.
27
- *
28
26
  * @see Shim
29
27
  * @see WebFrameworkShim
30
28
  */
@@ -75,22 +73,18 @@ TransactionShim.prototype.insertCATRequestHeaders = insertCATRequestHeaders
75
73
 
76
74
  /**
77
75
  * @interface TransactionSpec
78
- *
79
76
  * @description
80
77
  * Describes the type of transaction to be created by the function being
81
78
  * wrapped by {@link Shim#bindCreateTransaction}.
82
- *
83
79
  * @property {string} type
84
80
  * The type of transaction to create. Must be one of the values from
85
81
  * {@link Shim#TRANSACTION_TYPES}.
86
- *
87
82
  * @property {bool} [nest=false]
88
83
  * Indicates if the transaction being created is allowed to be nested within
89
84
  * another transaction of the same type. If `false`, the default, the transaction
90
85
  * will only be created if there is no existing transaction, or the current
91
86
  * transaction is of a different type. If `true`, the transaction will be
92
87
  * created regardless of the current transaction's type.
93
- *
94
88
  * @see Shim#bindCreateTransaction
95
89
  * @see Shim#TRANSACTION_TYPES
96
90
  */
@@ -105,18 +99,14 @@ TransactionShim.prototype.insertCATRequestHeaders = insertCATRequestHeaders
105
99
  * - `bindCreateTransaction(func, spec)`
106
100
  *
107
101
  * @memberof TransactionShim.prototype
108
- *
109
- * @param {Object|Function} nodule
102
+ * @param {object | Function} nodule
110
103
  * The source for the property to wrap, or a single function to wrap.
111
- *
112
104
  * @param {string} [property]
113
105
  * The property to wrap. If omitted, the `nodule` parameter is assumed to be
114
106
  * the function to wrap.
115
- *
116
107
  * @param {TransactionSpec} spec
117
108
  * The spec for creating the transaction.
118
- *
119
- * @return {Object|Function} The first parameter to this function, after
109
+ * @returns {object | Function} The first parameter to this function, after
120
110
  * wrapping it or its property.
121
111
  */
122
112
  function bindCreateTransaction(nodule, property, spec) {
@@ -164,7 +154,6 @@ function bindCreateTransaction(nodule, property, spec) {
164
154
  * back off when resolution exits the item.
165
155
  *
166
156
  * @memberof TransactionShim.prototype
167
- *
168
157
  * @param {string} pathSegment - The path segment to add to the naming stack.
169
158
  */
170
159
  function pushTransactionName(pathSegment) {
@@ -183,7 +172,6 @@ function pushTransactionName(pathSegment) {
183
172
  * this function, but we do not live in an ideal world.
184
173
  *
185
174
  * @memberof TransactionShim.prototype
186
- *
187
175
  * @param {string} [pathSegment]
188
176
  * Optional. Path segment to pop the stack repeatedly until a segment matching
189
177
  * `pathSegment` is removed.
@@ -203,7 +191,6 @@ function popTransactionName(pathSegment) {
203
191
  * Either this _or_ the naming stack should be used. Do not use them together.
204
192
  *
205
193
  * @memberof TransactionShim.prototype
206
- *
207
194
  * @param {string} name - The name to use for the transaction.
208
195
  */
209
196
  function setTransactionName(name) {
@@ -222,14 +209,11 @@ function setTransactionName(name) {
222
209
  *
223
210
  * This will check for either header naming style, and both request and reply
224
211
  * CAT headers.
225
- *
226
212
  * @param {object} headers
227
213
  * The request/response headers object to look in.
228
- *
229
214
  * @param {TraceSegment} [segment=null]
230
215
  * The trace segment to associate the header data with. If no segment is
231
216
  * provided then the currently active segment is used.
232
- *
233
217
  * @param {string} [transportType='Unknown']
234
218
  * The transport type that brought the headers. Usually `HTTP` or `HTTPS`.
235
219
  */
@@ -282,10 +266,8 @@ function handleCATHeaders(headers, segment, transportType) {
282
266
  * - `insertCATRequestHeaders(headers [, useAlternateHeaderNames])`
283
267
  *
284
268
  * @memberof TransactionShim.prototype
285
- *
286
269
  * @param {object} headers
287
270
  * The outbound request headers object to inject our CAT headers into.
288
- *
289
271
  * @param {bool} [useAlternateHeaderNames=false]
290
272
  * Indicates if HTTP-style headers should be used or alternate style. Some
291
273
  * transport protocols are more strict on the characters allowed in headers
@@ -327,10 +309,8 @@ function insertCATRequestHeaders(headers, useAlternateHeaderNames) {
327
309
  * - `insertCATReplyHeaders(headers [, useAlternateHeaderNames])`
328
310
  *
329
311
  * @memberof TransactionShim.prototype
330
- *
331
312
  * @param {object} headers
332
313
  * The outbound response headers object to inject our CAT headers into.
333
- *
334
314
  * @param {bool} [useAlternateHeaderNames=false]
335
315
  * Indicates if HTTP-style headers should be used or alternate style. Some
336
316
  * transport protocols are more strict on the characters allowed in headers
@@ -388,20 +368,15 @@ function insertCATReplyHeader(headers, useAlternateHeaderNames) {
388
368
  * `spec.type` is not the same as the current transaction's type.
389
369
  *
390
370
  * @private
391
- *
392
371
  * @param {Shim} shim
393
372
  * The shim used for the binding.
394
- *
395
- * @param {function} fn
373
+ * @param {Function} fn
396
374
  * The function link with the transaction.
397
- *
398
375
  * @param {string} name
399
376
  * The name of the wrapped function.
400
- *
401
377
  * @param {TransactionSpec} spec
402
378
  * The spec for the transaction to create.
403
- *
404
- * @return {function} A function which wraps `fn` and creates potentially nested
379
+ * @returns {Function} A function which wraps `fn` and creates potentially nested
405
380
  * transactions linked to its execution.
406
381
  */
407
382
  function _makeNestedTransWrapper(shim, fn, name, spec) {
@@ -412,7 +387,7 @@ function _makeNestedTransWrapper(shim, fn, name, spec) {
412
387
 
413
388
  // Reuse existing transactions only if the type matches.
414
389
  let transaction = shim.tracer.getTransaction()
415
- let segment = shim.tracer.segment
390
+ let segment = shim.getSegment()
416
391
 
417
392
  // Only create a new transaction if we either do not have a current
418
393
  // transaction _or_ the current transaction is not of the type we want.
@@ -433,20 +408,15 @@ function _makeNestedTransWrapper(shim, fn, name, spec) {
433
408
  * A transaction will only be created if there is not a currently active one.
434
409
  *
435
410
  * @private
436
- *
437
411
  * @param {Shim} shim
438
412
  * The shim used for the binding.
439
- *
440
- * @param {function} fn
413
+ * @param {Function} fn
441
414
  * The function link with the transaction.
442
- *
443
415
  * @param {string} name
444
416
  * The name of the wrapped function.
445
- *
446
417
  * @param {TransactionSpec} spec
447
418
  * The spec for the transaction to create.
448
- *
449
- * @return {function} A function which wraps `fn` and potentially creates a new
419
+ * @returns {Function} A function which wraps `fn` and potentially creates a new
450
420
  * transaction linked to the function's execution.
451
421
  */
452
422
  function _makeTransWrapper(shim, fn, name, spec) {
package/lib/shimmer.js CHANGED
@@ -90,9 +90,8 @@ const shimmer = (module.exports = {
90
90
  /**
91
91
  * Detects if the given function has already been wrapped.
92
92
  *
93
- * @param {function} fn - The function to look for a wrapper on.
94
- *
95
- * @return {bool} True if `fn` exists and has an attached original, else false.
93
+ * @param {Function} fn - The function to look for a wrapper on.
94
+ * @returns {bool} True if `fn` exists and has an attached original, else false.
96
95
  */
97
96
  isWrapped: function isWrapped(fn) {
98
97
  return !!(fn && fn.__NR_original)
@@ -111,7 +110,7 @@ const shimmer = (module.exports = {
111
110
  * helpful than you'd think.
112
111
  * @param {string} methods One or more names of methods or functions to extract
113
112
  * and wrap.
114
- * @param {function} wrapper A generator that, when called, returns a
113
+ * @param {Function} wrapper A generator that, when called, returns a
115
114
  * wrapped version of the original function.
116
115
  */
117
116
  wrapMethod: function wrapMethod(nodule, noduleName, methods, wrapper) {
@@ -177,8 +176,7 @@ const shimmer = (module.exports = {
177
176
  * @param {object} noduleName Human-readable module / Class name. More
178
177
  * helpful than you'd think.
179
178
  * @param {string} property The property to replace with the accessor.
180
- * @param {function} options Optional getter and setter to use for the accessor.
181
- *
179
+ * @param {Function} options Optional getter and setter to use for the accessor.
182
180
  * @returns {object} The original value of the property.
183
181
  */
184
182
  wrapDeprecated: function wrapDeprecated(nodule, noduleName, property, options) {
@@ -275,6 +273,8 @@ const shimmer = (module.exports = {
275
273
  /**
276
274
  * Patch the module.load function so that we see modules loading and
277
275
  * have an opportunity to patch them with instrumentation.
276
+ *
277
+ * @param agent
278
278
  */
279
279
  patchModule: function patchModule(agent) {
280
280
  logger.trace('Wrapping module loader.')
@@ -287,6 +287,9 @@ const shimmer = (module.exports = {
287
287
  // we can examine it after the load call has returned.
288
288
  const resolvedFilepath = resolve.apply(this, arguments)
289
289
  filepathMap[file] = resolvedFilepath
290
+
291
+ _onResolveFileName(agent, file, resolvedFilepath)
292
+
290
293
  return resolvedFilepath
291
294
  }
292
295
  })
@@ -306,6 +309,7 @@ const shimmer = (module.exports = {
306
309
  * their parent has already been loaded/cached by Node.
307
310
  * Provides a fall-back for unexpected cases that may occur.
308
311
  * Also provides flexibility for testing now that node 11+ caches these.
312
+ *
309
313
  * @param {*} request
310
314
  * @param {*} parent
311
315
  * @param {*} isMain
@@ -398,6 +402,10 @@ const shimmer = (module.exports = {
398
402
  },
399
403
 
400
404
  registerInstrumentation: function registerInstrumentation(opts) {
405
+ if (!hasValidRegisterOptions(opts)) {
406
+ return
407
+ }
408
+
401
409
  shimmer.registeredInstrumentations[opts.moduleName] = opts
402
410
  },
403
411
 
@@ -419,6 +427,9 @@ const shimmer = (module.exports = {
419
427
  * re-wrap them.
420
428
  *
421
429
  * Use this to re-apply any applicable instrumentation.
430
+ *
431
+ * @param agent
432
+ * @param modulePath
422
433
  */
423
434
  reinstrument: function reinstrument(agent, modulePath) {
424
435
  return _postLoad(agent, require(modulePath), modulePath)
@@ -428,6 +439,8 @@ const shimmer = (module.exports = {
428
439
  * Given a NodeJS module name, return the name/identifier of our
429
440
  * instrumentation. These two things are usually, but not always,
430
441
  * the same.
442
+ *
443
+ * @param moduleName
431
444
  */
432
445
  getInstrumentationNameFromModuleName(moduleName) {
433
446
  let instrumentation
@@ -467,8 +480,13 @@ function applyDebugState(shim, nodule) {
467
480
  * All instrumentation files must export the same interface: a single
468
481
  * initialization function that takes the agent and the module to be
469
482
  * instrumented.
483
+ *
484
+ * @param agent
485
+ * @param nodule
486
+ * @param moduleName
487
+ * @param resolvedName
470
488
  */
471
- function instrument(agent, nodule, moduleName, resolvedName) {
489
+ function instrumentPostLoad(agent, nodule, moduleName, resolvedName) {
472
490
  const instrumentation = shimmer.registeredInstrumentations[moduleName]
473
491
  if (
474
492
  properties.hasOwn(nodule, '__NR_instrumented') ||
@@ -536,11 +554,93 @@ function _firstPartyInstrumentation(agent, fileName, shim, nodule, moduleName) {
536
554
  function _postLoad(agent, nodule, name, resolvedName) {
537
555
  const instrumentation = shimmer.getInstrumentationNameFromModuleName(name)
538
556
 
557
+ const registeredInstrumentation = shimmer.registeredInstrumentations[instrumentation]
558
+ const hasPostLoadInstrumentation =
559
+ registeredInstrumentation && registeredInstrumentation.onRequire
560
+
539
561
  // Check if this is a known instrumentation and then run it.
540
- if (shimmer.registeredInstrumentations[instrumentation]) {
541
- logger.trace('Instrumenting %s.', name)
542
- return instrument(agent, nodule, instrumentation, resolvedName)
562
+ if (hasPostLoadInstrumentation) {
563
+ logger.trace('Instrumenting %s with onRequire (module loaded) hook.', name)
564
+ return instrumentPostLoad(agent, nodule, instrumentation, resolvedName)
543
565
  }
544
566
 
545
567
  return nodule
546
568
  }
569
+
570
+ function _onResolveFileName(agent, requiredNameOrPath, resolvedFilepath) {
571
+ const instrumentation = shimmer.getInstrumentationNameFromModuleName(requiredNameOrPath)
572
+
573
+ const registeredInstrumentation = shimmer.registeredInstrumentations[instrumentation]
574
+ const hasResolvedFileInstrumentation =
575
+ registeredInstrumentation && registeredInstrumentation.onResolved
576
+
577
+ // Check if this is a known instrumentation and then run it.
578
+ if (hasResolvedFileInstrumentation) {
579
+ logger.trace('Instrumenting %s with onResolved hook.', requiredNameOrPath)
580
+ _instrumentOnResolved(agent, instrumentation, resolvedFilepath)
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Invokes the onResolved handler with a shim instance.
586
+ *
587
+ * Given Node.js caches resolvedFilePaths in versions we support and we cache as well
588
+ * for the cases we force resolution, we should not run into the case of multiple
589
+ * invocations for the same module. As such, this function does not defend against multiple runs.
590
+ *
591
+ * @param agent
592
+ * @param moduleName
593
+ * @param resolvedFilepath
594
+ */
595
+ function _instrumentOnResolved(agent, moduleName, resolvedFilepath) {
596
+ const instrumentation = shimmer.registeredInstrumentations[moduleName]
597
+
598
+ const shim = shims.createShimFromType(instrumentation.type, agent, moduleName, resolvedFilepath)
599
+
600
+ try {
601
+ instrumentation.onResolved(shim, moduleName, resolvedFilepath)
602
+ } catch (instrumentationError) {
603
+ if (instrumentation.onError) {
604
+ try {
605
+ instrumentation.onError(instrumentationError)
606
+ } catch (error) {
607
+ logger.warn(
608
+ error,
609
+ instrumentationError,
610
+ 'OnResolved instrumentation for %s failed, then the onError handler threw an error',
611
+ moduleName
612
+ )
613
+ }
614
+ } else {
615
+ logger.warn(
616
+ instrumentationError,
617
+ 'OnResolved instrumentation for %s failed. Please report this to the ' +
618
+ 'maintainers of the custom instrumentation.',
619
+ moduleName
620
+ )
621
+ }
622
+ }
623
+ }
624
+
625
+ function hasValidRegisterOptions(opts) {
626
+ if (!opts) {
627
+ logger.warn('Instrumentation registration failed, no options provided')
628
+ return false
629
+ }
630
+
631
+ if (!opts.moduleName) {
632
+ logger.warn(`Instrumentation registration failed, 'moduleName' not provided`)
633
+ return false
634
+ }
635
+
636
+ if (!opts.onRequire && !opts.onResolved) {
637
+ logger.warn(
638
+ 'Instrumentation registration for %s failed, no require hooks provided.',
639
+ opts.moduleName
640
+ )
641
+
642
+ return false
643
+ }
644
+
645
+ return true
646
+ }
@@ -16,13 +16,13 @@ const SEGMENT = '__NR_segment'
16
16
 
17
17
  module.exports = Tracer
18
18
 
19
- function Tracer(agent) {
19
+ function Tracer(agent, contextManager) {
20
20
  if (!agent) {
21
21
  throw new Error('Must be initialized with an agent.')
22
22
  }
23
23
 
24
24
  this.agent = agent
25
- this._segment = null
25
+ this._contextManager = contextManager
26
26
  }
27
27
 
28
28
  Tracer.prototype.getTransaction = getTransaction
@@ -47,33 +47,35 @@ Tracer.prototype.wrapCallback = wrapCallback
47
47
 
48
48
  Object.defineProperty(Tracer.prototype, 'segment', {
49
49
  get: function segmentGetter() {
50
- return this._segment
50
+ return this._contextManager.getContext()
51
51
  },
52
52
  set: function segmentSetter(segment) {
53
- this._segment && this._segment.probe('Segment removed from tracer')
54
- segment && segment.probe('Set tracer.segment')
55
- return (this._segment = segment)
53
+ this._contextManager.setContext(segment)
54
+ return segment
56
55
  }
57
56
  })
58
57
 
59
58
  function getTransaction() {
60
- if (this.segment && this.segment.transaction && this.segment.transaction.isActive()) {
61
- return this.segment.transaction
59
+ const currentSegment = this._contextManager.getContext()
60
+ if (currentSegment && currentSegment.transaction && currentSegment.transaction.isActive()) {
61
+ return currentSegment.transaction
62
62
  }
63
63
 
64
64
  return null
65
65
  }
66
66
 
67
+ // TODO: Remove/replace external uses to tracer.getSegment()
67
68
  function getSegment() {
68
- return this.segment
69
+ return this._contextManager.getContext()
69
70
  }
70
71
 
71
72
  function getSpanContext() {
72
- return this.segment && this.segment.getSpanContext()
73
+ const currentSegment = this.getSegment()
74
+ return currentSegment && currentSegment.getSpanContext()
73
75
  }
74
76
 
75
77
  function createSegment(name, recorder, _parent) {
76
- const parent = _parent || this.segment
78
+ const parent = _parent || this.getSegment()
77
79
  if (!parent || !parent.transaction.isActive()) {
78
80
  logger.trace(
79
81
  {
@@ -111,7 +113,7 @@ function transactionProxy(handler) {
111
113
  }
112
114
 
113
115
  // don't nest transactions, reuse existing ones
114
- const segment = tracer.segment
116
+ const segment = tracer.getSegment()
115
117
  if (segment) {
116
118
  if (segment.transaction.traceStacks) {
117
119
  segment.probe('!!! Nested transaction creation !!!')
@@ -143,9 +145,9 @@ function transactionProxy(handler) {
143
145
  * if there is an in play segment and uses that as the root instead of
144
146
  * transaction.trace.root.
145
147
  *
146
- * @param {Function} handler - Generator to be proxied.
147
- *
148
- * @return {Function} Proxy.
148
+ * @param {string} type - Type of transaction to create. 'web' or 'bg'.
149
+ * @param {Function} handler - Generator to proxy.
150
+ * @returns {Function} Proxy.
149
151
  */
150
152
  function transactionNestProxy(type, handler) {
151
153
  if (handler === undefined && typeof type === 'function') {
@@ -165,7 +167,7 @@ function transactionNestProxy(type, handler) {
165
167
 
166
168
  // don't nest transactions, reuse existing ones
167
169
  let transaction = tracer.getTransaction()
168
- let segment = tracer.segment
170
+ let segment = tracer.getSegment()
169
171
 
170
172
  let createNew = false
171
173
 
@@ -192,7 +194,7 @@ function bindFunction(handler, segment, full) {
192
194
  return handler
193
195
  }
194
196
 
195
- return _makeWrapped(this, handler, segment || this.segment, !!full)
197
+ return _makeWrapped(this, handler, segment || this.getSegment(), !!full)
196
198
  }
197
199
  function _makeWrapped(tracer, handler, active, full) {
198
200
  wrapped[ORIGINAL] = getOriginal(handler)
@@ -201,18 +203,19 @@ function _makeWrapped(tracer, handler, active, full) {
201
203
  return wrapped
202
204
 
203
205
  function wrapped() {
204
- const prev = tracer.segment
205
- tracer.segment = active
206
+ const prev = tracer.getSegment()
207
+
206
208
  if (active && full) {
207
209
  active.start()
208
210
  }
211
+
209
212
  try {
210
- return handler.apply(this, arguments)
213
+ return tracer._contextManager.runInContext(active, handler, this, arguments)
211
214
  } catch (err) {
212
215
  logger.trace(err, 'Error from wrapped function:')
213
216
 
214
217
  if (prev === null && process.domain != null) {
215
- process.domain.__NR_transactionSegment = tracer.segment
218
+ process.domain.__NR_transactionSegment = tracer.getSegment()
216
219
  }
217
220
 
218
221
  throw err // Re-throwing application error, this is not an agent error.
@@ -220,7 +223,6 @@ function _makeWrapped(tracer, handler, active, full) {
220
223
  if (active && full) {
221
224
  active.touch()
222
225
  }
223
- tracer.segment = prev
224
226
  }
225
227
  }
226
228
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "8.5.2",
3
+ "version": "8.6.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -149,6 +149,7 @@
149
149
  "versioned-tests": "./bin/run-versioned-tests.sh",
150
150
  "update-changelog-version": "node ./bin/update-changelog-version",
151
151
  "versioned": "npm run versioned:npm7",
152
+ "versioned:major": "npm run prepare-test && VERSIONED_MODE=--major NPM7=1 time ./bin/run-versioned-tests.sh",
152
153
  "versioned:npm6": "npm run prepare-test && time ./bin/run-versioned-tests.sh",
153
154
  "versioned:npm7": "npm run prepare-test && NPM7=1 time ./bin/run-versioned-tests.sh",
154
155
  "prepare": "husky install"