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 +24 -0
- package/THIRD_PARTY_NOTICES.md +33 -2
- package/api.js +11 -5
- package/lib/aggregators/log-aggregator.js +26 -2
- package/lib/collector/api.js +18 -14
- package/lib/collector/remote-method.js +37 -3
- package/lib/instrumentation/nr-winston-transport.js +108 -0
- package/lib/instrumentation/pino/nr-hooks.js +19 -0
- package/lib/instrumentation/pino/pino.js +156 -0
- package/lib/instrumentation/winston.js +24 -96
- package/lib/instrumentations.js +1 -0
- package/lib/metrics/index.js +19 -17
- package/lib/metrics/metric-aggregator.js +15 -11
- package/lib/metrics/names.js +10 -0
- package/lib/stats/index.js +13 -5
- package/package.json +5 -4
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.
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
38
|
-
|
|
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
|
package/lib/collector/api.js
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
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,
|
|
64
|
+
function registerFormatter({ opts, agent, winston }) {
|
|
58
65
|
const instrumentedFormatter = nrWinstonFormatter(agent, winston)
|
|
59
66
|
|
|
60
|
-
if (
|
|
61
|
-
|
|
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
|
|
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 (
|
|
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
|
package/lib/instrumentations.js
CHANGED
|
@@ -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 },
|
package/lib/metrics/index.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
78
|
-
* @param {
|
|
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
|
-
* @
|
|
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 {
|
|
96
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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 {
|
|
94
|
-
* @param {
|
|
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
|
-
* @
|
|
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 {
|
|
108
|
-
* @
|
|
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
|
-
* @
|
|
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)
|
package/lib/metrics/names.js
CHANGED
|
@@ -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,
|
package/lib/stats/index.js
CHANGED
|
@@ -28,8 +28,8 @@ function Stats() {
|
|
|
28
28
|
/**
|
|
29
29
|
* Update the summary statistics with a new value.
|
|
30
30
|
*
|
|
31
|
-
* @param {
|
|
32
|
-
* @param {
|
|
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
|
|
68
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|