newrelic 9.3.0 → 9.4.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/NEWS.md CHANGED
@@ -1,13 +1,40 @@
1
- ### v9.3.0 (2022-10-17)
2
-
1
+ ### v9.4.0 (2022-10-24)
2
+
3
+ * Removed legacy agent async context propagation. The default behavior is now what was behind the `feature_flag.new_promise_tracking`. You can read more about the difference [here](https://docs.newrelic.com/docs/release-notes/agent-release-notes/nodejs-release-notes/node-agent-7-3-0#new-features).
4
+
5
+ * Fixed an issue with the ES Module loader that properly registers instrumentation when the file path included url encoded characters.
6
+
7
+ * Added an API for enqueuing application logs for forwarding
8
+
9
+ ```js
10
+ newrelic.recordLogEvent({ message: 'hello world', level: 'info' })`
11
+ ```
12
+
13
+
14
+ **Note**: If you are including a serialized error make sure it is on the `error` key of the log event:
15
+
16
+ ```js
17
+ const error = new Error('testing errors');
18
+ newrelic.recordLogEvent({ message: 'error example', level: 'error', error })
19
+ ```
20
+
21
+ * Fixed `cassandra-driver` instrumentation to properly set instance details on query segments/spans.
22
+
23
+ * Added a new context manager that leverages AsyncLocalStorage for async context propagation.
24
+ * This will be available via a feature flag `config.feature_flag.async_local_context`
25
+ * Alternatively you can set the environment variable of `NEW_RELIC_FEATURE_FLAG_ASYNC_LOCAL_CONTEXT=1`
26
+ * By enabling this feature flag it should make the agent use less memory and CPU.
27
+
28
+ ### v9.3.0 (2022-10-17)
29
+
3
30
  * Added instrumentation to bunyan to support application logging use cases: forwarding, local decorating, and metrics.
4
31
 
5
- Big thanks to @brianphillips for his contribution 🚀
6
-
7
- * Added c8 to track code coverage.
8
-
9
- * Added documentation about custom instrumentation in ES module applications
10
-
32
+ Big thanks to @brianphillips for his contribution 🚀
33
+
34
+ * Added c8 to track code coverage.
35
+
36
+ * Added documentation about custom instrumentation in ES module applications
37
+
11
38
  ### v9.2.0 (2022-10-06)
12
39
 
13
40
  * Added ability to instrument ES Modules with the New Relic ESM Loader.
package/api.js CHANGED
@@ -18,6 +18,7 @@ const isValidType = require('./lib/util/attribute-types')
18
18
  const TransactionShim = require('./lib/shim/transaction-shim')
19
19
  const TransactionHandle = require('./lib/transaction/handle')
20
20
  const AwsLambda = require('./lib/serverless/aws-lambda')
21
+ const applicationLogging = require('./lib/util/application-logging')
21
22
 
22
23
  const ATTR_DEST = require('./lib/config/attribute-filter').DESTINATIONS
23
24
  const MODULE_TYPE = require('./lib/shim/constants').MODULE_TYPE
@@ -426,6 +427,86 @@ API.prototype.noticeError = function noticeError(error, customAttributes) {
426
427
  this.agent.errors.addUserError(transaction, error, filteredAttributes)
427
428
  }
428
429
 
430
+ /**
431
+ * Sends an application log message to New Relic. The agent already
432
+ * automatically does this for some instrumented logging libraries,
433
+ * but in case you are using another logging method that is not
434
+ * already instrumented by the agent, you can use this function
435
+ * instead.
436
+ *
437
+ * If application log forwarding is disabled in the agent
438
+ * configuration, this function does nothing.
439
+ *
440
+ * An example of using this function is
441
+ *
442
+ * newrelic.recordLogEvent({
443
+ * message: 'cannot find file',
444
+ * level: 'ERROR',
445
+ * error: new SystemError('missing.txt')
446
+ * })
447
+ *
448
+ * @param {object} logEvent The log event object to send. Any
449
+ * attributes besides `message`, `level`, `timestamp`, and `error` are
450
+ * recorded unchanged. The `logEvent` object itself will be mutated by
451
+ * this function.
452
+ * @param {string} logEvent.message The log message.
453
+ * @param {string} logEvent.level The log level severity. If this key is
454
+ * missing, it will default to UNKNOWN
455
+ * @param {number} logEvent.timestamp ECMAScript epoch number denoting the
456
+ * time that this log message was produced. If this key is missing,
457
+ * it will default to the output of `Date.now()`.
458
+ * @param {Error} logEvent.error Error associated to this log event. Ignored if missing.
459
+ */
460
+ API.prototype.recordLogEvent = function recordLogEvent(logEvent = {}) {
461
+ const metric = this.agent.metrics.getOrCreateMetric(NAMES.SUPPORTABILITY.API + '/recordLogEvent')
462
+ metric.incrementCallCount()
463
+
464
+ if (!applicationLogging.isLogForwardingEnabled(this.agent.config, this.agent)) {
465
+ logger.warnOnce(
466
+ 'Record logs',
467
+ 'Application log forwarding disabled, method API#recordLogEvent will not record messages'
468
+ )
469
+ return
470
+ }
471
+
472
+ // If they don't pass a logEvent object, or it doesn't have the
473
+ // required `message` key, bail out.
474
+ if (typeof logEvent !== 'object' || logEvent.message === undefined) {
475
+ logger.warn(
476
+ 'recordLogEvent requires an object with a `message` attribute for its single argument, got %s (%s)',
477
+ stringify(logEvent),
478
+ typeof logEvent
479
+ )
480
+ return
481
+ }
482
+ logEvent.message = applicationLogging.truncate(logEvent.message)
483
+
484
+ if (!logEvent.level) {
485
+ logger.debug('no log level set, setting it to UNKNOWN')
486
+ logEvent.level = 'UNKNOWN'
487
+ }
488
+
489
+ if (typeof logEvent.timestamp !== 'number') {
490
+ logger.debug('no timestamp set, setting it to `Date.now()`')
491
+ logEvent.timestamp = Date.now()
492
+ }
493
+
494
+ if (logEvent.error) {
495
+ logEvent['error.message'] = applicationLogging.truncate(logEvent.error.message)
496
+ logEvent['error.stack'] = applicationLogging.truncate(logEvent.error.stack)
497
+ logEvent['error.class'] =
498
+ logEvent.error.name === 'Error' ? logEvent.error.constructor.name : logEvent.error.name
499
+ delete logEvent.error
500
+ }
501
+
502
+ if (applicationLogging.isMetricsEnabled(this.agent.config)) {
503
+ applicationLogging.incrementLoggingLinesMetrics(logEvent.level, this.agent.metrics)
504
+ }
505
+
506
+ const metadata = this.agent.getLinkingMetadata()
507
+ this.agent.logs.add(Object.assign({}, logEvent, metadata))
508
+ }
509
+
429
510
  /**
430
511
  * If the URL for a transaction matches the provided pattern, name the
431
512
  * transaction with the provided name. If there are capture groups in the
package/esm-loader.mjs CHANGED
@@ -9,6 +9,7 @@ import loggingModule from './lib/logger.js'
9
9
  import NAMES from './lib/metrics/names.js'
10
10
  import semver from 'semver'
11
11
  import path from 'node:path'
12
+ import { fileURLToPath } from 'node:url'
12
13
 
13
14
  const isSupportedVersion = () => semver.gte(process.version, 'v16.12.0')
14
15
  // This check will prevent resolve hooks executing from within this file
@@ -70,7 +71,7 @@ export async function resolve(specifier, context, nextResolve) {
70
71
  const instrumentationDefinitionCopy = Object.assign({}, instrumentationDefinition)
71
72
 
72
73
  // Stripping the prefix is necessary because the code downstream gets this url without it
73
- instrumentationDefinitionCopy.moduleName = url.replace('file://', '')
74
+ instrumentationDefinitionCopy.moduleName = fileURLToPath(url)
74
75
 
75
76
  // Added to keep our Supportability metrics from exploding/including customer info via full filepath
76
77
  instrumentationDefinitionCopy.specifier = specifier
@@ -178,6 +179,9 @@ function addESMSupportabilityMetrics(agent) {
178
179
  * This is done by injecting the ESM shim which proxies every property on the exported
179
180
  * module and registers the module with shimmer so instrumentation can be registered properly.
180
181
  *
182
+ * Note: this autogenerated code _requires_ that the import have the file:// prefix!
183
+ * Without it, Node.js throws an ERR_INVALID_URL error: you've been warned.
184
+ *
181
185
  * @param {string} url the URL returned by the resolve chain
182
186
  * @param {string} specifier string identifier in an import statement or import() expression
183
187
  * @returns {string} source code rewritten to wrap with our esm-shim
@@ -185,7 +189,7 @@ function addESMSupportabilityMetrics(agent) {
185
189
  async function wrapEsmSource(url, specifier) {
186
190
  const pkg = await import(url)
187
191
  const props = Object.keys(pkg)
188
- const trimmedUrl = url.replace('file://', '')
192
+ const trimmedUrl = fileURLToPath(url)
189
193
 
190
194
  return `
191
195
  import wrapModule from '${esmShimPath.href}'
@@ -0,0 +1,69 @@
1
+ /*
2
+ * Copyright 2021 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const { AsyncLocalStorage } = require('async_hooks')
9
+
10
+ /**
11
+ * Class for managing state in the agent.
12
+ * Uses AsyncLocalStorage for context propagation of state across async boundaries.
13
+ *
14
+ * Given current usage with every instrumented function, the functions in this
15
+ * class should do as little work as possible to avoid unnecessary overhead.
16
+ *
17
+ * @class
18
+ */
19
+ class AsyncLocalContextManager {
20
+ /**
21
+ * @param {object} config New Relic config instance
22
+ */
23
+ constructor(config) {
24
+ this._config = config
25
+
26
+ this._asyncLocalStorage = new AsyncLocalStorage()
27
+ }
28
+
29
+ /**
30
+ * Get the currently active context.
31
+ *
32
+ * @returns {object} The current active context.
33
+ */
34
+ getContext() {
35
+ return this._asyncLocalStorage.getStore() || null
36
+ }
37
+
38
+ /**
39
+ * Set a new active context. Not bound to function execution.
40
+ * The AsyncLocalStorage method is considered experimental
41
+ *
42
+ * @param {object} newContext The context to set as active.
43
+ */
44
+ setContext(newContext) {
45
+ this._asyncLocalStorage.enterWith(newContext)
46
+ }
47
+
48
+ /**
49
+ * Run a function with the passed in context as the active context.
50
+ * Restores the previously active context upon completion.
51
+ *
52
+ * @param {object} context The context to set as active during callback execution.
53
+ * @param {Function} callback The function to execute in context.
54
+ * @param {Function} [cbThis] Optional `this` to apply to the callback.
55
+ * @param {Array<*>} [args] Optional arguments object or args array to invoke the callback with.
56
+ * @returns {*} Returns the value returned by the callback function.
57
+ */
58
+ runInContext(context, callback, cbThis, args) {
59
+ const toInvoke = cbThis ? callback.bind(cbThis) : callback
60
+
61
+ if (args) {
62
+ return this._asyncLocalStorage.run(context, toInvoke, ...args)
63
+ }
64
+
65
+ return this._asyncLocalStorage.run(context, toInvoke)
66
+ }
67
+ }
68
+
69
+ module.exports = AsyncLocalContextManager
@@ -5,6 +5,8 @@
5
5
 
6
6
  'use strict'
7
7
 
8
+ const semver = require('semver')
9
+
8
10
  const logger = require('../logger')
9
11
 
10
12
  /**
@@ -16,6 +18,26 @@ const logger = require('../logger')
16
18
  * the current configuration.
17
19
  */
18
20
  function createContextManager(config) {
21
+ if (config.feature_flag.async_local_context) {
22
+ // TODO: Remove >=16 check when only support 16+. AsyncLocal became stable in 16.4.0
23
+ if (semver.satisfies(process.version, '>=16.4.0')) {
24
+ return createAsyncLocalContextManager(config)
25
+ }
26
+
27
+ logger.warn('The AsyncLocalContextManager is only supported on Node version 16.4.0 and later.')
28
+ }
29
+
30
+ return createLegacyContextManager(config)
31
+ }
32
+
33
+ function createAsyncLocalContextManager(config) {
34
+ logger.info('Using AsyncLocalContextManager')
35
+
36
+ const AsyncLocalContextManager = require('./async-local-context-manager')
37
+ return new AsyncLocalContextManager(config)
38
+ }
39
+
40
+ function createLegacyContextManager(config) {
19
41
  if (config.logging.diagnostics) {
20
42
  logger.info('Using LegacyDiagnosticContextManager')
21
43
 
@@ -8,12 +8,12 @@
8
8
  // unreleased flags gating an active feature
9
9
  exports.prerelease = {
10
10
  express5: false,
11
- new_promise_tracking: false,
12
11
  promise_segments: false,
13
12
  reverse_naming_rules: false,
14
13
  undici_instrumentation: false,
15
14
  undici_async_tracking: true,
16
- unresolved_promise_cleanup: true
15
+ unresolved_promise_cleanup: true,
16
+ async_local_context: false
17
17
  }
18
18
 
19
19
  // flags that are no longer used for released features
@@ -24,6 +24,7 @@ exports.released = [
24
24
  'custom_metrics',
25
25
  'express_segments',
26
26
  'native_metrics',
27
+ 'new_promise_tracking',
27
28
  'protocol_17',
28
29
  'serverless_mode',
29
30
  'send_request_uri_attribute',
@@ -212,7 +212,7 @@ function wrapPromiseChannel(shim) {
212
212
 
213
213
  const proto = libPModel.Channel.prototype
214
214
  if (shim.isWrapped(proto.consume)) {
215
- shim.logger.trace('Promise model already isntrumented.')
215
+ shim.logger.trace('Promise model already instrumented.')
216
216
  return
217
217
  }
218
218
 
@@ -11,19 +11,10 @@ const {
11
11
  isLocalDecoratingEnabled,
12
12
  isMetricsEnabled,
13
13
  createModuleUsageMetric,
14
- incrementLoggingLinesMetrics
14
+ incrementLoggingLinesMetrics,
15
+ truncate
15
16
  } = require('../util/application-logging')
16
17
 
17
- const MAX_LENGTH = 1021
18
- const OUTPUT_LENGTH = 1024
19
- const truncate = (str) => {
20
- if (str && str.length > OUTPUT_LENGTH) {
21
- return str.substring(0, MAX_LENGTH) + '...'
22
- }
23
-
24
- return str
25
- }
26
-
27
18
  const logger = require('../logger').child({ component: 'bunyan' })
28
19
 
29
20
  function augmentLogData(originalLog, agent, nameFromLevel) {
@@ -5,16 +5,91 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- module.exports = function initialize(agent, cassandra, moduleName, shim) {
9
- const proto = cassandra.Client.prototype
8
+ const semver = require('semver')
9
+
10
+ /**
11
+ * Instruments the `cassandra-driver` module, function that is
12
+ * passed to `onRequire` when instantiating instrumentation.
13
+ *
14
+ * @param {object} _agent - NewRelic agent
15
+ * @param {object} cassandra - The cassandra-driver library definition
16
+ * @param {string} _moduleName - String representation of require/import path
17
+ * @param {object} shim - shim for instrumentation
18
+ */
19
+ module.exports = function initialize(_agent, cassandra, _moduleName, shim) {
20
+ const cassandraVersion = shim.require('./package.json').version
21
+
10
22
  shim.setDatastore(shim.CASSANDRA)
11
- shim.recordOperation(proto, ['connect', 'shutdown'], { callback: shim.LAST })
12
- if (proto._innerExecute) {
13
- shim.recordQuery(proto, '_innerExecute', { query: shim.FIRST, callback: shim.LAST })
23
+
24
+ const ClientProto = cassandra.Client.prototype
25
+ const RequestExecutionProto = shim.require('./lib/request-execution.js').prototype
26
+
27
+ shim.recordOperation(ClientProto, ['connect', 'shutdown'], { callback: shim.LAST })
28
+
29
+ if (semver.satisfies(cassandraVersion, '>=4.4.0')) {
30
+ shim.recordQuery(ClientProto, '_execute', {
31
+ query: shim.FIRST,
32
+ callback: shim.LAST
33
+ })
34
+
35
+ shim.wrap(
36
+ RequestExecutionProto,
37
+ '_sendOnConnection',
38
+ function wrapSendOnConnection(shim, _sendOnConnection) {
39
+ return function wrappedSendOnConnection() {
40
+ shim.captureInstanceAttributes(
41
+ this._connection.address,
42
+ this._connection.port,
43
+ this._connection.keyspace
44
+ )
45
+
46
+ return _sendOnConnection.apply(this, arguments)
47
+ }
48
+ }
49
+ )
14
50
  } else {
15
- shim.recordQuery(proto, '_execute', { query: shim.FIRST, callback: shim.LAST })
51
+ shim.recordQuery(ClientProto, '_innerExecute', {
52
+ query: shim.FIRST,
53
+ callback: shim.LAST
54
+ })
55
+
56
+ shim.wrap(RequestExecutionProto, 'start', function wrapStart(shim, start) {
57
+ return function wrappedStart() {
58
+ const parent = shim.getSegment()
59
+ const self = this
60
+
61
+ const args = shim.argsToArray.apply(shim, arguments)
62
+
63
+ /**
64
+ * In older versions of cassandra-driver, we can't rely on RequestExecution._sendOnConnection,
65
+ * so instead we use the callback passed to RequestExecution.start as a sort of hook point,
66
+ * because we know for sure that the connection was set and in scope right before the callback
67
+ * is executed.
68
+ *
69
+ * We also have to set the active segment to the RequestExecution.start's segment to ensure that
70
+ * we're adding the connection attributes to the correct segment.
71
+ *
72
+ * See: https://github.com/datastax/nodejs-driver/blob/v3.4.0/lib/request-execution.js#L51
73
+ */
74
+ args[0] = shim.wrap(args[0], function wrapGetHostCallback(shim, getHostCallback) {
75
+ return function wrappedGetHostCallback() {
76
+ shim.setActiveSegment(parent)
77
+
78
+ shim.captureInstanceAttributes(
79
+ self._connection.address,
80
+ self._connection.port,
81
+ self._connection.keyspace
82
+ )
83
+ return getHostCallback.apply(this, arguments)
84
+ }
85
+ })
86
+
87
+ return start.apply(this, args)
88
+ }
89
+ })
16
90
  }
17
- shim.recordBatchQuery(proto, 'batch', {
91
+
92
+ shim.recordBatchQuery(ClientProto, 'batch', {
18
93
  query: findBatchQueryArg,
19
94
  callback: shim.LAST
20
95
  })
@@ -24,9 +99,13 @@ module.exports = function initialize(agent, cassandra, moduleName, shim) {
24
99
  * Given the arguments for Cassandra's `batch` method, this finds the first
25
100
  * query in the batch.
26
101
  *
27
- * @return {string} The query for this batch request.
102
+ * @param {object} _shim - shim for instrumentation
103
+ * @param {Function} _batch - original batch function
104
+ * @param {string} _fnName - the function name (batch)
105
+ * @param {Array} args - original arguments passed to the batch function
106
+ * @returns {string} The query for this batch request.
28
107
  */
29
- function findBatchQueryArg(shim, batch, fnName, args) {
108
+ function findBatchQueryArg(_shim, _batch, _fnName, args) {
30
109
  const sql = (args[0] && args[0][0]) || ''
31
110
  return sql.query || sql
32
111
  }
@@ -11,17 +11,20 @@ const asyncHooks = require('async_hooks')
11
11
  module.exports = initialize
12
12
 
13
13
  function initialize(agent, shim) {
14
+ if (agent.config.feature_flag.async_local_context) {
15
+ logger.debug(
16
+ 'New AsyncLocalStorage context enabled. Not enabling manual async_hooks or promise instrumentation'
17
+ )
18
+
19
+ return
20
+ }
21
+
14
22
  // this map is reused to track the segment that was active when
15
23
  // the before callback is called to be replaced in the after callback
16
24
  const segmentMap = new Map()
17
25
  module.exports.segmentMap = segmentMap
18
26
 
19
- let hookHandlers = getStandardHooks(segmentMap, agent, shim)
20
-
21
- if (agent.config.feature_flag.new_promise_tracking) {
22
- logger.info('Enabling new promise tracking style via new_promise_tracking feature flag.')
23
- hookHandlers = getPromiseResolveStyleHooks(segmentMap, agent, shim)
24
- }
27
+ const hookHandlers = getPromiseResolveStyleHooks(segmentMap, agent, shim)
25
28
 
26
29
  const hook = asyncHooks.createHook(hookHandlers)
27
30
  hook.enable()
@@ -33,63 +36,6 @@ function initialize(agent, shim) {
33
36
  return true
34
37
  }
35
38
 
36
- function getStandardHooks(segmentMap, agent, shim) {
37
- const hooks = {
38
- init: function initHook(id, type, triggerId, promiseWrap) {
39
- if (type !== 'PROMISE') {
40
- return
41
- }
42
-
43
- const parentSegment = segmentMap.get(triggerId)
44
-
45
- if (parentSegment && !parentSegment.transaction.isActive()) {
46
- // Stop propagating if the transaction was ended.
47
- return
48
- }
49
-
50
- if (!parentSegment && !agent.getTransaction()) {
51
- return
52
- }
53
-
54
- const activeSegment = shim.getActiveSegment() || parentSegment
55
- if (promiseWrap && promiseWrap.promise) {
56
- promiseWrap.promise.__NR_id = id
57
- }
58
- segmentMap.set(id, activeSegment)
59
- },
60
-
61
- before: function beforeHook(id) {
62
- const hookSegment = segmentMap.get(id)
63
-
64
- if (!hookSegment) {
65
- return
66
- }
67
-
68
- segmentMap.set(id, shim.getActiveSegment())
69
- shim.setActiveSegment(hookSegment)
70
- },
71
- after: function afterHook(id) {
72
- const hookSegment = segmentMap.get(id)
73
-
74
- // hookSegment is the segment that was active before the promise
75
- // executed. If the promise is executing before a segment has been
76
- // restored, hookSegment will be null and should be restored. Thus
77
- // undefined is the only invalid value here.
78
- if (hookSegment === undefined) {
79
- return
80
- }
81
-
82
- segmentMap.set(id, shim.getActiveSegment())
83
- shim.setActiveSegment(hookSegment)
84
- },
85
- destroy: function destHook(id) {
86
- segmentMap.delete(id)
87
- }
88
- }
89
-
90
- return hooks
91
- }
92
-
93
39
  function getPromiseResolveStyleHooks(segmentMap, agent, shim) {
94
40
  const hooks = {
95
41
  init: function initHook(id, type, triggerId, asyncResource) {
@@ -8,27 +8,9 @@
8
8
  module.exports = initialize
9
9
 
10
10
  function initialize(agent, timers, moduleName, shim) {
11
- const processMethods = ['nextTick', '_nextDomainTick', '_tickDomainCallback']
12
-
13
- shim.wrap(process, processMethods, function wrapProcess(shim, fn) {
14
- return function wrappedProcess() {
15
- const segment = shim.getActiveSegment()
16
- if (!segment) {
17
- return fn.apply(this, arguments)
18
- }
19
-
20
- // Manual copy because helper methods add significant overhead in some usages
21
- const len = arguments.length
22
- const args = new Array(len)
23
- for (let i = 0; i < len; ++i) {
24
- args[i] = arguments[i]
25
- }
26
-
27
- shim.bindSegment(args, shim.FIRST, segment)
28
-
29
- return fn.apply(this, args)
30
- }
31
- })
11
+ if (!agent.config.feature_flag.async_local_context) {
12
+ instrumentProcessMethods(shim, process)
13
+ }
32
14
 
33
15
  instrumentTimerMethods(timers)
34
16
 
@@ -43,9 +25,11 @@ function initialize(agent, timers, moduleName, shim) {
43
25
 
44
26
  shim.record(nodule, asynchronizers, recordAsynchronizers)
45
27
 
46
- // We don't want to create segments for setImmediate calls, as the
47
- // object allocation may incur too much overhead in some situations
48
- shim.wrap(nodule, 'setImmediate', wrapSetImmediate)
28
+ if (!agent.config.feature_flag.async_local_context) {
29
+ // We don't want to create segments for setImmediate calls, as the
30
+ // object allocation may incur too much overhead in some situations
31
+ shim.wrap(nodule, 'setImmediate', wrapSetImmediate)
32
+ }
49
33
 
50
34
  shim.wrap(nodule, 'clearTimeout', wrapClearTimeout)
51
35
 
@@ -84,6 +68,30 @@ function initialize(agent, timers, moduleName, shim) {
84
68
  }
85
69
  }
86
70
 
71
+ function instrumentProcessMethods(shim, process) {
72
+ const processMethods = ['nextTick', '_nextDomainTick', '_tickDomainCallback']
73
+
74
+ shim.wrap(process, processMethods, function wrapProcess(shim, fn) {
75
+ return function wrappedProcess() {
76
+ const segment = shim.getActiveSegment()
77
+ if (!segment) {
78
+ return fn.apply(this, arguments)
79
+ }
80
+
81
+ // Manual copy because helper methods add significant overhead in some usages
82
+ const len = arguments.length
83
+ const args = new Array(len)
84
+ for (let i = 0; i < len; ++i) {
85
+ args[i] = arguments[i]
86
+ }
87
+
88
+ shim.bindSegment(args, shim.FIRST, segment)
89
+
90
+ return fn.apply(this, args)
91
+ }
92
+ })
93
+ }
94
+
87
95
  function makeWrappedPromisifyCompatible(shim, timers) {
88
96
  const originalSetTimeout = shim.getOriginal(timers.setTimeout)
89
97
  Object.getOwnPropertySymbols(originalSetTimeout).forEach((symbol) => {
@@ -95,8 +103,10 @@ function makeWrappedPromisifyCompatible(shim, timers) {
95
103
  timers.setInterval[symbol] = originalSetInterval[symbol]
96
104
  })
97
105
 
98
- const originalSetImmediate = shim.getOriginal(timers.setImmediate)
99
- Object.getOwnPropertySymbols(originalSetImmediate).forEach((symbol) => {
100
- timers.setImmediate[symbol] = originalSetImmediate[symbol]
101
- })
106
+ if (!shim.agent.config.feature_flag.async_local_context) {
107
+ const originalSetImmediate = shim.getOriginal(timers.setImmediate)
108
+ Object.getOwnPropertySymbols(originalSetImmediate).forEach((symbol) => {
109
+ timers.setImmediate[symbol] = originalSetImmediate[symbol]
110
+ })
111
+ }
102
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "9.3.0",
3
+ "version": "9.4.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -146,7 +146,8 @@
146
146
  "bench": "node ./bin/run-bench.js",
147
147
  "docker-env": "./bin/docker-env-vars.sh",
148
148
  "docs": "npm ci && jsdoc -c ./jsdoc-conf.json --private -r .",
149
- "integration": "npm run prepare-test && npm run sub-install && time c8 -o ./coverage/integration tap test/integration/**/**/*.tap.js --timeout=180 --no-coverage --reporter classic",
149
+ "integration": "npm run prepare-test && npm run sub-install && time tap --test-regex='(\\/|^test\\/integration\\/.*\\.tap\\.js)$' --timeout=180 --no-coverage --reporter classic",
150
+ "integration:async-local": "npm run prepare-test && npm run sub-install && time tap --test-regex='(\\/|^test\\/integration\\/.*\\.tap\\.js)$' --timeout=180 --no-coverage --reporter classic --test-env=NEW_RELIC_FEATURE_FLAG_ASYNC_LOCAL_CONTEXT=1",
150
151
  "prepare-test": "npm run ssl && npm run docker-env",
151
152
  "lint": "eslint ./*.{js,mjs} lib test bin examples",
152
153
  "lint:fix": "eslint --fix, ./*.{js,mjs} lib test bin examples",
@@ -159,15 +160,18 @@
159
160
  "test": "npm run integration && npm run unit && npm run unit:esm",
160
161
  "third-party-updates": "oss third-party manifest --includeOptDeps && oss third-party notices --includeOptDeps && git add THIRD_PARTY_NOTICES.md third_party_manifest.json",
161
162
  "unit": "rm -f newrelic_agent.log && time c8 -o ./coverage/unit tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.js)$' --timeout=180 --no-coverage --reporter classic",
163
+ "unit:async-local": "rm -f newrelic_agent.log && time tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.js)$' --timeout=180 --no-coverage --reporter classic --test-env=NEW_RELIC_FEATURE_FLAG_ASYNC_LOCAL_CONTEXT=1",
162
164
  "unit:esm": "rm -f newrelic_agent.log && time c8 -o ./coverage/esm-unit --include *.mjs --include **/*.mjs --exclude false tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.mjs)$' --timeout=180 --no-coverage --reporter classic --node-arg=--experimental-loader=testdouble",
163
165
  "update-cross-agent-tests": "./bin/update-cats.sh",
164
166
  "versioned-tests": "./bin/run-versioned-tests.sh",
165
167
  "update-changelog-version": "node ./bin/update-changelog-version",
166
168
  "checkout-external-versioned": "node ./test/versioned-external/checkout-external-tests.js",
167
169
  "versioned": "npm run versioned:npm7",
168
- "versioned:major": "npm run checkout-external-versioned && npm run prepare-test && VERSIONED_MODE=--major NPM7=1 time ./bin/run-versioned-tests.sh",
170
+ "versioned:major": "VERSIONED_MODE=--major npm run versioned:npm7",
169
171
  "versioned:npm6": "npm run checkout-external-versioned && npm run prepare-test && time ./bin/run-versioned-tests.sh",
170
172
  "versioned:npm7": "npm run checkout-external-versioned && npm run prepare-test && NPM7=1 time ./bin/run-versioned-tests.sh",
173
+ "versioned:async-local": "NEW_RELIC_FEATURE_FLAG_ASYNC_LOCAL_CONTEXT=1 npm run versioned:npm7",
174
+ "versioned:async-local:major": "NEW_RELIC_FEATURE_FLAG_ASYNC_LOCAL_CONTEXT=1 npm run versioned:major",
171
175
  "prepare": "husky install"
172
176
  },
173
177
  "bin": {