newrelic 5.13.0 → 6.2.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/lib/config/env.js CHANGED
@@ -51,6 +51,11 @@ const ENV_MAPPING = {
51
51
  include: 'NEW_RELIC_TRANSACTION_EVENTS_ATTRIBUTES_INCLUDE'
52
52
  }
53
53
  },
54
+ diagnostics: {
55
+ code_injector: {
56
+ enabled: 'NEW_RELIC_DIAGNOSTICS_CODE_INJECTOR_ENABLED'
57
+ }
58
+ },
54
59
  transaction_tracer: {
55
60
  attributes: {
56
61
  enabled: 'NEW_RELIC_TRANSACTION_TRACER_ATTRIBUTES_ENABLED',
@@ -139,6 +144,11 @@ const ENV_MAPPING = {
139
144
  serverless_mode: {
140
145
  enabled: 'NEW_RELIC_SERVERLESS_MODE_ENABLED'
141
146
  },
147
+ plugins: {
148
+ native_metrics: {
149
+ enabled: 'NEW_RELIC_NATIVE_METRICS_ENABLED'
150
+ }
151
+ },
142
152
  trusted_account_key: 'NEW_RELIC_TRUSTED_ACCOUNT_KEY',
143
153
  primary_application_id: 'NEW_RELIC_PRIMARY_APPLICATION_ID',
144
154
  account_id: 'NEW_RELIC_ACCOUNT_ID'
@@ -169,6 +179,11 @@ const LIST_VARS = new Set([
169
179
  'NEW_RELIC_AUDIT_LOG_ENDPOINTS'
170
180
  ])
171
181
 
182
+ // Mapping to customize the split delimiter for the LIST_VARS
183
+ const LIST_VARS_CUSTOM_DELIMITERS = {
184
+ 'NEW_RELIC_APP_NAME': /;|,/
185
+ }
186
+
172
187
  // Values in object lists are comma-delimited object literals
173
188
  const OBJECT_LIST_VARS = new Set([
174
189
  'NEW_RELIC_NAMING_RULES'
@@ -186,6 +201,7 @@ const BOOLEAN_VARS = new Set([
186
201
  'NEW_RELIC_TRACER_ENABLED',
187
202
  'NEW_RELIC_TRANSACTION_TRACER_ATTRIBUTES_ENABLED',
188
203
  'NEW_RELIC_TRANSACTION_EVENTS_ATTRIBUTES_ENABLED',
204
+ 'NEW_RELIC_DIAGNOSTICS_CODE_INJECTOR_ENABLED',
189
205
  'NEW_RELIC_TRANSACTION_SEGMENTS_ATTRIBUTES_ENABLED',
190
206
  'NEW_RELIC_DEBUG_METRICS',
191
207
  'NEW_RELIC_DEBUG_TRACER',
@@ -210,7 +226,8 @@ const BOOLEAN_VARS = new Set([
210
226
  'NEW_RELIC_UTILIZATION_DETECT_DOCKER',
211
227
  'NEW_RELIC_UTILIZATION_DETECT_GCP',
212
228
  'NEW_RELIC_UTILIZATION_DETECT_KUBERNETES',
213
- 'NEW_RELIC_SERVERLESS_MODE_ENABLED'
229
+ 'NEW_RELIC_SERVERLESS_MODE_ENABLED',
230
+ 'NEW_RELIC_NATIVE_METRICS_ENABLED'
214
231
  ])
215
232
 
216
233
  const FLOAT_VARS = new Set([
@@ -225,6 +242,7 @@ const INT_VARS = new Set([
225
242
 
226
243
  exports.ENV_MAPPING = ENV_MAPPING
227
244
  exports.LIST_VARS = LIST_VARS
245
+ exports.LIST_VARS_CUSTOM_DELIMITERS = LIST_VARS_CUSTOM_DELIMITERS
228
246
  exports.OBJECT_LIST_VARS = OBJECT_LIST_VARS
229
247
  exports.BOOLEAN_VARS = BOOLEAN_VARS
230
248
  exports.FLOAT_VARS = FLOAT_VARS
@@ -135,12 +135,11 @@ function Config(config) {
135
135
  this.sampling_target_period_in_seconds = 60
136
136
  this.max_payload_size_in_bytes = DEFAULT_MAX_PAYLOAD_SIZE_IN_BYTES
137
137
 
138
- // how frequently harvester runs
139
- this.data_report_period = 60
140
-
141
138
  // this value is arbitrary
142
139
  this.max_trace_segments = 900
143
140
 
141
+ this.entity_guid = null
142
+
144
143
  // feature level of this account
145
144
  this.product_level = 0
146
145
  // product-level related
@@ -182,7 +181,7 @@ function Config(config) {
182
181
  this.event_harvest_config = {
183
182
  report_period_ms: 60000,
184
183
  harvest_limits: {
185
- analytic_event_data: this.transaction_events.max_samples_per_minute,
184
+ analytic_event_data: this.transaction_events.max_samples_stored,
186
185
  custom_event_data: this.custom_insights_events.max_samples_stored,
187
186
  error_event_data: this.error_collector.max_event_samples_stored,
188
187
  span_event_data: SPAN_EVENT_LIMIT
@@ -338,7 +337,6 @@ Config.prototype._fromServer = function _fromServer(params, key) {
338
337
 
339
338
  case 'apdex_t':
340
339
  case 'web_transactions_apdex':
341
- case 'data_report_period':
342
340
  this._updateIfChanged(params, key)
343
341
  break
344
342
  case 'event_harvest_config':
@@ -551,6 +549,11 @@ Config.prototype._fromServer = function _fromServer(params, key) {
551
549
  )
552
550
  break
553
551
 
552
+ // Entity GUID
553
+ case 'entity_guid':
554
+ this.entity_guid = params[key]
555
+ break
556
+
554
557
  // These settings aren't supported by the agent (yet).
555
558
  case 'sampling_rate':
556
559
  case 'episodes_file':
@@ -578,7 +581,6 @@ Config.prototype._fromServer = function _fromServer(params, key) {
578
581
  case 'transaction_events.attributes.exclude':
579
582
  case 'transaction_events.attributes.include':
580
583
  case 'transaction_events.max_samples_stored':
581
- case 'transaction_events.max_samples_per_minute':
582
584
  case 'transaction_tracer.attributes.enabled':
583
585
  case 'transaction_tracer.attributes.exclude':
584
586
  case 'transaction_tracer.attributes.include':
@@ -1029,7 +1031,7 @@ Config.prototype._fromPassed = function _fromPassed(external, internal, arbitrar
1029
1031
  return
1030
1032
  }
1031
1033
 
1032
- if (typeof node === 'object' && !Array.isArray(node)) {
1034
+ if (typeof node === 'object' && !(node instanceof RegExp) && !Array.isArray(node)) {
1033
1035
  // is top level and can have arbitrary keys
1034
1036
  var allowArbitrary = internal === this || HAS_ARBITRARY_KEYS.has(key)
1035
1037
  this._fromPassed(node, internal[key], allowArbitrary)
@@ -1104,7 +1106,8 @@ Config.prototype._fromEnvironment = function _fromEnvironment(metadata, data) {
1104
1106
  var setting = process.env[node]
1105
1107
  if (setting) {
1106
1108
  if (ENV.LIST_VARS.has(node)) {
1107
- data[value] = setting.split(',').map(function trimVal(k) {
1109
+ let split = ENV.LIST_VARS_CUSTOM_DELIMITERS[node] || /,/
1110
+ data[value] = setting.split(split).map(function trimVal(k) {
1108
1111
  return k.trim()
1109
1112
  })
1110
1113
  } else if (ENV.OBJECT_LIST_VARS.has(node)) {
@@ -1146,6 +1149,25 @@ Config.prototype._loggingManuallySet = function _loggingManuallySet(inputConfig)
1146
1149
  return (inputEnabled !== undefined) || (envEnabled !== undefined)
1147
1150
  }
1148
1151
 
1152
+ /**
1153
+ * Returns true if native-metrics has been manually enabled via configuration
1154
+ * file or enveironment variable
1155
+ *
1156
+ * @param {*} inputConfig configuration pass to the Config constructor
1157
+ *
1158
+ * @returns {boolean}
1159
+ */
1160
+ Config.prototype._nativeMetricsManuallySet =
1161
+ function _nativeMetricsManuallySet(inputConfig) {
1162
+ const inputEnabled = inputConfig
1163
+ && inputConfig.plugins
1164
+ && inputConfig.plugins.native_metrics
1165
+ && inputConfig.plugins.native_metrics.enabled
1166
+ const envEnabled = process.env.NEW_RELIC_NATIVE_METRICS_ENABLED
1167
+
1168
+ return (inputEnabled !== undefined) || (envEnabled !== undefined)
1169
+ }
1170
+
1149
1171
  /**
1150
1172
  * Returns true if distributed tracing has been manually enabled via configuration file or
1151
1173
  * environment variable.
@@ -1189,6 +1211,24 @@ Config.prototype._enforceServerless = function _enforceServerless(inputConfig) {
1189
1211
  )
1190
1212
  }
1191
1213
 
1214
+ if (this._nativeMetricsManuallySet(inputConfig) &&
1215
+ this.plugins.native_metrics.enabled) {
1216
+ logger.info(
1217
+ 'Enabling the native-metrics module when in serverless mode may greatly ' +
1218
+ 'increase cold-start times. Given the limited benefit of the VM metrics' +
1219
+ 'and general lack of control in a serverless environment, we do not ' +
1220
+ 'recommend this trade-off.'
1221
+ )
1222
+ } else {
1223
+ this.plugins.native_metrics.enabled = false
1224
+
1225
+ logger.info(
1226
+ 'The native-metrics module is disabled by default when serverless_mode ' +
1227
+ 'is enabled. If desired, enable the native-metrics module via config file ' +
1228
+ 'or environment variable.'
1229
+ )
1230
+ }
1231
+
1192
1232
  if (!this._DTManuallySet(inputConfig)) {
1193
1233
  this.distributed_tracing.enabled = true
1194
1234
  }
@@ -1207,6 +1247,10 @@ Config.prototype._enforceServerless = function _enforceServerless(inputConfig) {
1207
1247
  }
1208
1248
  // default trusted_account_key to account_id
1209
1249
  this.trusted_account_key = this.trusted_account_key || this.account_id
1250
+
1251
+ // Not required in serverless mode but must default to Unknown to function.
1252
+ this.primary_application_id = this.primary_application_id || 'Unknown'
1253
+
1210
1254
  return
1211
1255
  }
1212
1256
  const DT_KEYS = ['account_id', 'primary_application_id', 'trusted_account_key']
@@ -0,0 +1,161 @@
1
+ 'use strict'
2
+
3
+ // to avoid parsing the esprima code base we require it here
4
+ // TODO: excise this with better internal file filtering
5
+ const esprima = require('esprima')
6
+ const ecg = require('escodegen')
7
+ const fileNameToken = {
8
+ type: 'Literal',
9
+ value: null
10
+ }
11
+ const lineNumberTokenTemplate = {
12
+ type: 'Literal',
13
+ value: null
14
+ }
15
+
16
+ class Mutator {
17
+ constructor(appliesTo, mutate) {
18
+ this.appliesTo = appliesTo
19
+ this.mutate = mutate
20
+ this.tokens = []
21
+ }
22
+
23
+ add(token) {
24
+ this.tokens.push(token)
25
+ }
26
+
27
+ apply() {
28
+ this.tokens.forEach(this.mutate)
29
+ }
30
+ }
31
+
32
+ const functionTypes = new Set([
33
+ 'ArrowFunctionExpression',
34
+ 'FunctionExpression',
35
+ 'AsyncFunction'
36
+ ])
37
+
38
+ const equalityCheckOps = new Set([
39
+ '==',
40
+ '===',
41
+ '!=',
42
+ '!==',
43
+ 'instanceof'
44
+ ])
45
+
46
+ const variableTypes = new Set([
47
+ 'MemberExpression',
48
+ 'Identifier'
49
+ ])
50
+
51
+ const callTypes = new Set([
52
+ 'CallExpression',
53
+ 'NewExpression'
54
+ ])
55
+
56
+ const wrapTemplate = esprima.parse('__NR_wrap()').body[0].expression
57
+ const unwrapTemplate = esprima.parse('__NR_unwrap()').body[0].expression
58
+
59
+ const mutators = [
60
+ new Mutator(function wrapAssignmentPredicate(token) {
61
+ return token.type === 'AssignmentExpression' &&
62
+ token.operator === '=' &&
63
+ functionTypes.has(token.right.type)
64
+ }, function injectWrapAssignment(token) {
65
+ token.right = wrapToken(token.right)
66
+ }),
67
+ new Mutator(function wrapArgPredicate(token) {
68
+ return callTypes.has(token.type)
69
+ }, function injectWrapArg(token) {
70
+ token.arguments = token.arguments.map(wrapToken)
71
+ }),
72
+ new Mutator(function unwrapPredicate(token) {
73
+ return token.type === 'BinaryExpression' && equalityCheckOps.has(token.operator)
74
+ }, function injectUnwrap(token) {
75
+ token.left = unwrapToken(token.left)
76
+ token.right = unwrapToken(token.right)
77
+ })
78
+ ]
79
+
80
+ function wrapToken(argToken) {
81
+ const type = argToken.type
82
+ if (
83
+ !functionTypes.has(type) &&
84
+ !callTypes.has(type) &&
85
+ !variableTypes.has(type) ||
86
+ !argToken.loc
87
+ ) {
88
+ return argToken
89
+ }
90
+
91
+ const wrapped = Object.assign({}, wrapTemplate)
92
+ const lineNumberToken = Object.assign({}, lineNumberTokenTemplate)
93
+ lineNumberToken.value = argToken.loc.start.line
94
+ wrapped.arguments = [argToken, lineNumberToken, Object.assign({}, fileNameToken)]
95
+ return wrapped
96
+ }
97
+
98
+ function unwrapToken(argToken) {
99
+ if (!variableTypes.has(argToken.type)) {
100
+ return argToken
101
+ }
102
+
103
+ const wrapped = Object.assign({}, unwrapTemplate)
104
+ wrapped.arguments = [argToken]
105
+ return wrapped
106
+ }
107
+
108
+ function inject(sourceCode, file) {
109
+ // wrap the incoming file code to make it more palatable for esprima.
110
+ // node likewise wraps the contents of the file in a function, so this
111
+ // replicates the behavior (e.g. allows for global returns)
112
+ sourceCode = 'function main() {' + sourceCode + '}'
113
+ const sourceRootBody = esprima.parse(sourceCode, {loc: true}).body[0].body.body
114
+
115
+ const toRelax = [].concat(sourceRootBody)
116
+
117
+ while (toRelax.length) {
118
+ const currentToken = toRelax.pop()
119
+
120
+ mutators.forEach(m => {
121
+ if (m.appliesTo(currentToken)) {
122
+ m.add(currentToken)
123
+ }
124
+ })
125
+
126
+ for (let key of Object.keys(currentToken)) {
127
+ if (key === 'loc') continue
128
+ const value = currentToken[key]
129
+ if (value && value instanceof Object) {
130
+ if (Array.isArray(value)) {
131
+ for (let t of value) {
132
+ if (t) {
133
+ toRelax.push(t)
134
+ }
135
+ }
136
+ } else {
137
+ toRelax.push(value)
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ // TODO: make this less janky
144
+ fileNameToken.value = file
145
+ mutators.forEach(m => m.apply())
146
+ fileNameToken.value = null
147
+ // create a new base level token that contains all the statements we
148
+ // want to pass back to node
149
+ const printed = ecg.generate({
150
+ type: 'Program',
151
+ body: sourceRootBody,
152
+ sourceType: 'script'
153
+ }, {
154
+ format: {
155
+ semicolons: false
156
+ }
157
+ })
158
+ return printed
159
+ }
160
+
161
+ module.exports = { inject }
@@ -32,9 +32,10 @@ module.exports = function initialize(agent, bluebird, moduleName, shim) {
32
32
  ])
33
33
 
34
34
  shim.wrapCast(Promise, [
35
- 'all', 'any', 'attempt', 'bind', 'cast', 'delay', 'each', 'filter',
36
- 'fromCallback', 'fromNode', 'fulfilled', 'join', 'map', 'mapSeries',
37
- 'props', 'race', 'reduce', 'reject', 'rejected', 'resolve', 'some', 'try'
35
+ 'all', 'allSettled', 'any', 'attempt', 'bind', 'cast', 'delay', 'each',
36
+ 'filter', 'fromCallback', 'fromNode', 'fulfilled', 'join', 'map',
37
+ 'mapSeries', 'props', 'race', 'reduce', 'reject', 'rejected', 'resolve',
38
+ 'some', 'try'
38
39
  ])
39
40
  shim.wrapPromisify(Promise, ['coroutine', 'method', 'promisify'])
40
41
 
@@ -138,6 +138,7 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
138
138
  }
139
139
  }
140
140
  segment.addAttribute('url', `${proto}//${hostname}${parsed.path}`)
141
+ segment.addAttribute('procedure', opts.method || 'GET')
141
142
 
142
143
  // Wrap the emit method. We're doing a special wrapper instead of using
143
144
  // `tracer.bindEmitter` because we want to do some logic based on certain
@@ -111,13 +111,21 @@ function wrapQueriable(shim, queriable, isPoolQuery) {
111
111
  }
112
112
 
113
113
  var proto = Object.getPrototypeOf(queriable)
114
+
115
+ var describe
114
116
  if (isPoolQuery) {
115
- shim.recordQuery(proto, 'query', describePoolQuery)
117
+ describe = describePoolQuery
116
118
  } else {
117
- shim.recordQuery(proto, 'query', describeQuery)
119
+ describe = describeQuery
118
120
  shim.setInternalProperty(proto, '__NR_databaseName', null)
119
121
  }
120
122
 
123
+ shim.recordQuery(proto, 'query', describe)
124
+
125
+ if (queriable.execute) {
126
+ shim.recordQuery(proto, 'execute', describe)
127
+ }
128
+
121
129
  return true
122
130
  }
123
131
 
@@ -5,7 +5,7 @@ var MODULE_TYPE = require('./shim/constants').MODULE_TYPE
5
5
  // Return a new copy of this array every time we're called
6
6
  module.exports = function instrumentations() {
7
7
  return {
8
- 'aws-sdk': {type: MODULE_TYPE.GENERIC},
8
+ 'aws-sdk': {module: '@newrelic/aws-sdk'},
9
9
  'amqplib': {type: MODULE_TYPE.MESSAGE},
10
10
  'cassandra-driver': {type: MODULE_TYPE.DATASTORE},
11
11
  'connect': {type: MODULE_TYPE.WEB_FRAMEWORK},
package/lib/shimmer.js CHANGED
@@ -6,6 +6,7 @@ var logger = require('./logger').child({component: 'shimmer'})
6
6
  var INSTRUMENTATIONS = require('./instrumentations')()
7
7
  var properties = require('./util/properties')
8
8
  var shims = require('./shim')
9
+ const { inject } = require('./injector')
9
10
 
10
11
  var MODULE_TYPE = shims.constants.MODULE_TYPE
11
12
 
@@ -56,6 +57,13 @@ var CORE_INSTRUMENTATION = {
56
57
  }
57
58
  }
58
59
 
60
+ const FORCE_MODULE_RESOLUTION_WARNING =
61
+ 'Unable to retrieve cached path for one or more modules ' +
62
+ 'with an already loaded parent. Forcing resolution. ' +
63
+ 'This should not occur during normal agent execution. ' +
64
+ 'Module resolution performance my be impacted. ' +
65
+ 'See trace-level logs for specific modules.'
66
+
59
67
  /**
60
68
  * Unwrapping is only likely to be used by test code, and is a fairly drastic
61
69
  * maneuver, but it should be pretty safe if there's a desire to reboot the
@@ -342,6 +350,121 @@ var shimmer = module.exports = {
342
350
  var Module = require('module')
343
351
  var filepathMap = {}
344
352
 
353
+ // TODO: how do we unpatch this?
354
+ const diagConf = agent.config.diagnostics.code_injector
355
+ if (diagConf.enabled) {
356
+ // XXX: this is required to handle unwrapping event listener removal
357
+ const EEProto = require('events').EventEmitter.prototype
358
+ shimmer.wrapMethod(
359
+ EEProto,
360
+ 'EventEmitter.prototype',
361
+ ['addListener', 'on', 'once', 'off', 'prependListener', 'prependOnceListener'],
362
+ function wrapAddListener(addListener) {
363
+ return function wrappedAddListener(ev, listener) {
364
+ return addListener.call(this, ev, __NR_unwrap(listener))
365
+ }
366
+ }
367
+ )
368
+ shimmer.wrapMethod(
369
+ EEProto,
370
+ 'EventEmitter.prototype',
371
+ 'removeListener',
372
+ function wrapRemoveListener(removeListener) {
373
+ return function wrappedRemoveListener(ev, listener) {
374
+ return removeListener.call(this, ev, __NR_unwrap(listener))
375
+ }
376
+ }
377
+ )
378
+ const internalCodePattern = diagConf.internal_file_pattern
379
+ const proto = Module.prototype
380
+ shimmer.wrapMethod(
381
+ proto,
382
+ 'Module.prototype',
383
+ '_compile',
384
+ function wrapCompile(_compile) {
385
+ return function wrappedCompile(code, file) {
386
+ let injected
387
+ try {
388
+ injected = internalCodePattern.test(file) ? code : inject(code, file)
389
+ return _compile.call(this, injected, file)
390
+ } catch (e) {
391
+ logger.debug('Unable to parse file:', file, e)
392
+ return _compile.call(this, code, file)
393
+ }
394
+ }
395
+ }
396
+ )
397
+
398
+ // TODO: use shimmer methods
399
+ global.tracer = agent.tracer
400
+ global.__NR_wrap = __NR_wrap
401
+ function __NR_wrap(f, lineNum, fileName) {
402
+ const scheduledSegment = agent.tracer.getSegment()
403
+ if (
404
+ typeof f !== 'function' ||
405
+ !scheduledSegment ||
406
+ !scheduledSegment.transaction.isActive()
407
+ ) {
408
+ return f
409
+ }
410
+
411
+
412
+ const scheduledTransaction = scheduledSegment.transaction
413
+ return new Proxy(f, {
414
+ get: function getTrap(target, prop) {
415
+ // Allow for look up of the target
416
+ if (prop === '__NR_new_original') {
417
+ return target
418
+ }
419
+ return target[prop]
420
+ },
421
+ construct: function constructTrap(Target, proxyArgs) {
422
+ const currentSegment = agent.tracer.getSegment()
423
+ if (
424
+ scheduledTransaction.isActive() &&
425
+ (
426
+ !currentSegment ||
427
+ currentSegment.transaction.id !== scheduledTransaction.id
428
+ )
429
+ ) {
430
+ logger.info(
431
+ `lost state in ${fileName} (line ${lineNum});`,
432
+ `expected to be in transaction ${scheduledTransaction.id},`,
433
+ `instead landed in ${currentSegment && currentSegment.transaction.id}`
434
+ )
435
+ }
436
+ return new Target(...proxyArgs)
437
+ },
438
+ apply: function wrappedApply(target, thisArg, args) {
439
+ const currentSegment = agent.tracer.getSegment()
440
+ if (
441
+ scheduledTransaction.isActive() &&
442
+ (
443
+ !currentSegment ||
444
+ currentSegment.transaction.id !== scheduledTransaction.id
445
+ )
446
+ ) {
447
+ logger.info(
448
+ `lost state in ${fileName} (line ${lineNum});`,
449
+ `expected to be in transaction ${scheduledTransaction.id},`,
450
+ `instead landed in ${currentSegment && currentSegment.transaction.id}`
451
+ )
452
+ }
453
+ return target.apply(thisArg, args)
454
+ }
455
+ })
456
+ }
457
+
458
+ global.__NR_unwrap = __NR_unwrap
459
+ function __NR_unwrap(f) {
460
+ if (typeof f !== 'function' || !f || !f.__NR_new_original) {
461
+ return f
462
+ }
463
+
464
+ return f.__NR_new_original
465
+ }
466
+ }
467
+
345
468
  shimmer.wrapMethod(Module, 'Module', '_resolveFilename', function wrapRes(resolve) {
346
469
  return function wrappedResolveFilename(file) {
347
470
  // This is triggered by the load call, so record the path that has been seen so
@@ -353,14 +476,39 @@ var shimmer = module.exports = {
353
476
  })
354
477
 
355
478
  shimmer.wrapMethod(Module, 'Module', '_load', function wrapLoad(load) {
356
- return function wrappedLoad(file) {
357
- // Because we can only observe the call and return of the load function, and the
358
- // load function calls resolve, we must mirror the recursive load calls with a
359
- // stack.
479
+ return function wrappedLoad(request, parent, isMain) {
480
+ // _load() will invoke _resolveFilename() first time resolving a module.
360
481
  const m = load.apply(this, arguments)
361
- return _postLoad(agent, m, file, filepathMap[file])
482
+
483
+ const fileName = resolveFileName(request, parent, isMain)
484
+ return _postLoad(agent, m, request, fileName)
362
485
  }
363
486
  })
487
+
488
+ /**
489
+ * Forces file name resolve for modules not in our cache when
490
+ * their parent has already been loaded/cached by Node.
491
+ * Provides a fall-back for unexpected cases that may occur.
492
+ * Also provides flexibilty for testing now that node 11+ caches these.
493
+ * @param {*} request
494
+ * @param {*} parent
495
+ * @param {*} isMain
496
+ */
497
+ function resolveFileName(request, parent, isMain) {
498
+ const cachedPath = filepathMap[request]
499
+ if (!cachedPath && parent && parent.loaded) {
500
+ logger.warnOnce('Force Resolution', FORCE_MODULE_RESOLUTION_WARNING)
501
+
502
+ if (logger.traceEnabled()) {
503
+ logger.trace(`No cached path found for ${request}. Forcing resolution.`)
504
+ }
505
+
506
+ // Our patched _resolveFilename will cache. No need to here.
507
+ return Module._resolveFilename(request, parent, isMain)
508
+ }
509
+
510
+ return cachedPath
511
+ }
364
512
  },
365
513
 
366
514
  unpatchModule: function unpatchModule() {
@@ -40,7 +40,7 @@ class SpanEventAggregator extends EventAggregator {
40
40
  spanLogger.trace({
41
41
  spansCollected: this.length,
42
42
  spansSeen: this.seen
43
- }, 'Entity stats on harvest')
43
+ }, 'Entity stats on span harvest')
44
44
  }
45
45
  super.send()
46
46
  }
@@ -88,7 +88,7 @@ class SpanEvent {
88
88
 
89
89
  const tx = segment.transaction
90
90
 
91
- span.intrinsics.traceId = tx.traceId || tx.id
91
+ span.intrinsics.traceId = tx.getTraceId()
92
92
  span.intrinsics.guid = segment.id
93
93
  span.intrinsics.parentId = parentId
94
94
  span.intrinsics.transactionId = tx.id
@@ -35,6 +35,13 @@ module.exports = class TransactionHandle {
35
35
  this._transaction.setForceIgnore(true)
36
36
  }
37
37
 
38
+ /**
39
+ * Return whether this Transaction is being sampled
40
+ */
41
+ isSampled() {
42
+ return this._transaction.isSampled()
43
+ }
44
+
38
45
  /**
39
46
  *
40
47
  * Proxy method for Transaction#createDistrubtedTracePayload.
@@ -70,6 +77,10 @@ module.exports.Stub = class TransactionHandleStub {
70
77
  logger.debug("No transaction found when calling Transaction.ignore")
71
78
  }
72
79
 
80
+ isSampled() {
81
+ logger.debug("No transaction found when calling Transaction.isSampled")
82
+ }
83
+
73
84
  createDistributedTracePayload() {
74
85
  logger.debug(
75
86
  "No transaction found when calling Transaction.createDistributedTracePayload"
@@ -72,7 +72,6 @@ function Transaction(agent) {
72
72
  )
73
73
 
74
74
  ++agent.activeTransactions
75
- ++agent.transactionCreatedInHarvest
76
75
 
77
76
  this.numSegments = 0
78
77
  this.id = hashes.makeId()
@@ -972,7 +971,7 @@ function createDistributedTracePayload() {
972
971
  ac: accountId,
973
972
  ap: appId,
974
973
  tx: this.id,
975
- tr: this.traceId || this.id,
974
+ tr: this.getTraceId(),
976
975
  pr: this.priority,
977
976
  sa: this.sampled,
978
977
  ti: Date.now()
@@ -1000,7 +999,7 @@ function addDistributedTraceIntrinsics(attrs) {
1000
999
  this._calculatePriority()
1001
1000
 
1002
1001
  // *always* add these if DT flag is enabled.
1003
- attrs.traceId = this.traceId || this.id
1002
+ attrs.traceId = this.getTraceId()
1004
1003
  attrs.guid = this.id
1005
1004
  attrs.priority = this.priority
1006
1005
 
@@ -1028,6 +1027,11 @@ function addDistributedTraceIntrinsics(attrs) {
1028
1027
  }
1029
1028
  }
1030
1029
 
1030
+ Transaction.prototype.isSampled = function isSampled() {
1031
+ this._calculatePriority()
1032
+ return this.sampled
1033
+ }
1034
+
1031
1035
  /**
1032
1036
  * Generates a priority for the transaction if it does not have one already.
1033
1037
  */
@@ -1074,4 +1078,11 @@ function addRequestParameters(requestParameters) {
1074
1078
  }
1075
1079
  }
1076
1080
 
1081
+ /**
1082
+ * Gets the current distributed trace traceId
1083
+ */
1084
+ Transaction.prototype.getTraceId = function getTraceId() {
1085
+ return this.traceId || this.id
1086
+ }
1087
+
1077
1088
  module.exports = Transaction