newrelic 2.1.0 → 2.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/.eslintrc CHANGED
@@ -4,6 +4,7 @@
4
4
  "browser": false
5
5
  },
6
6
  "globals": {
7
+ "Map": true,
7
8
  "Promise": true
8
9
  },
9
10
  "rules": {
@@ -0,0 +1,5 @@
1
+ ## CHANGE LOG
2
+
3
+ ## INTERNAL LINKS
4
+
5
+ ## NOTES
package/NEWS.md CHANGED
@@ -1,14 +1,50 @@
1
1
 
2
- ### v2.1.0 (2017-08-08)
2
+ ### v2.2.0 (2017-08-22):
3
+ * Added support for ignoring ranges of status codes.
4
+
5
+ The configuration `error_collector.ignore_status_codes` can now take ranges
6
+ of numbers. For example, `ignore_status_codes: ['400-404']` would ignore 400,
7
+ 401, 402, 403, and 404.
8
+
9
+ * Fixed a bug when a custom collector port was provided in the configuration
10
+ that prevented redirected connections from working.
11
+
12
+ * Fixed a bug in `Shim#record` that could cause an exception when trying to
13
+ create a new segment as part of an ended/inactive transaction.
14
+
15
+ * Fixed issue with custom Hapi handlers causing an error.
16
+
17
+ Previously custom Hapi handlers defined using the `server.handler()` method
18
+ were causing the Hapi server to return a 500 error. Now they are correctly
19
+ handled and recorded as middleware functions.
20
+
21
+ * Transaction state is now maintained in `ChildProcess` event listeners.
22
+
23
+ * Updated examples and documentation regarding custom transaction creation.
24
+
25
+ All examples and documentation now point at the `newrelic.start*Transaction`
26
+ methods.
27
+
28
+ * Reducing logging verbosity in the SQL query obfuscator.
29
+
30
+ * Experimental instrumentation for `async/await`
31
+
32
+ This is experimental instrumentation and has not yet been tested in a wide
33
+ array of production environments. The feature is currently off by default
34
+ behind a feature flag. To enable this experimental instrumentation, add
35
+ `await_support: true` to the `feature_flag` setting in your agent config
36
+ file.
37
+
38
+ ### v2.1.0 (2017-08-08):
3
39
  * Improved metadata collection for AWS, Azure, GCE, and Pivotal Cloud Foundry.
4
40
 
5
41
  * Fixed a bug in PG query obfuscation for `$` placeholders.
6
42
 
7
- The agent used to mis-detect `$1` value placeholders as unmatched dollar-quoted
8
- strings causing the whole query to be obfuscated to just `?`. These
9
- placeholders are now correctly detected and obfuscated.
43
+ The agent used to mis-detect `$1` value placeholders as unmatched
44
+ dollar-quoted strings causing the whole query to be obfuscated to just `?`.
45
+ These placeholders are now correctly detected and obfuscated.
10
46
 
11
- ### v2.0.2 (2017-08-01)
47
+ ### v2.0.2 (2017-08-01):
12
48
  * Improved documentation for `newrelic.start*Transaction` and `TransactionHandle.`
13
49
 
14
50
  Formatting for the `startWebTransaction` and `startBackgroundTransaction`
@@ -22,7 +58,7 @@
22
58
 
23
59
  * Fixed unhandled rejection error caused by `ioredis` instrumentation.
24
60
 
25
- ### v2.0.1 (2017-07-25)
61
+ ### v2.0.1 (2017-07-25):
26
62
  * Fixed issue with transaction events not including correct duration values.
27
63
 
28
64
  This issue was introduced in v2.0.0, and it has affected web transactions histogram
package/README.md CHANGED
@@ -656,7 +656,7 @@ application, the agent can't tell when they should begin and end.
656
656
  Read more at:
657
657
  https://docs.newrelic.com/docs/agents/nodejs-agent/supported-features/nodejs-custom-instrumentation
658
658
 
659
- #### newrelic.createWebTransaction(url, handle)
659
+ #### newrelic.startWebTransaction(url, handle)
660
660
 
661
661
  `url` is the name of the web transaction. It should be pretty static, not
662
662
  including anything like user ids or any other data that is very specific to the
@@ -669,16 +669,22 @@ called within an active background transaction, it will create a new,
669
669
  independent transaction and any calls within the `handle` will be bound to the
670
670
  new web transaction.
671
671
 
672
- Custom transactions **must** be ended manually by calling `endTransaction()`.
673
- Timing for custom transaction starts from when the returned wrapped function is
674
- called until `endTransaction()` is called.
672
+ Custom transactions can be ended within the `handle` in one of three ways:
675
673
 
676
- #### newrelic.createBackgroundTransaction(name, [group], handle)
674
+ 1. Call `transaction.end()`. The `transaction` can be received by calling
675
+ `newrelic.getTransaction()` first thing in the handler function. Then,
676
+ when you call `transaction.end()` timing will stop.
677
+ 2. Return a promise. The transaction will end when the promise resolves or
678
+ rejects.
679
+ 3. Do neither. If no promise is returned, and `getTransaction()` isn't
680
+ called, the transaction will end immediately after the handle returns.
681
+
682
+ #### newrelic.startBackgroundTransaction(name [, group], handle)
677
683
 
678
684
  `name` is the name of the job. It should be pretty static, and not include job
679
685
  ids or anything very specific to that run of the job. `group` is optional, and
680
686
  allows you to group types of jobs together. This should follow similar rules as
681
- the `name`. `handle` is a function that encompases your background job. Both
687
+ the `name`. `handle` is a function that encompasses your background job. Both
682
688
  custom and auto instrumentation will be captured as part of the transaction.
683
689
 
684
690
  If called within an active background transaction, it will act as a nested
@@ -686,14 +692,20 @@ tracer. If called within an active web transaction, it will create a new
686
692
  transaction and any calls within the `handle` will be bound to the new,
687
693
  independent background transaction.
688
694
 
689
- Custom transactions **must** be ended manually by calling `endTransaction()`.
690
- Timing for custom transaction starts from when the returned wrapped function is
691
- called until `endTransaction()` is called.
695
+ 1. Call `transaction.end()`. The `transaction` can be received by calling
696
+ `newrelic.getTransaction()` first thing in the handler function. Then,
697
+ when you call `transaction.end()` timing will stop.
698
+ 2. Return a promise. The transaction will end when the promise resolves or
699
+ rejects.
700
+ 3. Do neither. If no promise is returned, and `getTransaction()` isn't
701
+ called, the transaction will end immediately after the handle returns.
692
702
 
693
- #### newrelic.endTransaction()
703
+ #### newrelic.getTransaction()
694
704
 
695
- This takes no arguments and must be called to end any custom transaction. It
696
- will detect what kind of transaction was active and end it.
705
+ This takes no arguments and returns the currently active transaction. If the
706
+ returned transaction is a custom one, started with either `startWebTransaction`
707
+ or `startBackgroundTransaction`, then `transaction.end()` must be called to
708
+ end the transaction.
697
709
 
698
710
  #### newrelic.createTracer(name, handle)
699
711
 
package/api.js CHANGED
@@ -91,6 +91,7 @@ API.prototype.setTransactionName = function setTransactionName(name) {
91
91
  return
92
92
  }
93
93
 
94
+ logger.trace('Setting transaction %s name to %s', transaction.id, name)
94
95
  transaction.forceName = NAMES.CUSTOM + '/' + name
95
96
  }
96
97
 
@@ -623,8 +624,7 @@ API.prototype.createTracer = function createTracer(name, callback) {
623
624
  }
624
625
 
625
626
  API.prototype.createWebTransaction = util.deprecate(
626
- createWebTransaction,
627
- [
627
+ createWebTransaction, [
628
628
  'API#createWebTransaction is being deprecated!',
629
629
  'Please use API#startWebTransaction for transaction creation',
630
630
  'and API#getTransaction for transaction management including',
@@ -884,8 +884,7 @@ function startBackgroundTransaction(name, group, handle) {
884
884
  }
885
885
 
886
886
  API.prototype.createBackgroundTransaction = util.deprecate(
887
- createBackgroundTransaction,
888
- [
887
+ createBackgroundTransaction, [
889
888
  'API#createBackgroundTransaction is being deprecated!',
890
889
  'Please use API#startBackgroundTransaction for transaction creation',
891
890
  'and API#getTransaction for transaction management including',
@@ -1074,7 +1073,7 @@ API.prototype.recordMetric = function recordMetric(name, value) {
1074
1073
 
1075
1074
  for (var i = 0, l = required.length; i < l; ++i) {
1076
1075
  if (typeof value[required[i]] !== 'number') {
1077
- logger.warn('Metric object must include ' + required[i] + ' as a number')
1076
+ logger.warn('Metric object must include %s as a number', required[i])
1078
1077
  return
1079
1078
  }
1080
1079
 
package/index.js CHANGED
@@ -17,6 +17,9 @@ if (require.cache.__NR_cache) {
17
17
  'Attempting to load a second copy of newrelic from %s, using cache instead',
18
18
  __dirname
19
19
  )
20
+ if (require.cache.__NR_cache.agent) {
21
+ require.cache.__NR_cache.agent.recordSupportability('Agent/DoubleLoad')
22
+ }
20
23
  module.exports = require.cache.__NR_cache
21
24
  } else {
22
25
  initialize()
@@ -88,9 +91,10 @@ function initialize() {
88
91
  agent.start(function cb_start(error) {
89
92
  if (!error) {
90
93
  // TODO: After deprecating Node 0.10 and 0.12, simplify this regex.
94
+ // TODO: As new versions come out, make sure to update Angler metrics.
91
95
  var nodeMajor = /^v?((?:0\.)?\d+)/.exec(process.version)
92
- agent.metrics.getOrCreateMetric(
93
- 'Supportability/Nodejs/Version/' + ((nodeMajor && nodeMajor[1]) || 'unknown')
96
+ agent.recordSupportability(
97
+ 'Version/' + ((nodeMajor && nodeMajor[1]) || 'unknown')
94
98
  )
95
99
 
96
100
  return logger.debug("New Relic for Node.js is connected to New Relic.")
package/lib/agent.js CHANGED
@@ -107,6 +107,7 @@ function Agent(config) {
107
107
  this.segmentsCreatedInHarvest = 0
108
108
  this.segmentsClearedInHarvest = 0
109
109
  this.activeTransactions = 0
110
+ this.transactionCreatedInHarvest = 0
110
111
 
111
112
  // Hidden class optimizations.
112
113
  this.harvesterHandle = null
@@ -286,9 +287,11 @@ Agent.prototype.harvest = function harvest(callback) {
286
287
  harvestCleared: this.segmentsClearedInHarvest,
287
288
  activeTransactions: this.activeTransactions
288
289
  }, 'Entity stats on harvest')
290
+ this.recordSupportability('Transactions/Created', this.transactionCreatedInHarvest)
289
291
 
290
292
  this.segmentsCreatedInHarvest = 0
291
293
  this.segmentsClearedInHarvest = 0
294
+ this.transactionCreatedInHarvest = 0
292
295
 
293
296
  if (!this.collector.isConnected()) {
294
297
  return process.nextTick(function cb_nextTick() {
@@ -994,7 +997,7 @@ Agent.prototype._transactionFinished = function _transactionFinished(transaction
994
997
  logger.debug('Ignoring %s (%s).', transaction.name, transaction.id)
995
998
  }
996
999
 
997
- this.activeTransactions--
1000
+ --this.activeTransactions
998
1001
  this.totalActiveSegments -= transaction.numSegments
999
1002
  this.segmentsClearedInHarvest += transaction.numSegments
1000
1003
  }
@@ -1008,4 +1011,13 @@ Agent.prototype.getTransaction = function getTransaction() {
1008
1011
  return this.tracer.getTransaction()
1009
1012
  }
1010
1013
 
1014
+ Agent.prototype.recordSupportability = function recordSupportability(name, value) {
1015
+ var metric = this.metrics.getOrCreateMetric(NAMES.SUPPORTABILITY.NODEJS + '/' + name)
1016
+ if (value != null) {
1017
+ metric.recordValue(value)
1018
+ } else {
1019
+ metric.incrementCallCount()
1020
+ }
1021
+ }
1022
+
1011
1023
  module.exports = Agent
@@ -42,11 +42,11 @@ var HTTP_LOL_COLLECTOR = 503
42
42
  function dumpErrors(errors, name) {
43
43
  var index = 1
44
44
 
45
- errors.forEach(function cb_forEach(error) {
45
+ errors.forEach(function forEachError(error) {
46
46
  logger.trace(error, "Error %s during %s:", index++, name)
47
47
 
48
48
  if (error.laterErrors) {
49
- error.laterErrors.forEach(function cb_forEach(laterError) {
49
+ error.laterErrors.forEach(function forEachLaterError(laterError) {
50
50
  logger.trace(laterError, "Error %s during %s:", index++, name)
51
51
  })
52
52
  }
@@ -115,7 +115,7 @@ CollectorAPI.prototype.connect = function connect(callback) {
115
115
  var id = setTimeout(function again() {
116
116
  api._login(retry)
117
117
  }, backoff.interval * TO_MILLIS)
118
-
118
+
119
119
  if (id.unref) {
120
120
  id.unref()
121
121
  }
@@ -129,7 +129,7 @@ CollectorAPI.prototype._login = function _login(callback) {
129
129
  var agent = this._agent
130
130
 
131
131
 
132
- methods.redirect.invoke(null, function cb_invoke(error, collector, body) {
132
+ methods.redirect.invoke(null, function redirectCb(error, collector, body) {
133
133
  if (error) return callback(error, collector, body)
134
134
  if (!collector) {
135
135
  logger.error(
@@ -152,18 +152,20 @@ CollectorAPI.prototype._login = function _login(callback) {
152
152
  )
153
153
 
154
154
  agent.config.host = parts[0]
155
- if (parts.length > 1) {
156
- agent.config.port = parts[1]
157
- }
155
+ agent.config.port = parts[1] || (agent.config.ssl ? 443 : 80)
158
156
  }
159
157
  }
160
158
 
159
+ _getFacts()
160
+ })
161
+
162
+ function _getFacts() {
161
163
  facts(agent, function getEnvDict(environmentDict) {
162
164
  // The collector really likes arrays.
163
165
  // In fact, it kind of insists on them.
164
166
  var environment = [environmentDict]
165
167
 
166
- methods.handshake.invoke(environment, function cb_invoke(error, config, body) {
168
+ methods.handshake.invoke(environment, function handshakeCb(error, config, body) {
167
169
  if (error) return callback(error, config, body)
168
170
  if (!config || !config.agent_run_id) {
169
171
  return callback(new Error("No agent run ID received from handshake."), config)
@@ -183,7 +185,7 @@ CollectorAPI.prototype._login = function _login(callback) {
183
185
  callback(null, config, body)
184
186
  })
185
187
  })
186
- })
188
+ }
187
189
  }
188
190
 
189
191
  /**
package/lib/config.js CHANGED
@@ -12,6 +12,7 @@ var exists = fs.existsSync || path.existsSync
12
12
  var safeJSON = require('./util/safe-json')
13
13
  var stringifySync = safeJSON.stringifySync
14
14
  var parse = safeJSON.parse
15
+ var semver = require('semver')
15
16
  var os = require('os')
16
17
  var logger
17
18
 
@@ -727,6 +728,19 @@ Config.prototype.logUnknown = function logUnknown(json, key) {
727
728
  )
728
729
  }
729
730
 
731
+ /**
732
+ * Return the availability of async_hook for use by the agent.
733
+ */
734
+ Config.prototype.checkAsyncHookStatus = function checkAsyncHookStatus() {
735
+ return (
736
+ (
737
+ semver.satisfies(process.version, '>=8') ||
738
+ semver.prerelease(process.version)
739
+ ) &&
740
+ this.feature_flag.await_support
741
+ )
742
+ }
743
+
730
744
  /**
731
745
  * Gets the user set host display name. If not provided, it returns the default value.
732
746
  *
@@ -948,13 +962,59 @@ Config.prototype._fromEnvironment = function _fromEnvironment(metadata, data) {
948
962
  * based on special properties of configuration values should go here as well.
949
963
  */
950
964
  Config.prototype._canonicalize = function _canonicalize() {
951
- var codes = this.error_collector && this.error_collector.ignore_status_codes
952
- if (codes) {
953
- this.error_collector.ignore_status_codes = codes.map(function cb_map(code) {
954
- return parseInt(code, 10)
955
- })
965
+ function parseCodes(codes) {
966
+ // range does not support negative values
967
+ function parseRange(range, parsed) {
968
+ var split = range.split('-')
969
+ if (split.length !== 2) {
970
+ logger.warn('Failed to parse range %s', range)
971
+ return parsed
972
+ }
973
+ if (split[0] === '') { // catch negative code. ex. -7
974
+ return parsed.push(parseInt(range, 10))
975
+ }
976
+ var lower = parseInt(split[0], 10)
977
+ var upper = parseInt(split[1], 10)
978
+ if (Number.isNaN(lower) || Number.isNaN(upper)) {
979
+ logger.warn('Range must contain two numbers %s', range)
980
+ return parsed
981
+ }
982
+ if (lower > upper) {
983
+ logger.warn('Range must start with lower bound %s', range)
984
+ } else if (lower < 0 || upper > 1000) {
985
+ logger.warn('Range must be between 0 and 1000 %s', range)
986
+ } else { // success
987
+ for (var i = lower; i <= upper; i++) {
988
+ parsed.push(i)
989
+ }
990
+ }
991
+ return parsed
992
+ }
993
+
994
+ var parsedCodes = []
995
+ for (var i = 0; i < codes.length; i++) {
996
+ var code = codes[i]
997
+ var parsedCode
998
+ if (typeof code === 'string' && code.indexOf('-') !== -1) {
999
+ parseRange(code, parsedCodes)
1000
+ } else {
1001
+ parsedCode = parseInt(code, 10)
1002
+ if (!Number.isNaN(parsedCode)) {
1003
+ parsedCodes.push(parsedCode)
1004
+ } else {
1005
+ logger.warn('Failed to parse status code %s', code)
1006
+ }
1007
+ }
1008
+ }
1009
+ return parsedCodes
956
1010
  }
957
1011
 
1012
+ var statusCodes = this.error_collector && this.error_collector.ignore_status_codes
1013
+ if (statusCodes) {
1014
+ this.error_collector.ignore_status_codes = parseCodes(statusCodes)
1015
+ }
1016
+
1017
+
958
1018
  var logAliases = {
959
1019
  'verbose': 'trace',
960
1020
  'debugging': 'debug',
@@ -1020,10 +1080,10 @@ Config.prototype.measureInternal = function measureInternal(suffix, duration) {
1020
1080
  Config.prototype.validateFlags = function validateFlags() {
1021
1081
  Object.keys(this.feature_flag).forEach(function cb_forEach(key) {
1022
1082
  if (feature_flag.released.indexOf(key) > -1) {
1023
- logger.warn('Feature flag ' + key + ' has been released')
1083
+ logger.warn('Feature flag %s has been released', key)
1024
1084
  }
1025
1085
  if (feature_flag.unreleased.indexOf(key) > -1) {
1026
- logger.warn('Feature flag ' + key + ' has been deprecated')
1086
+ logger.warn('Feature flag %s has been deprecated', key)
1027
1087
  }
1028
1088
  })
1029
1089
  }
package/lib/db/tracer.js CHANGED
@@ -56,15 +56,15 @@ QueryTracer.prototype.addQuery = function addQuery(segment, type, query, trace)
56
56
 
57
57
  switch (this.config.transaction_tracer.record_sql) {
58
58
  case 'raw':
59
- logger.info('recording raw sql')
59
+ logger.trace('recording raw sql')
60
60
  segment.parameters.sql = slowQuery.query
61
61
  break
62
62
  case 'obfuscated':
63
- logger.info('recording obfuscated sql')
63
+ logger.trace('recording obfuscated sql')
64
64
  segment.parameters.sql_obfuscated = slowQuery.obfuscated
65
65
  break
66
66
  default:
67
- logger.info(
67
+ logger.trace(
68
68
  'not collecting slow-query because transaction_tracer.record_sql was set to %s',
69
69
  this.config.transaction_tracer.record_sql
70
70
  )
@@ -6,6 +6,7 @@ exports.prerelease = {
6
6
  custom_metrics: true,
7
7
  express5: false,
8
8
  synthetics: true,
9
+ await_support: false,
9
10
  native_metrics: true,
10
11
  promise_segments: false,
11
12
  reverse_naming_rules: false,
@@ -0,0 +1,92 @@
1
+ 'use strict'
2
+
3
+ var logger = require('../../logger').child({component: 'async_hooks'})
4
+ var promInit = require('../promise')
5
+ var semver = require('semver')
6
+
7
+ module.exports = initialize
8
+
9
+ /**
10
+ * The spec for the native `Promise` class.
11
+ */
12
+ var STATIC_PROMISE_METHODS = ['accept', 'all', 'defer', 'race', 'reject', 'resolve']
13
+ var NATIVE_PROMISE_SPEC = {
14
+ name: 'global',
15
+ constructor: 'Promise',
16
+ executor: true,
17
+ $proto: {
18
+ then: ['then', 'chain'],
19
+ catch: ['catch']
20
+ },
21
+ $static: {
22
+ $copy: STATIC_PROMISE_METHODS,
23
+ cast: STATIC_PROMISE_METHODS
24
+ }
25
+ }
26
+
27
+ function initialize(agent) {
28
+ var enableHooks = agent.config.checkAsyncHookStatus()
29
+ if (enableHooks && tryAsyncHooks(agent)) {
30
+ logger.debug('Using async_hooks.')
31
+ } else if (semver.satisfies(process.version, '>=0.12')) {
32
+ logger.debug('Using promise instrumentation.')
33
+ promInit(agent, global, NATIVE_PROMISE_SPEC)
34
+ }
35
+ }
36
+
37
+ function tryAsyncHooks(agent) {
38
+ var asyncHooks = null
39
+ try {
40
+ asyncHooks = require('async_hooks')
41
+ } catch (e) {
42
+ logger.info(e, 'Not using async_hooks module.')
43
+ return false
44
+ }
45
+
46
+ // this map is reused to track the segment that was active when
47
+ // the before callback is called to be replaced in the after callback
48
+ var segmentMap = new Map()
49
+ module.exports._segmentMap = segmentMap
50
+
51
+ asyncHooks.createHook({
52
+ init: function initHook(id, type) {
53
+ var transaction = agent.getTransaction()
54
+
55
+ if (!transaction || type !== 'PROMISE') {
56
+ return
57
+ }
58
+
59
+ segmentMap.set(id, agent.tracer.getSegment())
60
+ },
61
+
62
+ before: function beforeHook(id) {
63
+ var hookSegment = segmentMap.get(id)
64
+
65
+ if (!hookSegment) {
66
+ return
67
+ }
68
+
69
+ segmentMap.set(id, agent.tracer.getSegment())
70
+ agent.tracer.segment = hookSegment
71
+ },
72
+ after: function afterHook(id) {
73
+ var hookSegment = segmentMap.get(id)
74
+
75
+ // hookSegment is the segment that was active before the promise
76
+ // executed. If the promise is executing before a segment has been
77
+ // restored, hookSegment will be null and should be restored. Thus
78
+ // undefined is the only invalid value here.
79
+ if (hookSegment === undefined) {
80
+ return
81
+ }
82
+
83
+ segmentMap.set(id, agent.tracer.getSegment())
84
+ agent.tracer.segment = hookSegment
85
+ },
86
+ destroy: function destHook(id) {
87
+ segmentMap.delete(id)
88
+ }
89
+ }).enable()
90
+
91
+ return true
92
+ }
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  var wrap = require('../../shimmer').wrapMethod
4
+ var isWrapped = require('../../shimmer').isWrapped
4
5
 
5
6
  module.exports = initialize
6
7
 
@@ -12,4 +13,38 @@ function initialize(agent, childProcess) {
12
13
  function wrapMethod(fn, method) {
13
14
  return agent.tracer.wrapFunctionLast('child_process.' + method, null, fn)
14
15
  }
16
+
17
+ var childProcessProto = childProcess && childProcess.ChildProcess
18
+ // ChildProcess is exposed on Node 4 and higher
19
+ if (childProcessProto) {
20
+ wrapChildProcessCls(childProcess.ChildProcess)
21
+ } else {
22
+ wrap(childProcess, 'childProcess', ['fork', 'spawn'], wrapSpawn)
23
+ }
24
+
25
+ function wrapSpawn(fn) {
26
+ return function wrapped() {
27
+ var child = fn.apply(this, arguments)
28
+ if (child && child.constructor && child.constructor.prototype) {
29
+ wrapChildProcessCls(child.constructor)
30
+ }
31
+ return child
32
+ }
33
+ }
34
+
35
+ function wrapChildProcessCls(childProcessCls) {
36
+ if (!childProcessCls || !childProcessCls.prototype ||
37
+ isWrapped(childProcessCls.prototype.on)) {
38
+ return
39
+ }
40
+
41
+ wrap(
42
+ childProcessCls.prototype,
43
+ 'childProcess.ChildProcess.prototype',
44
+ 'on',
45
+ function wrapEmit(fn) {
46
+ return agent.tracer.wrapFunctionNoSegment(fn, ['on', 'addListener'])
47
+ }
48
+ )
49
+ }
15
50
  }
@@ -1,29 +1,11 @@
1
1
  'use strict'
2
2
 
3
+ var asyncHookInstrumentation = require('./async_hooks')
3
4
  var events = require('events')
4
5
  var wrap = require('../../shimmer').wrapMethod
5
- var promInit = require('../promise')
6
- var semver = require('semver')
7
6
 
8
7
  module.exports = initialize
9
8
 
10
- /**
11
- * The spec for the native `Promise` class.
12
- */
13
- var STATIC_PROMISE_METHODS = ['accept', 'all', 'defer', 'race', 'reject', 'resolve']
14
- var NATIVE_PROMISE_SPEC = {
15
- name: 'global',
16
- constructor: 'Promise',
17
- executor: true,
18
- $proto: {
19
- then: ['then', 'chain'],
20
- catch: ['catch']
21
- },
22
- $static: {
23
- $copy: STATIC_PROMISE_METHODS,
24
- cast: STATIC_PROMISE_METHODS
25
- }
26
- }
27
9
 
28
10
  function initialize(agent) {
29
11
  // `_fatalException` is an undocumented feature of domains, introduced in
@@ -63,10 +45,9 @@ function initialize(agent) {
63
45
  }
64
46
  )
65
47
 
66
- // Promises don't exist prior to version 12
67
- if (semver.satisfies(process.version, '>=0.12')) {
68
- promInit(agent, global, NATIVE_PROMISE_SPEC)
69
- }
48
+ // This will initialize the most optimal native-promise instrumentation
49
+ // that we have available.
50
+ asyncHookInstrumentation(agent)
70
51
  }
71
52
 
72
53
  function listenerCount(emitter, evnt) {
@@ -131,7 +131,7 @@ function wrapEmitWithTransaction(agent, emit) {
131
131
  var start = parseFloat(qtime)
132
132
 
133
133
  if (isNaN(start)) {
134
- logger.warn('Queue time header parsed as NaN (' + qtime + ')')
134
+ logger.warn('Queue time header parsed as NaN (%s)', qtime)
135
135
  } else {
136
136
  // nano seconds
137
137
  if (start > 1e18) start = start / 1e6
@@ -23,6 +23,33 @@ module.exports = function initialize(agent, hapi, moduleName, shim) {
23
23
  }
24
24
 
25
25
  function wrapServer(shim, Server) {
26
+ // wrap server.handler function that registers a new handler type
27
+ // the second argument is expected to be a function that generates the handler function
28
+ shim.wrap(Server.prototype, 'handler', function wrapHandler(shim, original) {
29
+ return function wrappedHandler() {
30
+ var args = shim.argsToArray.apply(shim, arguments)
31
+
32
+ var handlerGenerator = args[1]
33
+ if (typeof handlerGenerator === 'function') {
34
+ args[1] = wrapGenerator(handlerGenerator)
35
+ }
36
+
37
+ return original.apply(this, args)
38
+
39
+ function wrapGenerator(generator) {
40
+ return function wrappedGenerator() {
41
+ var generatorArgs = shim.argsToArray.apply(shim, arguments)
42
+ var handler = generator.apply(this, generatorArgs)
43
+ if (typeof handler === 'function') {
44
+ var route = generatorArgs[0]
45
+ return wrapRouteHandler(shim, handler, route && route.path)
46
+ }
47
+ return handler
48
+ }
49
+ }
50
+ }
51
+ })
52
+
26
53
  shim.wrap(Server.prototype, 'route', function wrapRoute(shim, original) {
27
54
  return function wrappedRoute() {
28
55
  var args = shim.argsToArray.apply(shim, arguments)
@@ -35,44 +62,19 @@ function wrapServer(shim, Server) {
35
62
  var route = args[0]
36
63
  // handler function could be on the route object, or on a nested config object
37
64
  if (route.config) {
38
- wrapRoute(shim, route.config, route.path)
65
+ _wrapRouteHandler(shim, route.config, route.path)
39
66
  } else {
40
- wrapRoute(shim, route)
67
+ _wrapRouteHandler(shim, route)
41
68
  }
42
69
 
43
70
  return original.apply(this, args)
44
71
 
45
- function wrapRoute(shim, route, path) {
46
- shim.wrap(route, 'handler', function wrapHandler(shim, handler) {
47
- var wrappedHandler = shim.wrap(handler, function wrapHandler(shim, original) {
48
- return function wrappedHandler() {
49
- var reply = arguments[1]
50
- if (reply) {
51
- shim.recordRender(reply, 'view')
52
- }
53
- return original.apply(this, arguments)
54
- }
55
- })
56
-
57
- return shim.recordMiddleware(wrappedHandler, {
58
- route: path || route.path,
59
- req: function getReq(shim, fn, fnName, args) {
60
- var request = args[0]
61
- if (request && request.raw) {
62
- return request.raw.req
63
- }
64
- },
65
- next: function wrapNext(shim, fn, fnName, args, wrap) {
66
- var reply = args[1]
67
- if (!shim.isFunction(reply)) return
68
- wrap(reply, 'response', true)
69
- },
70
- params: function getParams(shim, fn, fnName, args, req) {
71
- var req = args[0]
72
- if (!req) return
73
- return req.params
74
- }
75
- })
72
+ function _wrapRouteHandler(shim, container, path) {
73
+ if (typeof container.handler !== 'function') {
74
+ return
75
+ }
76
+ shim.wrap(container, 'handler', function wrapHandler(shim, handler) {
77
+ return wrapRouteHandler(shim, handler, path || route.path)
76
78
  })
77
79
  }
78
80
  }
@@ -109,3 +111,35 @@ function wrapCreateServer(shim, hapi) {
109
111
  }
110
112
  })
111
113
  }
114
+
115
+ function wrapRouteHandler(shim, handler, path) {
116
+ var wrappedHandler = shim.wrap(handler, function wrapHandler(shim, original) {
117
+ return function wrapped() {
118
+ var reply = arguments[1]
119
+ if (reply) {
120
+ shim.recordRender(reply, 'view')
121
+ }
122
+ return original.apply(this, arguments)
123
+ }
124
+ })
125
+
126
+ return shim.recordMiddleware(wrappedHandler, {
127
+ route: path,
128
+ req: function getReq(shim, fn, fnName, args) {
129
+ var request = args[0]
130
+ if (request && request.raw) {
131
+ return request.raw.req
132
+ }
133
+ },
134
+ next: function wrapNext(shim, fn, fnName, args, wrap) {
135
+ var reply = args[1]
136
+ if (!shim.isFunction(reply)) return
137
+ wrap(reply, 'response', true)
138
+ },
139
+ params: function getParams(shim, fn, fnName, args, req) {
140
+ var req = args[0]
141
+ if (!req) return
142
+ return req.params
143
+ }
144
+ })
145
+ }
@@ -150,7 +150,8 @@ var SUPPORTABILITY = {
150
150
  EVENTS: 'Supportability/Events',
151
151
  API: 'Supportability/API',
152
152
  UTILIZATION: 'Supportability/utilization',
153
- DEPENDENCIES: 'Supportability/InstalledDependencies'
153
+ DEPENDENCIES: 'Supportability/InstalledDependencies',
154
+ NODEJS: 'Supportability/Nodejs'
154
155
  }
155
156
 
156
157
  var UTILIZATION = {
package/lib/shim/shim.js CHANGED
@@ -127,6 +127,7 @@ Shim.prototype.record = record
127
127
  Shim.prototype.isWrapped = isWrapped
128
128
  Shim.prototype.unwrap = unwrap
129
129
  Shim.prototype.getSegment = getSegment
130
+ Shim.prototype.getActiveSegment = getActiveSegment
130
131
  Shim.prototype.storeSegment = storeSegment
131
132
  Shim.prototype.bindCallbackSegment = bindCallbackSegment
132
133
  Shim.prototype.applySegment = applySegment
@@ -875,8 +876,8 @@ function record(nodule, properties, recordNamer) {
875
876
  shim.logger.trace('Wrapping "%s" with metric recording.', name)
876
877
 
877
878
  return function wrapper() {
878
- // See if we're in a transaction.
879
- var parent = shim.getSegment()
879
+ // See if we're in an active transaction.
880
+ var parent = shim.getActiveSegment()
880
881
  if (!parent) {
881
882
  shim.logger.debug('Not recording function %s, not in a transaction.', name)
882
883
  return fn.apply(this, arguments)
@@ -911,6 +912,9 @@ function record(nodule, properties, recordNamer) {
911
912
  })
912
913
 
913
914
  // Apply the function, and (if it returned a stream) bind that too.
915
+ // If you are looking here Geoff, the reason there is no check for
916
+ // `segment` is because it should be guaranteed by the parent and active
917
+ // transaction check at the beginning of this function. :)
914
918
  var ret = _applyRecorderSegment(segment, this, args, segDesc)
915
919
  if (ret) {
916
920
  if (segDesc.stream) {
@@ -1141,8 +1145,8 @@ function bindCallbackSegment(args, cbIdx, parentSegment) {
1141
1145
  }
1142
1146
 
1143
1147
  /**
1144
- * Retrieves the segment associated with the given object, or the currently
1145
- * active segment if no object is given.
1148
+ * Retrieves the segment associated with the given object, or the current
1149
+ * segment if no object is given.
1146
1150
  *
1147
1151
  * - `getSegment([obj])`
1148
1152
  *
@@ -1151,8 +1155,8 @@ function bindCallbackSegment(args, cbIdx, parentSegment) {
1151
1155
  * @param {*} [obj] - The object to retrieve a segment from.
1152
1156
  *
1153
1157
  * @return {?TraceSegment} The trace segment associated with the given object or
1154
- * the currently active segment if no object is provided or no segment is
1155
- * associated with the object.
1158
+ * the current segment if no object is provided or no segment is associated
1159
+ * with the object.
1156
1160
  */
1157
1161
  function getSegment(obj) {
1158
1162
  if (obj && obj.__NR_segment) {
@@ -1161,6 +1165,32 @@ function getSegment(obj) {
1161
1165
  return this.tracer.getSegment()
1162
1166
  }
1163
1167
 
1168
+
1169
+ /**
1170
+ * Retrieves the segment associated with the given object, or the currently
1171
+ * active segment if no object is given.
1172
+ *
1173
+ * - `getActiveSegment([obj])`
1174
+ *
1175
+ * An active segment is one whose transaction is still active (e.g. has not
1176
+ * ended yet).
1177
+ *
1178
+ * @memberof Shim.prototype
1179
+ *
1180
+ * @param {*} [obj] - The object to retrieve a segment from.
1181
+ *
1182
+ * @return {?TraceSegment} The trace segment associated with the given object or
1183
+ * the currently active segment if no object is provided or no segment is
1184
+ * associated with the object.
1185
+ */
1186
+ function getActiveSegment(obj) {
1187
+ var segment = this.getSegment(obj)
1188
+ if (segment && segment.transaction && segment.transaction.isActive()) {
1189
+ return segment
1190
+ }
1191
+ return null
1192
+ }
1193
+
1164
1194
  /**
1165
1195
  * Associates a segment with the given object.
1166
1196
  *
@@ -652,8 +652,10 @@ function setErrorPredicate(pred) {
652
652
  */
653
653
  function captureUrlParams(params) {
654
654
  var segment = this.getSegment()
655
- var config = this.agent.config
656
- urltils.copyParameters(config, params, segment.parameters)
655
+ if (segment) {
656
+ var config = this.agent.config
657
+ urltils.copyParameters(config, params, segment.parameters)
658
+ }
657
659
  }
658
660
 
659
661
  // -------------------------------------------------------------------------- //
@@ -41,7 +41,7 @@ Stats.prototype.recordValue = function recordValue(totalTime, exclusiveTime) {
41
41
  this.max = Math.max(totalTime, this.max)
42
42
 
43
43
  this.sumOfSquares += (totalTime * totalTime)
44
- this.callCount += 1
44
+ ++this.callCount
45
45
  this.total += totalTime
46
46
  this.totalExclusive += exclusiveTime
47
47
  }
@@ -142,7 +142,7 @@ module.exports._getProcessorStats = function getProcessorStats(callback) {
142
142
  callback(parseCpuInfo(data))
143
143
  })
144
144
  } else {
145
- logger.debug('Unknown platform: ' + platform + ', could not retrieve processor info')
145
+ logger.debug('Unknown platform: %s; could not retrieve processor info', platform)
146
146
  callback(processorStats)
147
147
  }
148
148
  }
@@ -162,7 +162,7 @@ module.exports._getMemoryStats = function getMemoryStats(callback) {
162
162
  callback(parseMemInfo(data))
163
163
  })
164
164
  } else {
165
- logger.debug('Unknown platform: ' + platform + ', could not retrieve memory info')
165
+ logger.debug('Unknown platform: %s; could not retrieve memory info', platform)
166
166
  callback(null)
167
167
  }
168
168
  }
@@ -181,7 +181,7 @@ function getKernelVersion(callback) {
181
181
  callback(data)
182
182
  })
183
183
  } else {
184
- logger.debug('Unknown platform' + platform + ', could not read kernel version')
184
+ logger.debug('Unknown platform: %s; could not read kernel version', platform)
185
185
  callback(null)
186
186
  }
187
187
  }
@@ -196,7 +196,7 @@ function getSysctlValue(names, callback) {
196
196
  function respond(err, stdout, stderr) {
197
197
  if (returned) return
198
198
  if (err) {
199
- logger.debug('Error when trying to run: sysctl -n ' + name + ': %s', err.message)
199
+ logger.debug('Error when trying to run: sysctl -n %s: %s', name, err.message)
200
200
  callback(null)
201
201
  returned = true
202
202
  } else if (!stderr) {
@@ -204,7 +204,7 @@ function getSysctlValue(names, callback) {
204
204
  returned = true
205
205
  }
206
206
  if (++ran === names.length && !returned) {
207
- logger.debug('No sysctl info found for names: ' + names.toString())
207
+ logger.debug('No sysctl info found for names: %j', names)
208
208
  callback(null)
209
209
  }
210
210
  }
@@ -53,7 +53,8 @@ function Transaction(agent) {
53
53
  agent.metricNameNormalizer
54
54
  )
55
55
 
56
- agent.activeTransactions++
56
+ ++agent.activeTransactions
57
+ ++agent.transactionCreatedInHarvest
57
58
 
58
59
  this.numSegments = 0
59
60
 
@@ -144,8 +145,9 @@ Transaction.prototype.end = function end(done) {
144
145
  )
145
146
  }
146
147
 
147
- var transaction = this
148
+ this.agent.recordSupportability('Transactions/Segments', this.numSegments)
148
149
 
150
+ var transaction = this
149
151
  transaction.trace.end()
150
152
  process.nextTick(function nextTickedEnd() {
151
153
  // recorders must be run before the trace is collected
@@ -404,6 +406,12 @@ function finalizeNameFromUri(requestURL, statusCode) {
404
406
  * the current partial name is used.
405
407
  */
406
408
  Transaction.prototype.finalizeName = function finalizeName(name) {
409
+ // If no name is given, and this is a web transaction with a url, then
410
+ // finalize the name using the stored url.
411
+ if (name == null && this.type === 'web' && this.url) {
412
+ return this.finalizeNameFromUri(this.url, this.statusCode)
413
+ }
414
+
407
415
  this._partialName = name || this._partialName
408
416
 
409
417
  var fullName = TYPE_METRICS[this.type] + '/' + this._partialName
@@ -100,6 +100,7 @@ function transactionProxy(handler) {
100
100
  transaction: {id: segment.transaction.id, name: segment.transaction.getName()},
101
101
  segment: segment.name
102
102
  }, 'Active transaction when creating non-nested transaction')
103
+ tracer.agent.recordSupportability('Transactions/Nested')
103
104
  return handler.apply(this, arguments)
104
105
  }
105
106
  var transaction = new Transaction(tracer.agent)
@@ -129,12 +129,12 @@ Logger.prototype.child = function child(extra) {
129
129
  var parent = this
130
130
  childLogger.options = parent.options
131
131
 
132
- childLogger.write = function write(level, args, extra) {
133
- extra = getPropertiesToLog(extra)
132
+ childLogger.write = function write(level, args, _extra) {
133
+ _extra = getPropertiesToLog(_extra)
134
134
  var selfExtra = util._extend({}, this.extra)
135
135
 
136
- extra = util._extend(selfExtra, extra)
137
- return parent.write(level, args, extra)
136
+ _extra = util._extend(selfExtra, _extra)
137
+ return parent.write(level, args, _extra)
138
138
  }
139
139
 
140
140
  childLogger.setEnabled = Logger.prototype.setEnabled
@@ -97,7 +97,7 @@ exports.readProc = readProc
97
97
  function readProc(path, callback) {
98
98
  fs.readFile(path, function readProcFile(err, data) {
99
99
  if (err) {
100
- logger.error('Error when trying to read ' + path, err)
100
+ logger.error(err, 'Error when trying to read %s', path)
101
101
  callback(err, null)
102
102
  } else {
103
103
  callback(null, data.toString())
package/newrelic.js CHANGED
@@ -1,9 +1,8 @@
1
1
  'use strict'
2
-
3
2
  /**
4
3
  * New Relic agent configuration.
5
4
  *
6
- * See lib/config.default.js in the agent distribution for a more complete
5
+ * See lib/config.defaults.js in the agent distribution for a more complete
7
6
  * description of configuration variables and their potential values.
8
7
  */
9
8
  exports.config = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "licenses": [
6
6
  {
@@ -115,6 +115,7 @@
115
115
  },
116
116
  "devDependencies": {
117
117
  "async": "^2.1.4",
118
+ "benchmark": "^2.1.4",
118
119
  "bluebird": "^3.4.7",
119
120
  "eslint": "^2.9.0",
120
121
  "jsdoc": "^3.4.0",
package/stub_api.js CHANGED
@@ -3,13 +3,16 @@
3
3
  var logger = require('./lib/logger.js')
4
4
  var RealAPI = require('./api.js')
5
5
  var TransactionHandle = require('./lib/transaction/handle')
6
+ var util = require('util')
6
7
 
7
8
 
8
9
  /* eslint-disable no-eval */
9
10
  function stubFunction(name) {
10
- return eval("(function () {return function " + name + "() {" +
11
- "logger.debug('Not calling " + name + " because New Relic is disabled.');" +
12
- "}}())")
11
+ return eval(
12
+ "(function () {return function " + name + "() {" +
13
+ "logger.debug('Not calling " + name + " because New Relic is disabled.');" +
14
+ "}}())"
15
+ )
13
16
  }
14
17
  /* eslint-enable no-eval */
15
18
 
@@ -28,8 +31,22 @@ for (var i = 0; i < length; i++) {
28
31
  }
29
32
 
30
33
  Stub.prototype.createTracer = createTracer
31
- Stub.prototype.createWebTransaction = createWebTransaction
32
- Stub.prototype.createBackgroundTransaction = createBackgroundTransaction
34
+ Stub.prototype.createWebTransaction = util.deprecate(
35
+ createWebTransaction, [
36
+ 'API#createWebTransaction is being deprecated!',
37
+ 'Please use API#startWebTransaction for transaction creation',
38
+ 'and API#getTransaction for transaction management including',
39
+ 'ending transactions.'
40
+ ].join(' ')
41
+ )
42
+ Stub.prototype.createBackgroundTransaction = util.deprecate(
43
+ createBackgroundTransaction, [
44
+ 'API#createBackgroundTransaction is being deprecated!',
45
+ 'Please use API#startBackgroundTransaction for transaction creation',
46
+ 'and API#getTransaction for transaction management including',
47
+ 'ending transactions.'
48
+ ].join(' ')
49
+ )
33
50
  Stub.prototype.startWebTransaction = startWebTransaction
34
51
  Stub.prototype.startBackgroundTransaction = startBackgroundTransaction
35
52
  Stub.prototype.getTransaction = getTransaction
@@ -89,17 +106,17 @@ function startBackgroundTransaction(name, group, callback) {
89
106
  // Normally the following call executes callback asynchronously
90
107
  function shutdown(options, cb) {
91
108
  logger.debug('Not calling shutdown because New Relic is disabled.')
92
-
109
+
93
110
  var callback = cb
94
111
  if (!callback) {
95
112
  if (typeof options === 'function') {
96
113
  callback = options
97
114
  } else {
98
- callback = new Function()
115
+ callback = function __NR_defaultCb() {}
99
116
  }
100
117
  }
101
-
102
- process.nextTick(callback)
118
+
119
+ setImmediate(callback)
103
120
  }
104
121
 
105
122
  module.exports = Stub