newrelic 8.11.2 → 8.13.1

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,27 @@
1
+ ### v8.13.1 (2022-05-27)
2
+
3
+ * Fixed passing undefined as a formatter options to `winston.format.combine`
4
+
5
+ ### v8.13.0 (2022-05-26)
6
+
7
+ * Moved log forwarding logic to a transport so customer transports are not polluted with NR linking metadata and timestamp and error manipulations.
8
+
9
+ * Prevented transmitting logs when application level logging has been disabled.
10
+
11
+ ### v8.12.0 (2022-05-24)
12
+
13
+ * Added instrumentation to pino to support application logging use cases: forwarding, local decorating, and metrics.
14
+
15
+ * Added supportability metrics about the data usage bytes of harvested data to the collector endpoints.
16
+
17
+ * 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.
18
+
19
+ Thanks to @github-dd-nicolas for the contribution. 🎉
20
+
21
+ * Upgraded `@grpc/proto-loader` to fix a [CVE](https://security.snyk.io/vuln/SNYK-JS-PROTOBUFJS-2441248) with `protobufjs`.
22
+
23
+ * Upgraded `@newrelic/test-utilities` to resolve a dev-only audit warning.
24
+
1
25
  ### v8.11.2 (2022-05-23)
2
26
 
3
27
  * Fixed winston instrumentation to no longer coerce every log line to be json.
@@ -26,6 +26,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
26
26
  * [json-stringify-safe](#json-stringify-safe)
27
27
  * [readable-stream](#readable-stream)
28
28
  * [semver](#semver)
29
+ * [winston-transport](#winston-transport)
29
30
 
30
31
  **[devDependencies](#devDependencies)**
31
32
 
@@ -295,7 +296,7 @@ This product includes source derived from [@grpc/grpc-js](https://github.com/grp
295
296
 
296
297
  ### @grpc/proto-loader
297
298
 
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):
299
+ 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
300
 
300
301
  ```
301
302
  Apache License
@@ -1330,6 +1331,36 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1330
1331
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
1331
1332
  IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1332
1333
 
1334
+ ```
1335
+
1336
+ ### winston-transport
1337
+
1338
+ This product includes source derived from [winston-transport](https://github.com/winstonjs/winston-transport) ([v4.5.0](https://github.com/winstonjs/winston-transport/tree/v4.5.0)), distributed under the [MIT License](https://github.com/winstonjs/winston-transport/blob/v4.5.0/LICENSE):
1339
+
1340
+ ```
1341
+ The MIT License (MIT)
1342
+
1343
+ Copyright (c) 2015 Charlie Robbins & the contributors.
1344
+
1345
+ Permission is hereby granted, free of charge, to any person obtaining a copy
1346
+ of this software and associated documentation files (the "Software"), to deal
1347
+ in the Software without restriction, including without limitation the rights
1348
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1349
+ copies of the Software, and to permit persons to whom the Software is
1350
+ furnished to do so, subject to the following conditions:
1351
+
1352
+ The above copyright notice and this permission notice shall be included in all
1353
+ copies or substantial portions of the Software.
1354
+
1355
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1356
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1357
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1358
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1359
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1360
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1361
+ SOFTWARE.
1362
+
1363
+
1333
1364
  ```
1334
1365
 
1335
1366
 
@@ -1770,7 +1801,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1770
1801
 
1771
1802
  ### @newrelic/test-utilities
1772
1803
 
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):
1804
+ This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v6.5.4](https://github.com/newrelic/node-test-utilities/tree/v6.5.4)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v6.5.4/LICENSE):
1774
1805
 
1775
1806
  ```
1776
1807
  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) {
@@ -52,6 +65,17 @@ class LogAggregator extends EventAggregator {
52
65
  super.add(logLine, priority)
53
66
  })
54
67
  }
68
+
69
+ reconfigure(config) {
70
+ const oldLimit = this.limit
71
+ super.reconfigure(config)
72
+ if (this.limit <= 0 && oldLimit > 0) {
73
+ logger.debug('This New Relic account has disabled APM logs.')
74
+ this.clear()
75
+ } else if (oldLimit <= 0 && this.limit > 0) {
76
+ logger.debug('This New Relic account has re-enabled APM logs.')
77
+ }
78
+ }
55
79
  }
56
80
 
57
81
  module.exports = LogAggregator
@@ -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,108 @@
1
+ /*
2
+ * Copyright 2022 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const TransportStream = require('winston-transport')
8
+ const logger = require('../logger').child({ component: 'nr-winston-transport' })
9
+ const { truncate } = require('../util/application-logging')
10
+
11
+ /**
12
+ * Transport used to prepare a log line and add to the new relic agent
13
+ * log aggregator.
14
+ *
15
+ * Note*: This copies the log line so no other transports will get the
16
+ * mutated data.
17
+ */
18
+ class NrTransport extends TransportStream {
19
+ constructor(opts = {}) {
20
+ // set this option to have winston handle uncaught exceptions
21
+ // See: https://github.com/winstonjs/winston#handling-uncaught-exceptions-with-winston
22
+ opts.handleExceptions = true
23
+ super(opts)
24
+ this.name = 'newrelic'
25
+ this.agent = opts.agent
26
+ this.config = opts.agent.config
27
+ }
28
+
29
+ /**
30
+ * Executed on every log line. We will get the linking metadata
31
+ * and add this, along with reformatting of timestamp and error
32
+ * to a copy of the log line
33
+ *
34
+ * @param {object} logLine a winston log line
35
+ * @param {Function} callback callback to invoke once we are done
36
+ */
37
+ log(logLine, callback) {
38
+ const metadata = this.agent.getLinkingMetadata()
39
+ const formattedLine = reformatLogLine(logLine, metadata, this.agent)
40
+ this.agent.logs.add(formattedLine)
41
+ callback()
42
+ }
43
+ }
44
+
45
+ module.exports = NrTransport
46
+
47
+ /**
48
+ * Reformats a log line by reformatting errors, timestamp and adding
49
+ * new relic linking metadata(context). When uncaught exceptions exist
50
+ * an exception property will exist on the log line. This will tell us
51
+ * that we need to reformat the error
52
+ *
53
+ * @param {object} logLine log line
54
+ * @param {object} metadata linking metadata
55
+ * @param {object} agent NR agent
56
+ * @returns {object} copy of log line with NR linking metadata
57
+ */
58
+ function reformatLogLine(logLine, metadata, agent) {
59
+ // Add the metadata to a copy of the logLine
60
+ const formattedLine = Object.assign({}, logLine, metadata)
61
+
62
+ if (formattedLine.exception === true) {
63
+ reformatError(formattedLine)
64
+ }
65
+
66
+ reformatTimestamp(formattedLine, agent)
67
+
68
+ return formattedLine
69
+ }
70
+
71
+ /**
72
+ * Decorates the log line with truncated error.message, error.class, and error.stack and removes
73
+ * trace and stack
74
+ *
75
+ * @param {object} logLine a log line
76
+ */
77
+ function reformatError(logLine) {
78
+ // Due to Winston internals sometimes the error on the logLine object is a string or an
79
+ // empty object, and so the message property is all we have
80
+ const errorMessage = logLine.error.message || logLine.message || ''
81
+
82
+ logLine['error.message'] = truncate(errorMessage)
83
+ logLine['error.class'] =
84
+ logLine.error.name === 'Error' ? logLine.error.constructor.name : logLine.error.name
85
+ logLine['error.stack'] = truncate(logLine.error.stack)
86
+ logLine.message = truncate(logLine.message)
87
+
88
+ // Removes additional capture of stack to reduce overall payload/log-line size.
89
+ // The server has a maximum of ~4k characters per line allowed.
90
+ delete logLine.trace
91
+ delete logLine.stack
92
+ }
93
+
94
+ /**
95
+ * Turns timestamp into unix timestamp. If timestamp existed it will move original
96
+ * to `original_timestamp` key
97
+ *
98
+ * @param {object} logLine a log line
99
+ */
100
+ function reformatTimestamp(logLine) {
101
+ if (logLine.timestamp) {
102
+ logger.traceOnce(
103
+ 'Overwriting `timestamp` key; assigning original value to `original_timestamp`.'
104
+ )
105
+ logLine.original_timestamp = logLine.timestamp
106
+ }
107
+ logLine.timestamp = Date.now()
108
+ }
@@ -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
+ }
@@ -12,10 +12,11 @@ const {
12
12
  isLocalDecoratingEnabled,
13
13
  isMetricsEnabled,
14
14
  createModuleUsageMetric,
15
- incrementLoggingLinesMetrics,
16
- truncate
15
+ incrementLoggingLinesMetrics
17
16
  } = require('../util/application-logging')
17
+
18
18
  const logger = require('../logger').child({ component: 'winston' })
19
+ const NrTransport = require('./nr-winston-transport')
19
20
 
20
21
  module.exports = function instrument(agent, winston, _, shim) {
21
22
  const config = agent.config
@@ -33,40 +34,38 @@ module.exports = function instrument(agent, winston, _, shim) {
33
34
  return createLogger.apply(this, args)
34
35
  }
35
36
 
36
- registerFormatter({ opts, config, agent, winston })
37
+ createModuleUsageMetric('winston', agent.metrics)
38
+
39
+ if (isLocalDecoratingEnabled(config) || isMetricsEnabled(config)) {
40
+ registerFormatter({ opts, agent, winston })
41
+ }
42
+
43
+ const winstonLogger = createLogger.apply(this, args)
37
44
 
38
- return createLogger.apply(this, args)
45
+ if (isLogForwardingEnabled(config, agent)) {
46
+ winstonLogger.add(new NrTransport({ agent }))
47
+ }
48
+
49
+ return winstonLogger
39
50
  }
40
51
  })
41
52
  }
42
53
 
43
54
  /**
44
- * There is no right way to do this. But since we are automagical
45
- * it is hard to predict how a customer uses winston. We will iterate over the formatters specified on the logger and add last if forwarder or first if local decorating.
46
- * This is because we want all the customizations of previous formatters before adding log log to log aggregator. But in the case of local decorating we want to do this first so any formatter that is transforming data will have the changes.
47
- *
48
- * Note: The logic explained above does not apply if a customer specifies multiple formats for a given transport.
49
- * We cannot instrument the formats on the transport because if a customer has multiple transports we would be duplicating logs when forwaring.
55
+ * Apply a formatter to keep track of logging metrics, and in the case of local decorating appending
56
+ * the NR-LINKING metadata to message. We want to do this first so any formatter that is transforming
57
+ * data will have the changes.
50
58
  *
51
59
  * @param {object} params object passed to function
52
60
  * @param {object} params.opts options from winston.createLogger
53
- * @param {object} params.config agent config
54
61
  * @param {object} params.agent NR agent
55
62
  * @param {object} params.winston exported winston package
56
63
  */
57
- function registerFormatter({ opts, config, agent, winston }) {
64
+ function registerFormatter({ opts, agent, winston }) {
58
65
  const instrumentedFormatter = nrWinstonFormatter(agent, winston)
59
66
 
60
- if ('format' in opts) {
61
- const formatters = [opts.format]
62
-
63
- if (isLogForwardingEnabled(config, agent)) {
64
- formatters.push(instrumentedFormatter())
65
- } else {
66
- formatters.unshift(instrumentedFormatter())
67
- }
68
-
69
- opts.format = winston.format.combine(...formatters)
67
+ if (opts.format) {
68
+ opts.format = winston.format.combine(instrumentedFormatter(), opts.format)
70
69
  } else {
71
70
  opts.format = instrumentedFormatter()
72
71
  }
@@ -74,39 +73,26 @@ function registerFormatter({ opts, config, agent, winston }) {
74
73
 
75
74
  /**
76
75
  * This formatter is being used to facilitate
77
- * the application logging use cases.
78
- * It is worth noting that the features below are mutually
79
- * exclusive.
80
- *
81
- * The application logging use cases are local log decorating
82
- * and log forwarding.
76
+ * the two application logging use cases: metrics and local log decorating.
83
77
  *
84
78
  * Local log decorating appends `NR-LINKING` piped metadata to
85
79
  * the message key in log line. You must configure a log forwarder to get
86
80
  * this data to NR1.
87
81
  *
88
- * Log forwarding includes the linking metadata as keys on logging
89
- * object as well as adds the log line to the agent log aggregator.
90
- *
91
82
  * @param {object} agent NR agent
92
83
  * @param {object} winston exported winston package
93
- * @returns {object} log line with NR context or NR-LINKING metadata on message
84
+ * @returns {object} log line NR-LINKING metadata on message when local log decorating is enabled
94
85
  */
95
86
  function nrWinstonFormatter(agent, winston) {
96
87
  const config = agent.config
97
88
  const metrics = agent.metrics
98
- createModuleUsageMetric('winston', metrics)
99
89
 
100
90
  return winston.format((logLine) => {
101
91
  if (isMetricsEnabled(config)) {
102
92
  incrementLoggingLinesMetrics(logLine.level, metrics)
103
93
  }
104
94
 
105
- if (isLogForwardingEnabled(config, agent)) {
106
- const metadata = agent.getLinkingMetadata()
107
- reformatLogLine(logLine, metadata, agent)
108
- agent.logs.add(logLine)
109
- } else if (isLocalDecoratingEnabled(config)) {
95
+ if (isLocalDecoratingEnabled(config)) {
110
96
  logLine.message += agent.getNRLinkingMetadata()
111
97
  }
112
98
 
@@ -114,64 +100,6 @@ function nrWinstonFormatter(agent, winston) {
114
100
  })
115
101
  }
116
102
 
117
- /**
118
- * Reformats a log line by reformatting errors, timestamp and adding
119
- * new relic linking metadata(context)
120
- *
121
- * @param {object} logLine log line
122
- * @param {object} metadata linking metadata
123
- * @param {object} agent NR agent
124
- */
125
- function reformatLogLine(logLine, metadata, agent) {
126
- if (logLine.exception === true) {
127
- reformatError(logLine)
128
- }
129
-
130
- reformatTimestamp(logLine, agent)
131
-
132
- // Add the metadata to the logLine object being logged
133
- Object.assign(logLine, metadata)
134
- }
135
-
136
- /**
137
- * Decorates the log line with truncated error.message, error.class, and error.stack and removes
138
- * trace and stack
139
- *
140
- * @param {object} logLine a log line
141
- */
142
- function reformatError(logLine) {
143
- // Due to Winston internals sometimes the error on the logLine object is a string or an
144
- // empty object, and so the message property is all we have
145
- const errorMessage = logLine.error.message || logLine.message || ''
146
-
147
- logLine['error.message'] = truncate(errorMessage)
148
- logLine['error.class'] =
149
- logLine.error.name === 'Error' ? logLine.error.constructor.name : logLine.error.name
150
- logLine['error.stack'] = truncate(logLine.error.stack)
151
- logLine.message = truncate(logLine.message)
152
-
153
- // Removes additional capture of stack to reduce overall payload/log-line size.
154
- // The server has a maximum of ~4k characters per line allowed.
155
- delete logLine.trace
156
- delete logLine.stack
157
- }
158
-
159
- /**
160
- * Turns timestamp into unix timestamp. If timestamp existed it will move original
161
- * to `original_timestamp` key
162
- *
163
- * @param {object} logLine a log line
164
- */
165
- function reformatTimestamp(logLine) {
166
- if (logLine.timestamp) {
167
- logger.traceOnce(
168
- 'Overwriting `timestamp` key; assigning original value to `original_timestamp`.'
169
- )
170
- logLine.original_timestamp = logLine.timestamp
171
- }
172
- logLine.timestamp = Date.now()
173
- }
174
-
175
103
  /**
176
104
  * winston allows you to compose a logger
177
105
  * from an instantiated logger. Through a series
@@ -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.13.1",
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",
@@ -170,7 +170,8 @@
170
170
  "https-proxy-agent": "^5.0.0",
171
171
  "json-stringify-safe": "^5.0.0",
172
172
  "readable-stream": "^3.6.0",
173
- "semver": "^5.3.0"
173
+ "semver": "^5.3.0",
174
+ "winston-transport": "^4.5.0"
174
175
  },
175
176
  "optionalDependencies": {
176
177
  "@newrelic/native-metrics": "^8.0.0"
@@ -179,7 +180,7 @@
179
180
  "@newrelic/eslint-config": "^0.0.3",
180
181
  "@newrelic/newrelic-oss-cli": "^0.1.2",
181
182
  "@newrelic/proxy": "^2.0.0",
182
- "@newrelic/test-utilities": "^6.5.2",
183
+ "@newrelic/test-utilities": "^6.5.4",
183
184
  "@octokit/rest": "^18.0.15",
184
185
  "@slack/bolt": "^3.7.0",
185
186
  "ajv": "^6.12.6",