newrelic 8.11.2 → 8.12.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,3 +1,17 @@
1
+ ### v8.12.0 (2022-05-24)
2
+
3
+ * Added instrumentation to pino to support application logging use cases: forwarding, local decorating, and metrics.
4
+
5
+ * Added supportability metrics about the data usage bytes of harvested data to the collector endpoints.
6
+
7
+ * Added an optional way to avoid wrapping browser agent script with <script> tag when using `api.getBrowserTimingHeader`. This will ease usage with Component based libraries like React.
8
+
9
+ Thanks to @github-dd-nicolas for the contribution. 🎉
10
+
11
+ * Upgraded `@grpc/proto-loader` to fix a [CVE](https://security.snyk.io/vuln/SNYK-JS-PROTOBUFJS-2441248) with `protobufjs`.
12
+
13
+ * Upgraded `@newrelic/test-utilities` to resolve a dev-only audit warning.
14
+
1
15
  ### v8.11.2 (2022-05-23)
2
16
 
3
17
  * Fixed winston instrumentation to no longer coerce every log line to be json.
@@ -295,7 +295,7 @@ This product includes source derived from [@grpc/grpc-js](https://github.com/grp
295
295
 
296
296
  ### @grpc/proto-loader
297
297
 
298
- This product includes source derived from [@grpc/proto-loader](https://github.com/grpc/grpc-node) ([v0.6.9](https://github.com/grpc/grpc-node/tree/v0.6.9)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/blob/v0.6.9/LICENSE):
298
+ This product includes source derived from [@grpc/proto-loader](https://github.com/grpc/grpc-node) ([v0.6.12](https://github.com/grpc/grpc-node/tree/v0.6.12)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/blob/v0.6.12/LICENSE):
299
299
 
300
300
  ```
301
301
  Apache License
@@ -1770,7 +1770,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1770
1770
 
1771
1771
  ### @newrelic/test-utilities
1772
1772
 
1773
- This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v6.5.2](https://github.com/newrelic/node-test-utilities/tree/v6.5.2)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v6.5.2/LICENSE):
1773
+ This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v6.5.3](https://github.com/newrelic/node-test-utilities/tree/v6.5.3)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v6.5.3/LICENSE):
1774
1774
 
1775
1775
  ```
1776
1776
  Apache License
package/api.js CHANGED
@@ -27,8 +27,8 @@ const NAMES = require('./lib/metrics/names')
27
27
  * CONSTANTS
28
28
  *
29
29
  */
30
- const RUM_STUB =
31
- "<script type='text/javascript' %s>window.NREUM||(NREUM={});" + 'NREUM.info = %s; %s</script>'
30
+ const RUM_STUB = 'window.NREUM||(NREUM={});NREUM.info = %s;'
31
+ const RUM_STUB_SHELL = `<script type='text/javascript' %s>${RUM_STUB} %s</script>`
32
32
 
33
33
  // these messages are used in the _gracefail() method below in getBrowserTimingHeader
34
34
  const RUM_ISSUES = [
@@ -489,19 +489,24 @@ API.prototype.addIgnoringRule = function addIgnoringRule(pattern) {
489
489
  }
490
490
 
491
491
  /**
492
- * Get the <script>...</script> header necessary for Browser Monitoring
492
+ * Get the script header necessary for Browser Monitoring
493
493
  * This script must be manually injected into your templates, as high as possible
494
494
  * in the header, but _after_ any X-UA-COMPATIBLE HTTP-EQUIV meta tags.
495
495
  * Otherwise you may hurt IE!
496
496
  *
497
+ * By default this method will return a script wrapped by `<script>` tags, but with
498
+ * option `hasToRemoveScriptWrapper` it can send back only the script content
499
+ * without the `<script>` wrapper. Useful for React component based frontend.
500
+ *
497
501
  * This method must be called _during_ a transaction, and must be called every
498
502
  * time you want to generate the headers.
499
503
  *
500
504
  * Do *not* reuse the headers between users, or even between requests.
501
505
  *
502
506
  * @param {string} [options.nonce] - Nonce to inject into `<script>` header.
507
+ * @param {boolean} [options.hasToRemoveScriptWrapper] - Used to import agent script without `<script>` tag wrapper.
503
508
  * @param options
504
- * @returns {string} The `<script>` header to be injected.
509
+ * @returns {string} The script content to be injected in `<head>` or put inside `<script>` tag (depending on options)
505
510
  */
506
511
  API.prototype.getBrowserTimingHeader = function getBrowserTimingHeader(options) {
507
512
  const metric = this.agent.metrics.getOrCreateMetric(
@@ -650,9 +655,10 @@ API.prototype.getBrowserTimingHeader = function getBrowserTimingHeader(options)
650
655
 
651
656
  // set nonce attribute if passed in options
652
657
  const nonce = options && options.nonce ? 'nonce="' + options.nonce + '"' : ''
658
+ const script = options && options.hasToRemoveScriptWrapper ? RUM_STUB : RUM_STUB_SHELL
653
659
 
654
660
  // the complete header to be written to the browser
655
- const out = util.format(RUM_STUB, nonce, json, jsAgentLoader)
661
+ const out = util.format(script, nonce, json, jsAgentLoader)
656
662
 
657
663
  logger.trace('generating RUM header', out)
658
664
 
@@ -34,8 +34,21 @@ class LogAggregator extends EventAggregator {
34
34
  return
35
35
  }
36
36
 
37
- const eventData = events.toArray()
38
- return [{ logs: eventData }]
37
+ const logs = events.toArray()
38
+
39
+ /**
40
+ * Due to logging library implementation details
41
+ * some log lines are already JSON. We must de-serialize
42
+ * so it can be serialized in RemoteMethod. I know, this sucks.
43
+ */
44
+ const formattedLogs = logs.map((logLine) => {
45
+ if (typeof logLine === 'string') {
46
+ return JSON.parse(logLine)
47
+ }
48
+
49
+ return logLine
50
+ })
51
+ return [{ logs: formattedLogs }]
39
52
  }
40
53
 
41
54
  add(logLine) {
@@ -68,20 +68,24 @@ function CollectorAPI(agent) {
68
68
  /* RemoteMethods can be reused and have little per-object state, so why not
69
69
  * save some GC time?
70
70
  */
71
- this._methods = {
72
- preconnect: new RemoteMethod('preconnect', agent.config, initialEndpoint),
73
- connect: new RemoteMethod('connect', agent.config, initialEndpoint),
74
- settings: new RemoteMethod('agent_settings', agent.config, initialEndpoint),
75
- errors: new RemoteMethod('error_data', agent.config, initialEndpoint),
76
- metrics: new RemoteMethod('metric_data', agent.config, initialEndpoint),
77
- traces: new RemoteMethod('transaction_sample_data', agent.config, initialEndpoint),
78
- shutdown: new RemoteMethod('shutdown', agent.config, initialEndpoint),
79
- events: new RemoteMethod('analytic_event_data', agent.config, initialEndpoint),
80
- customEvents: new RemoteMethod('custom_event_data', agent.config, initialEndpoint),
81
- queryData: new RemoteMethod('sql_trace_data', agent.config, initialEndpoint),
82
- errorEvents: new RemoteMethod('error_event_data', agent.config, initialEndpoint),
83
- spanEvents: new RemoteMethod('span_event_data', agent.config, initialEndpoint),
84
- logEvents: new RemoteMethod('log_event_data', agent.config, initialEndpoint)
71
+ this._methods = {}
72
+ for (const [accessor, name] of [
73
+ ['preconnect', 'preconnect'],
74
+ ['connect', 'connect'],
75
+ ['settings', 'agent_settings'],
76
+ ['errors', 'error_data'],
77
+ ['metrics', 'metric_data'],
78
+ ['traces', 'transaction_sample_data'],
79
+ ['shutdown', 'shutdown'],
80
+ ['events', 'analytic_event_data'],
81
+ ['customEvents', 'custom_event_data'],
82
+ ['queryData', 'sql_trace_data'],
83
+ ['errorEvents', 'error_event_data'],
84
+ ['spanEvents', 'span_event_data'],
85
+ ['logEvents', 'log_event_data']
86
+ ]) {
87
+ const method = new RemoteMethod(name, agent, initialEndpoint)
88
+ this._methods[accessor] = method
85
89
  }
86
90
  }
87
91
 
@@ -16,6 +16,11 @@ const Sink = require('../util/stream-sink')
16
16
  const agents = require('./http-agents')
17
17
  const certificates = require('./ssl/certificates')
18
18
  const isValidLength = require('../util/byte-limit').isValidLength
19
+ const { DATA_USAGE } = require('../metrics/names')
20
+
21
+ function getMetricName(name) {
22
+ return `${DATA_USAGE.PREFIX}/${name}/${DATA_USAGE.SUFFIX}`
23
+ }
19
24
 
20
25
  /*
21
26
  *
@@ -29,13 +34,17 @@ const USER_AGENT_FORMAT = 'NewRelic-NodeAgent/%s (nodejs %s %s-%s)'
29
34
  const ENCODING_HEADER = 'CONTENT-ENCODING'
30
35
  const DEFAULT_ENCODING = 'identity'
31
36
 
32
- function RemoteMethod(name, config, endpoint) {
37
+ function RemoteMethod(name, agent, endpoint) {
33
38
  if (!name) {
34
39
  throw new TypeError('Must include name of method to invoke on collector.')
35
40
  }
41
+ if (!agent) {
42
+ throw new TypeError('Must include an agent instance.')
43
+ }
36
44
 
37
45
  this.name = name
38
- this._config = config
46
+ this._config = agent.config
47
+ this._agent = agent
39
48
  this._protocolVersion = 17
40
49
 
41
50
  this.endpoint = endpoint
@@ -62,6 +71,21 @@ RemoteMethod.prototype.serialize = function serialize(payload, callback) {
62
71
  return callback(null, res)
63
72
  }
64
73
 
74
+ /**
75
+ * This records the amount of data usage per function call and sends
76
+ * it to the metrics.
77
+ *
78
+ * @param {string} sent The serialized object we sent
79
+ * @param {object} received The raw object we got back as a response
80
+ */
81
+ RemoteMethod.prototype._reportDataUsage = function reportDataUsage(sent, received) {
82
+ const payloadByteLength = byteLength(sent)
83
+ const responseByteLength = received ? byteLength(JSON.stringify(received)) : 0
84
+ const measurement = [payloadByteLength, responseByteLength, true]
85
+ this._agent.metrics.measureBytes(DATA_USAGE.COLLECTOR, ...measurement)
86
+ this._agent.metrics.measureBytes(getMetricName(this.name), ...measurement)
87
+ }
88
+
65
89
  /**
66
90
  * The primary operation on RemoteMethod objects. If you're calling anything on
67
91
  * RemoteMethod objects aside from invoke (and you're not writing test code),
@@ -88,7 +112,14 @@ RemoteMethod.prototype.invoke = function invoke(payload, nrHeaders, callback) {
88
112
  if (err) {
89
113
  return callback(err)
90
114
  }
91
- this._post(serialized, nrHeaders, callback)
115
+ this._post(
116
+ serialized,
117
+ nrHeaders,
118
+ function onResponse(error, res) {
119
+ this._reportDataUsage(serialized, res && res.payload)
120
+ callback(error, res)
121
+ }.bind(this)
122
+ )
92
123
  }.bind(this)
93
124
  )
94
125
  }
@@ -251,6 +282,7 @@ RemoteMethod.prototype._safeRequest = function _safeRequest(options) {
251
282
  * original callback given to .send).
252
283
  * @param {Function} options.onResponse Response handler for this request (created by
253
284
  * ._post).
285
+ * @param options
254
286
  */
255
287
  RemoteMethod.prototype._request = function _request(options) {
256
288
  const requestOptions = {
@@ -375,6 +407,8 @@ RemoteMethod.prototype._headers = function _headers(options) {
375
407
  * load on the collector down.
376
408
  *
377
409
  * FIXME: come up with a better heuristic
410
+ *
411
+ * @param data
378
412
  */
379
413
  RemoteMethod.prototype._shouldCompress = function _shouldCompress(data) {
380
414
  return data && byteLength(data) > 65536
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Copyright 2022 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const pino = require('./pino')
8
+
9
+ /**
10
+ * Need to use nr-hooks style for pino because we're using the onResolved hook
11
+ * to register instrumentation.
12
+ */
13
+ module.exports = [
14
+ {
15
+ type: 'generic',
16
+ moduleName: 'pino',
17
+ onResolved: pino
18
+ }
19
+ ]
@@ -0,0 +1,156 @@
1
+ /*
2
+ * Copyright 2022 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const {
8
+ createModuleUsageMetric,
9
+ isApplicationLoggingEnabled,
10
+ isLogForwardingEnabled,
11
+ isMetricsEnabled,
12
+ isLocalDecoratingEnabled,
13
+ incrementLoggingLinesMetrics,
14
+ truncate
15
+ } = require('../../util/application-logging')
16
+ const semver = require('semver')
17
+
18
+ module.exports = function instrument(shim) {
19
+ const pinoVersion = shim.require('./package.json').version
20
+
21
+ if (semver.lt(pinoVersion, '7.0.0')) {
22
+ shim.logger.debug('Instrumentation only supported on pino >=7.0.0.')
23
+ return
24
+ }
25
+
26
+ const tools = shim.require('./lib/tools')
27
+ const agent = shim.agent
28
+ const config = agent.config
29
+
30
+ if (!isApplicationLoggingEnabled(config)) {
31
+ shim.logger.debug('Application logging not enabled. Not instrumenting pino.')
32
+ return
33
+ }
34
+
35
+ const metrics = agent.metrics
36
+ createModuleUsageMetric('pino', metrics)
37
+
38
+ const levelUtils = shim.require('./lib/levels')
39
+
40
+ /**
41
+ * Creates an object where the keys are the level labels and values are the level number.
42
+ * Pino passes in level as number but our spec needs it to be the label
43
+ */
44
+ const levelMap = Object.entries(levelUtils.levels).reduce((levels, level) => {
45
+ const [label, number] = level
46
+ levels[number] = label
47
+ return levels
48
+ }, {})
49
+
50
+ const symbols = shim.require('./lib/symbols')
51
+
52
+ /**
53
+ * Wraps the level cache so we can properly set a formatter to return the
54
+ * label as level in log line instead of number
55
+ */
56
+ shim.wrap(levelUtils, 'genLsCache', function genLsCache(shim, genLevelsCache) {
57
+ return function wrappedGenLsCache() {
58
+ const args = shim.argsToArray.apply(shim, arguments)
59
+ args[0][symbols.formattersSym].level = function nrLevelMapping(label) {
60
+ return { level: label }
61
+ }
62
+ return genLevelsCache.apply(this, args)
63
+ }
64
+ })
65
+
66
+ shim.wrap(tools, 'asJson', function wrapJson(shim, asJson) {
67
+ /**
68
+ * Wraps function in pino that is used to construct/serialize a log
69
+ * line to json
70
+ *
71
+ * @param {object} obj data from mixins
72
+ * @param {string} msg message of log line
73
+ * @param {number} num log level as number
74
+ * @param {string} time formatted snippet of json with time(`,"time":<unix time>`)
75
+ * @returns {string} serialized log line
76
+ */
77
+ return function wrappedAsJson() {
78
+ // overriding the symbol that defines the key for message(pino defaults this to `msg`)
79
+ this[symbols.messageKeySym] = 'message'
80
+
81
+ const args = shim.argsToArray.apply(shim, arguments)
82
+
83
+ /**
84
+ * changing the label from time to timestamp
85
+ * and assign unix timestamp to match our specification
86
+ */
87
+ args[3] = `,"timestamp":${Date.now()}`
88
+
89
+ if (isMetricsEnabled(config)) {
90
+ const level = args[2]
91
+ incrementLoggingLinesMetrics(levelMap[level], metrics)
92
+ }
93
+
94
+ if (isLogForwardingEnabled(config, agent)) {
95
+ const metadata = agent.getLinkingMetadata()
96
+ const chindings = this[symbols.chindingsSym]
97
+ reformatLogLine(args[0], metadata, chindings)
98
+ } else if (isLocalDecoratingEnabled(config)) {
99
+ args[1] += agent.getNRLinkingMetadata()
100
+ }
101
+
102
+ /**
103
+ * must call original asJson to allow pino
104
+ * to construct the entire log line before we
105
+ * add to the log aggregator
106
+ */
107
+ const logLine = asJson.apply(this, args)
108
+
109
+ if (isLogForwardingEnabled(config, agent)) {
110
+ agent.logs.add(logLine)
111
+ }
112
+
113
+ return logLine
114
+ }
115
+ })
116
+ }
117
+
118
+ /**
119
+ * reformats error and assigns NR context data
120
+ * to log line
121
+ *
122
+ * @param {object} logLine log line
123
+ * @param {object} metadata NR context data
124
+ * @param {string} chindings serialized string of all common log line data
125
+ */
126
+ function reformatLogLine(logLine, metadata, chindings = '') {
127
+ if (logLine.err) {
128
+ reformatError(logLine)
129
+ }
130
+
131
+ /**
132
+ * pino adds this already for us at times
133
+ * since asJson manually constructs the json string,
134
+ * it will have hostname twice if we do not delete ours.
135
+ */
136
+ if (chindings.includes('hostname')) {
137
+ delete metadata.hostname
138
+ }
139
+
140
+ Object.assign(logLine, metadata)
141
+ }
142
+
143
+ /**
144
+ * Truncates the message, stack and class of error
145
+ * and reassigns to a `error.*` keyspace. Also removes the `err`
146
+ * key on a log line
147
+ *
148
+ * @param {object} logLine log line
149
+ */
150
+ function reformatError(logLine) {
151
+ logLine['error.message'] = truncate(logLine.err.message)
152
+ logLine['error.stack'] = truncate(logLine.err.stack)
153
+ logLine['error.class'] =
154
+ logLine.err.name === 'Error' ? logLine.err.constructor.name : logLine.err.name
155
+ delete logLine.err
156
+ }
@@ -26,6 +26,7 @@ module.exports = function instrumentations() {
26
26
  'memcached': { type: MODULE_TYPE.DATASTORE },
27
27
  'mongodb': { type: MODULE_TYPE.DATASTORE },
28
28
  'mysql': { module: './instrumentation/mysql' },
29
+ 'pino': { module: './instrumentation/pino' },
29
30
  'pg': { type: MODULE_TYPE.DATASTORE },
30
31
  'q': { type: null },
31
32
  'redis': { type: MODULE_TYPE.DATASTORE },
@@ -41,9 +41,10 @@ const FROM_MILLIS = 1e-3
41
41
  * at serialization time, which allows the mappings from name to ID to happen
42
42
  * on the fly.
43
43
  *
44
- * @param {Number} apdexT The apdex-tolerating value, for use in creating apdex
44
+ * @param {number} apdexT The apdex-tolerating value, for use in creating apdex
45
45
  * statistics.
46
46
  * @param {MetricMapper} mapper The mapper that turns metric names into IDs.
47
+ * @param normalizer
47
48
  */
48
49
  function Metrics(apdexT, mapper, normalizer) {
49
50
  if (apdexT == null || apdexT === '') {
@@ -74,10 +75,10 @@ function Metrics(apdexT, mapper, normalizer) {
74
75
  *
75
76
  * @param {string} name The name of the metric.
76
77
  * @param {string} scope (Optional) The scope to which the metric belongs.
77
- * @param {Number} duration The duration of the related operation, in milliseconds.
78
- * @param {Number} exclusive (Optional) The portion of the operation specific to this
78
+ * @param {number} duration The duration of the related operation, in milliseconds.
79
+ * @param {number} exclusive (Optional) The portion of the operation specific to this
79
80
  * metric.
80
- * @return {Stats} The aggregated data related to this metric.
81
+ * @returns {Stats} The aggregated data related to this metric.
81
82
  */
82
83
  Metrics.prototype.measureMilliseconds = measureMilliseconds
83
84
 
@@ -92,12 +93,14 @@ function measureMilliseconds(name, scope, duration, exclusive) {
92
93
  * the collection will create a set of data before recording the measurement.
93
94
  *
94
95
  * @param {string} name The name of the metric.
95
- * @param {Number} size The size of the related operation, in bytes.
96
- * @return {Stats} The aggregated data related to this metric.
96
+ * @param {number} size The size of the related operation, in bytes.
97
+ * @param {number} exclusiveSize The exclusive size of the related operation, in megabytes.
98
+ * @param {boolean} exact If true, size is interpreted as bytes rather than megabytes
99
+ * @returns {Stats} The aggregated data related to this metric.
97
100
  */
98
- Metrics.prototype.measureBytes = function measureBytes(name, size) {
101
+ Metrics.prototype.measureBytes = function measureBytes(name, size, exclusiveSize, exact) {
99
102
  const stats = this.getOrCreateMetric(name)
100
- stats.recordValueInBytes(size)
103
+ stats.recordValueInBytes(size, exclusiveSize, exact)
101
104
  return stats
102
105
  }
103
106
 
@@ -107,7 +110,7 @@ Metrics.prototype.measureBytes = function measureBytes(name, size) {
107
110
  *
108
111
  * @param {string} name The name of the requested metric.
109
112
  * @param {string} scope (Optional) The scope to which the metric is bound.
110
- * @return {Stats} The aggregated data for that name.
113
+ * @returns {Stats} The aggregated data for that name.
111
114
  */
112
115
  Metrics.prototype.getOrCreateMetric = function getOrCreateMetric(name, scope) {
113
116
  const resolved = this._resolve(scope)
@@ -131,8 +134,7 @@ Metrics.prototype.getOrCreateMetric = function getOrCreateMetric(name, scope) {
131
134
  * a given run, because key transaction metrics
132
135
  * are set at connect time via server-side
133
136
  * configuration.
134
- *
135
- * @return {ApdexStats} The aggregated data for that name.
137
+ * @returns {ApdexStats} The aggregated data for that name.
136
138
  */
137
139
  Metrics.prototype.getOrCreateApdexMetric = getOrCreateApdexMetric
138
140
 
@@ -163,7 +165,7 @@ function getOrCreateApdexMetric(name, scope, overrideApdex) {
163
165
  * @param {string} name Metric name.
164
166
  * @param {string} scope (Optional) The scope, if any, to which the metric
165
167
  * belongs.
166
- * @return {object} Either a stats aggregate, an apdex stats aggregate, or
168
+ * @returns {object} Either a stats aggregate, an apdex stats aggregate, or
167
169
  * undefined.
168
170
  */
169
171
  Metrics.prototype.getMetric = function getMetric(name, scope) {
@@ -179,7 +181,7 @@ Metrics.prototype.getMetric = function getMetric(name, scope) {
179
181
  * by JSON.stringify and delivery to the collector. Hope you like nested
180
182
  * arrays!
181
183
  *
182
- * @return {Object} Set of nested arrays containing metric information.
184
+ * @returns {object} Set of nested arrays containing metric information.
183
185
  */
184
186
  Metrics.prototype.toJSON = function toJSON() {
185
187
  return this._toUnscopedData().concat(this._toScopedData())
@@ -195,7 +197,6 @@ Metrics.prototype.toJSON = function toJSON() {
195
197
  *
196
198
  * @param {Metrics} other
197
199
  * The collection to be folded into this one.
198
- *
199
200
  * @param {boolean} adjustStartTime
200
201
  * If the start time for the timeslice should be adjusted.
201
202
  */
@@ -228,7 +229,7 @@ function _merge(a, b) {
228
229
  * already exist.
229
230
  *
230
231
  * @param {string} scope (Optional) The scope to look up.
231
- * @return {object} The namespace associated with the provided scope, or the
232
+ * @returns {object} The namespace associated with the provided scope, or the
232
233
  * un-scoped metrics if the scope isn't set.
233
234
  */
234
235
  Metrics.prototype._resolve = function _resolve(scope) {
@@ -268,6 +269,7 @@ Metrics.prototype._getUnscopedData = function _getUnscopedData(name) {
268
269
  * mappings along the way. Split from _getUnscopedData for performance.
269
270
  *
270
271
  * @param {string} name The string to look up.
272
+ * @param scope
271
273
  */
272
274
  Metrics.prototype._getScopedData = function _getScopedData(name, scope) {
273
275
  if (!this.scoped[scope][name]) {
@@ -283,7 +285,7 @@ Metrics.prototype._getScopedData = function _getScopedData(name, scope) {
283
285
  }
284
286
 
285
287
  /**
286
- * @return {object} A serializable version of the unscoped metrics. Intended
288
+ * @returns {object} A serializable version of the unscoped metrics. Intended
287
289
  * for use by toJSON.
288
290
  */
289
291
  Metrics.prototype._toUnscopedData = function _toUnscopedData() {
@@ -300,7 +302,7 @@ Metrics.prototype._toUnscopedData = function _toUnscopedData() {
300
302
  }
301
303
 
302
304
  /**
303
- * @return {object} A serializable version of the scoped metrics. Intended for
305
+ * @returns {object} A serializable version of the scoped metrics. Intended for
304
306
  * use by toJSON.
305
307
  */
306
308
  Metrics.prototype._toScopedData = function _toScopedData() {
@@ -75,7 +75,7 @@ class MetricAggregator extends Aggregator {
75
75
  *
76
76
  * @param {string} name The name of the requested metric.
77
77
  * @param {string} scope (Optional) The scope to which the metric is bound.
78
- * @return {Stats} The aggregated data for that name.
78
+ * @returns {Stats} The aggregated data for that name.
79
79
  */
80
80
  getOrCreateMetric(name, scope) {
81
81
  return this._metrics.getOrCreateMetric(name, scope)
@@ -90,10 +90,10 @@ class MetricAggregator extends Aggregator {
90
90
  *
91
91
  * @param {string} name The name of the metric.
92
92
  * @param {string} scope (Optional) The scope to which the metric belongs.
93
- * @param {Number} duration The duration of the related operation, in milliseconds.
94
- * @param {Number} exclusive (Optional) The portion of the operation specific to this
93
+ * @param {number} duration The duration of the related operation, in milliseconds.
94
+ * @param {number} exclusive (Optional) The portion of the operation specific to this
95
95
  * metric.
96
- * @return {Stats} The aggregated data related to this metric.
96
+ * @returns {Stats} The aggregated data related to this metric.
97
97
  */
98
98
  measureMilliseconds(name, scope, duration, exclusive) {
99
99
  return this._metrics.measureMilliseconds(name, scope, duration, exclusive)
@@ -102,13 +102,18 @@ class MetricAggregator extends Aggregator {
102
102
  /**
103
103
  * Set the size of an operation. If there are no data for the name existing,
104
104
  * the collection will create a set of data before recording the measurement.
105
+ * If data do exist for the given name, the value will be incremented by the
106
+ * given size. Use `exclusiveSize` to set the size of this specific operation,
107
+ * if it is different from the overall size of the operation.
105
108
  *
106
109
  * @param {string} name The name of the metric.
107
- * @param {Number} size The size of the related operation, in bytes.
108
- * @return {Stats} The aggregated data related to this metric.
110
+ * @param {number} size The size of the related operation, in megabytes.
111
+ * @param {number} exclusiveSize The exclusive size of the related operation, in megabytes.
112
+ * @param {boolean} exact If true, size is interpreted as bytes rather than megabytes
113
+ * @returns {Stats} The aggregated data related to this metric.
109
114
  */
110
- measureBytes(name, size) {
111
- return this._metrics.measureBytes(name, size)
115
+ measureBytes(name, size, exclusiveSize, exact) {
116
+ return this._metrics.measureBytes(name, size, exclusiveSize, exact)
112
117
  }
113
118
 
114
119
  /**
@@ -119,7 +124,7 @@ class MetricAggregator extends Aggregator {
119
124
  * @param {string} name Metric name.
120
125
  * @param {string} scope (Optional) The scope, if any, to which the metric
121
126
  * belongs.
122
- * @return {object} Either a stats aggregate, an apdex stats aggregate, or
127
+ * @returns {object} Either a stats aggregate, an apdex stats aggregate, or
123
128
  * undefined.
124
129
  */
125
130
  getMetric(name, scope) {
@@ -138,8 +143,7 @@ class MetricAggregator extends Aggregator {
138
143
  * a given run, because key transaction metrics
139
144
  * are set at connect time via server-side
140
145
  * configuration.
141
- *
142
- * @return {ApdexStats} The aggregated data for that name.
146
+ * @returns {ApdexStats} The aggregated data for that name.
143
147
  */
144
148
  getOrCreateApdexMetric(name, scope, overrideApdex) {
145
149
  return this._metrics.getOrCreateApdexMetric(name, scope, overrideApdex)
@@ -204,6 +204,15 @@ const EVENT_HARVEST = {
204
204
  }
205
205
  }
206
206
 
207
+ const DATA_USAGE_PREFIX = `${SUPPORTABILITY.NODEJS}/Collector`
208
+ const DATA_USAGE_SUFFIX = 'Output/Bytes'
209
+
210
+ const DATA_USAGE = {
211
+ SUFFIX: DATA_USAGE_SUFFIX,
212
+ PREFIX: DATA_USAGE_PREFIX,
213
+ COLLECTOR: `${DATA_USAGE_PREFIX}/${DATA_USAGE_SUFFIX}`
214
+ }
215
+
207
216
  const WEB = {
208
217
  RESPONSE_TIME: 'WebTransaction',
209
218
  FRAMEWORK_PREFIX: 'WebFrameworkUri',
@@ -295,6 +304,7 @@ module.exports = {
295
304
  CPU: CPU,
296
305
  CUSTOM: 'Custom',
297
306
  CUSTOM_EVENTS: CUSTOM_EVENTS,
307
+ DATA_USAGE,
298
308
  DB: DB,
299
309
  DISTRIBUTED_TRACE,
300
310
  ERRORS: ERRORS,
@@ -28,8 +28,8 @@ function Stats() {
28
28
  /**
29
29
  * Update the summary statistics with a new value.
30
30
  *
31
- * @param {Number} totalTime Time, in seconds, of the measurement.
32
- * @param {Number} exclusiveTime Time that was taken by only the
31
+ * @param {number} totalTime Time, in seconds, of the measurement.
32
+ * @param {number} exclusiveTime Time that was taken by only the
33
33
  * current measurement (optional).
34
34
  */
35
35
  Stats.prototype.recordValue = function recordValue(totalTime, exclusiveTime) {
@@ -63,9 +63,15 @@ function recordValueInMillis(totalTime, exclusiveTime) {
63
63
  this.recordValue(totalTime * FROM_MILLIS, exclusiveTime >= 0 ? exclusiveTime * FROM_MILLIS : null)
64
64
  }
65
65
 
66
- Stats.prototype.recordValueInBytes = function recordValueInBytes(bytes, exclusiveBytes) {
67
- exclusiveBytes = exclusiveBytes || bytes
68
- this.recordValue(bytes / BYTES_PER_MB, exclusiveBytes / BYTES_PER_MB)
66
+ Stats.prototype.recordValueInBytes = function recordValueInBytes(bytes, exclusiveBytes, exact) {
67
+ exclusiveBytes = typeof exclusiveBytes === 'number' ? exclusiveBytes : bytes
68
+ if (!exact) {
69
+ // normally values are recorded in megabytes and so must be converted from bytes.
70
+ // set exact=true to set the byte value directly.
71
+ bytes = bytes / BYTES_PER_MB
72
+ exclusiveBytes = exclusiveBytes / BYTES_PER_MB
73
+ }
74
+ this.recordValue(bytes, exclusiveBytes)
69
75
  }
70
76
 
71
77
  Stats.prototype.incrementCallCount = function incrementCallCount(count) {
@@ -77,6 +83,8 @@ Stats.prototype.incrementCallCount = function incrementCallCount(count) {
77
83
 
78
84
  /**
79
85
  * Fold another summary's statistics into this one.
86
+ *
87
+ * @param other
80
88
  */
81
89
  Stats.prototype.merge = function merge(other) {
82
90
  if (other.count && !other.callCount) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "8.11.2",
3
+ "version": "8.12.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -160,7 +160,7 @@
160
160
  },
161
161
  "dependencies": {
162
162
  "@grpc/grpc-js": "^1.5.5",
163
- "@grpc/proto-loader": "^0.6.9",
163
+ "@grpc/proto-loader": "^0.6.12",
164
164
  "@newrelic/aws-sdk": "^4.1.1",
165
165
  "@newrelic/koa": "^6.1.1",
166
166
  "@newrelic/superagent": "^5.1.0",
@@ -179,7 +179,7 @@
179
179
  "@newrelic/eslint-config": "^0.0.3",
180
180
  "@newrelic/newrelic-oss-cli": "^0.1.2",
181
181
  "@newrelic/proxy": "^2.0.0",
182
- "@newrelic/test-utilities": "^6.5.2",
182
+ "@newrelic/test-utilities": "^6.5.3",
183
183
  "@octokit/rest": "^18.0.15",
184
184
  "@slack/bolt": "^3.7.0",
185
185
  "ajv": "^6.12.6",