newrelic 2.0.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.
Files changed (44) hide show
  1. package/.eslintrc +1 -0
  2. package/.github/PULL_REQUEST_TEMPLATE.md +5 -0
  3. package/NEWS.md +75 -0
  4. package/README.md +24 -12
  5. package/api.js +74 -29
  6. package/index.js +6 -2
  7. package/lib/agent.js +15 -3
  8. package/lib/collector/api.js +11 -9
  9. package/lib/collector/facts.js +9 -9
  10. package/lib/config.default.js +24 -2
  11. package/lib/config.js +70 -7
  12. package/lib/db/parse-sql.js +3 -0
  13. package/lib/db/tracer.js +3 -3
  14. package/lib/feature_flags.js +1 -0
  15. package/lib/instrumentation/core/async_hooks.js +92 -0
  16. package/lib/instrumentation/core/child_process.js +35 -0
  17. package/lib/instrumentation/core/globals.js +4 -23
  18. package/lib/instrumentation/core/http.js +1 -1
  19. package/lib/instrumentation/hapi.js +67 -33
  20. package/lib/instrumentation/ioredis.js +1 -6
  21. package/lib/metrics/names.js +7 -2
  22. package/lib/shim/datastore-shim.js +13 -1
  23. package/lib/shim/shim.js +46 -8
  24. package/lib/shim/webframework-shim.js +4 -2
  25. package/lib/stats/index.js +1 -1
  26. package/lib/system-info.js +21 -42
  27. package/lib/transaction/handle.js +14 -0
  28. package/lib/transaction/index.js +10 -2
  29. package/lib/transaction/tracer/index.js +1 -0
  30. package/lib/util/hashes.js +8 -1
  31. package/lib/util/logger.js +4 -4
  32. package/lib/util/sql/obfuscate.js +10 -4
  33. package/lib/utilization/aws-info.js +46 -0
  34. package/lib/utilization/azure-info.js +54 -0
  35. package/lib/utilization/common.js +106 -0
  36. package/lib/utilization/docker-info.js +100 -0
  37. package/lib/utilization/gcp-info.js +56 -0
  38. package/lib/utilization/index.js +28 -0
  39. package/lib/utilization/pcf-info.js +38 -0
  40. package/newrelic.js +1 -2
  41. package/package.json +2 -1
  42. package/stub_api.js +26 -9
  43. package/lib/aws-info.js +0 -100
  44. package/lib/parse-dockerinfo.js +0 -57
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
 
@@ -77,6 +78,9 @@ var ENV_MAPPING = {
77
78
  },
78
79
  utilization: {
79
80
  detect_aws: "NEW_RELIC_UTILIZATION_DETECT_AWS",
81
+ detect_pcf: "NEW_RELIC_UTILIZATION_DETECT_PCF",
82
+ detect_azure: "NEW_RELIC_UTILIZATION_DETECT_AZURE",
83
+ detect_gcp: "NEW_RELIC_UTILIZATION_DETECT_GCP",
80
84
  detect_docker: "NEW_RELIC_UTILIZATION_DETECT_DOCKER",
81
85
  logical_processors: "NEW_RELIC_UTILIZATION_LOGICAL_PROCESSORS",
82
86
  total_ram_mib: "NEW_RELIC_UTILIZATION_TOTAL_RAM_MIB",
@@ -724,6 +728,19 @@ Config.prototype.logUnknown = function logUnknown(json, key) {
724
728
  )
725
729
  }
726
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
+
727
744
  /**
728
745
  * Gets the user set host display name. If not provided, it returns the default value.
729
746
  *
@@ -945,13 +962,59 @@ Config.prototype._fromEnvironment = function _fromEnvironment(metadata, data) {
945
962
  * based on special properties of configuration values should go here as well.
946
963
  */
947
964
  Config.prototype._canonicalize = function _canonicalize() {
948
- var codes = this.error_collector && this.error_collector.ignore_status_codes
949
- if (codes) {
950
- this.error_collector.ignore_status_codes = codes.map(function cb_map(code) {
951
- return parseInt(code, 10)
952
- })
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
953
1010
  }
954
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
+
955
1018
  var logAliases = {
956
1019
  'verbose': 'trace',
957
1020
  'debugging': 'debug',
@@ -1017,10 +1080,10 @@ Config.prototype.measureInternal = function measureInternal(suffix, duration) {
1017
1080
  Config.prototype.validateFlags = function validateFlags() {
1018
1081
  Object.keys(this.feature_flag).forEach(function cb_forEach(key) {
1019
1082
  if (feature_flag.released.indexOf(key) > -1) {
1020
- logger.warn('Feature flag ' + key + ' has been released')
1083
+ logger.warn('Feature flag %s has been released', key)
1021
1084
  }
1022
1085
  if (feature_flag.unreleased.indexOf(key) > -1) {
1023
- logger.warn('Feature flag ' + key + ' has been deprecated')
1086
+ logger.warn('Feature flag %s has been deprecated', key)
1024
1087
  }
1025
1088
  })
1026
1089
  }
@@ -1,5 +1,8 @@
1
1
  'use strict'
2
2
 
3
+ // TODO: remove in favor of /lib/db/query-parsers/sql.js
4
+ // This module is currently used only in the Oracle instrumentation
5
+
3
6
  var logger = require('../logger').child({component: 'parse_sql'})
4
7
  var StatementMatcher = require('./statement-matcher')
5
8
  var ParsedStatement = require('./parsed-statement')
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
+ }
@@ -33,12 +33,7 @@ module.exports = function initialize(agent, redis, moduleName, shim) {
33
33
  return {
34
34
  name: (command.name || 'unknown'),
35
35
  parameters: parameters,
36
- callback: function bindCallback(shim, _f, _n, segment) {
37
- // record duration when promise resolves
38
- command.promise.finally(function cb_resolved() {
39
- segment.touch()
40
- })
41
- }
36
+ promise: true
42
37
  }
43
38
  }
44
39
  }
@@ -150,12 +150,17 @@ 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 = {
157
158
  AWS_ERROR: SUPPORTABILITY.UTILIZATION + '/aws/error',
158
- DOCKER_ERROR: SUPPORTABILITY.UTILIZATION + '/docker/error'
159
+ PCF_ERROR: SUPPORTABILITY.UTILIZATION + '/pcf/error',
160
+ AZURE_ERROR: SUPPORTABILITY.UTILIZATION + '/azure/error',
161
+ GCP_ERROR: SUPPORTABILITY.UTILIZATION + '/gcp/error',
162
+ DOCKER_ERROR: SUPPORTABILITY.UTILIZATION + '/docker/error',
163
+ BOOT_ID_ERROR: SUPPORTABILITY.UTILIZATION + '/boot_id/error'
159
164
  }
160
165
 
161
166
 
@@ -523,10 +523,22 @@ function recordBatchQuery(nodule, properties, querySpec) {
523
523
  */
524
524
  function parseQuery(query, nodule) {
525
525
  var parsed = this.queryParser.call(nodule, query)
526
+
527
+ var collection = parsed.collection
528
+ // strip enclosing special characters from collection (table) name
529
+ if (typeof collection === 'string' && collection.length > 2) {
530
+ if (/^[\[{'"`]/.test(collection)) {
531
+ collection = collection.substr(1)
532
+ }
533
+ if (/[\]}'"`]$/.test(collection)) {
534
+ collection = collection.substr(0, collection.length - 1)
535
+ }
536
+ }
537
+
526
538
  return new ParsedStatement(
527
539
  this._metrics.PREFIX,
528
540
  parsed.operation,
529
- parsed.collection,
541
+ collection,
530
542
  parsed.query
531
543
  )
532
544
  }
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,8 +912,11 @@ 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
- if (segment && ret) {
919
+ if (ret) {
916
920
  if (segDesc.stream) {
917
921
  shim.logger.trace('Binding return value as stream.')
918
922
  _bindStream(shim, ret, segment, {
@@ -1034,12 +1038,20 @@ function bindSegment(nodule, property, segment, full) {
1034
1038
 
1035
1039
  // Determine our arguments.
1036
1040
  if (this.isObject(property) && !this.isArray(property)) {
1037
- // bindSegment(func, segment, full)
1041
+ // bindSegment(func, segment [, full])
1038
1042
  full = segment
1039
1043
  segment = property
1040
1044
  property = null
1041
1045
  }
1042
1046
 
1047
+ // This protects against the `bindSegment(func, null, true)` case, where the
1048
+ // segment is `null`, and thus `true` (the full param) is detected as the
1049
+ // segment.
1050
+ if (segment != null && !this.isObject(segment)) {
1051
+ this.logger.debug({segment: segment}, 'Segment is not a segment, not binding.')
1052
+ return nodule
1053
+ }
1054
+
1043
1055
  var wrapped = this.wrap(nodule, property, function wrapFunc(shim, func) {
1044
1056
  if (!shim.isFunction(func)) {
1045
1057
  return func
@@ -1133,8 +1145,8 @@ function bindCallbackSegment(args, cbIdx, parentSegment) {
1133
1145
  }
1134
1146
 
1135
1147
  /**
1136
- * Retrieves the segment associated with the given object, or the currently
1137
- * 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.
1138
1150
  *
1139
1151
  * - `getSegment([obj])`
1140
1152
  *
@@ -1143,8 +1155,8 @@ function bindCallbackSegment(args, cbIdx, parentSegment) {
1143
1155
  * @param {*} [obj] - The object to retrieve a segment from.
1144
1156
  *
1145
1157
  * @return {?TraceSegment} The trace segment associated with the given object or
1146
- * the currently active segment if no object is provided or no segment is
1147
- * associated with the object.
1158
+ * the current segment if no object is provided or no segment is associated
1159
+ * with the object.
1148
1160
  */
1149
1161
  function getSegment(obj) {
1150
1162
  if (obj && obj.__NR_segment) {
@@ -1153,6 +1165,32 @@ function getSegment(obj) {
1153
1165
  return this.tracer.getSegment()
1154
1166
  }
1155
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
+
1156
1194
  /**
1157
1195
  * Associates a segment with the given object.
1158
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
  }