newrelic 9.2.0 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NEWS.md CHANGED
@@ -1,3 +1,13 @@
1
+ ### v9.3.0 (2022-10-17)
2
+
3
+ * Added instrumentation to bunyan to support application logging use cases: forwarding, local decorating, and metrics.
4
+
5
+ Big thanks to @brianphillips for his contribution 🚀
6
+
7
+ * Added c8 to track code coverage.
8
+
9
+ * Added documentation about custom instrumentation in ES module applications
10
+
1
11
  ### v9.2.0 (2022-10-06)
2
12
 
3
13
  * Added ability to instrument ES Modules with the New Relic ESM Loader.
package/README.md CHANGED
@@ -84,6 +84,47 @@ $ node --experimental-loader newrelic/esm-loader.mjs your-program.js
84
84
 
85
85
  **Note**: Unlike the CommonJS methods listed above, there are no alternatives to running the agent without the `--experimental-loader` flag.
86
86
 
87
+ ### Custom Instrumentation
88
+
89
+ The agent supports adding your own custom instrumentation to ES module applications. In order to load custom instrumentation in an ES module app, you'll need to update your `newrelic.cjs` file to include the following:
90
+
91
+ ```js
92
+ /* File: newrelic.cjs */
93
+ 'use strict'
94
+ /**
95
+ * New Relic agent configuration.
96
+ *
97
+ * See lib/config/default.js in the agent distribution for a more complete
98
+ * description of configuration variables and their potential values.
99
+ */
100
+ exports.config = {
101
+ app_name: ['Your application or service name'],
102
+ license_key: 'your new relic license key',
103
+ api: {
104
+ esm: {
105
+ custom_instrumentation_entrypoint: '/path/to/my/instrumentation.js'
106
+ }
107
+ }
108
+ /* ... rest of configuration .. */
109
+ }
110
+ ```
111
+
112
+ If you do not use a configuration file, then use the environment variable `NEW_RELIC_API_ESM_CUSTOM_INSTRUMENTATION_ENTRYPOINT` instead.
113
+
114
+ By updating the configuration, the agent's ES module loader will ensure that your custom instrumentation is added at module load. This is required in ES module applications due to the immutability of module export bindings: we are unable to apply our instrumentation after loading is complete.
115
+
116
+ We support the following custom instrumentation API methods in ES module apps:
117
+
118
+ * `newrelic.instrument`
119
+ * `newrelic.instrumentConglomerate`
120
+ * `newrelic.instrumentDatastore`
121
+ * `newrelic.instrumentMessages`
122
+ * `newrelic.instrumentWebframework`
123
+
124
+ Note that we _do not_ support `newrelic.instrumentLoadedModule`, for the same issue of immutability mentioned above.
125
+
126
+ If you want to see an example of how to write custom instrumentation in an ES module app, check out our [examples](https://github.com/newrelic/newrelic-node-examples/tree/main/esm-app) repo for a working demo.
127
+
87
128
  ## Getting Started
88
129
 
89
130
  For more information on getting started, [check the Node.js docs](https://docs.newrelic.com/docs/agents/nodejs-agent/getting-started/introduction-new-relic-nodejs).
@@ -37,6 +37,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
37
37
  * [@slack/bolt](#slackbolt)
38
38
  * [ajv](#ajv)
39
39
  * [async](#async)
40
+ * [c8](#c8)
40
41
  * [chai](#chai)
41
42
  * [commander](#commander)
42
43
  * [eslint-config-prettier](#eslint-config-prettier)
@@ -2091,6 +2092,28 @@ THE SOFTWARE.
2091
2092
 
2092
2093
  ```
2093
2094
 
2095
+ ### c8
2096
+
2097
+ This product includes source derived from [c8](https://github.com/bcoe/c8) ([v7.12.0](https://github.com/bcoe/c8/tree/v7.12.0)), distributed under the [ISC License](https://github.com/bcoe/c8/blob/v7.12.0/LICENSE.txt):
2098
+
2099
+ ```
2100
+ Copyright (c) 2017, Contributors
2101
+
2102
+ Permission to use, copy, modify, and/or distribute this software
2103
+ for any purpose with or without fee is hereby granted, provided
2104
+ that the above copyright notice and this permission notice
2105
+ appear in all copies.
2106
+
2107
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
2108
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
2109
+ OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE
2110
+ LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
2111
+ OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
2112
+ WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
2113
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2114
+
2115
+ ```
2116
+
2094
2117
  ### chai
2095
2118
 
2096
2119
  This product includes source derived from [chai](https://github.com/chaijs/chai) ([v4.3.6](https://github.com/chaijs/chai/tree/v4.3.6)), distributed under the [MIT License](https://github.com/chaijs/chai/blob/v4.3.6/LICENSE):
@@ -0,0 +1,110 @@
1
+ /*
2
+ * Copyright 2022 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const {
9
+ isApplicationLoggingEnabled,
10
+ isLogForwardingEnabled,
11
+ isLocalDecoratingEnabled,
12
+ isMetricsEnabled,
13
+ createModuleUsageMetric,
14
+ incrementLoggingLinesMetrics
15
+ } = require('../util/application-logging')
16
+
17
+ const MAX_LENGTH = 1021
18
+ const OUTPUT_LENGTH = 1024
19
+ const truncate = (str) => {
20
+ if (str && str.length > OUTPUT_LENGTH) {
21
+ return str.substring(0, MAX_LENGTH) + '...'
22
+ }
23
+
24
+ return str
25
+ }
26
+
27
+ const logger = require('../logger').child({ component: 'bunyan' })
28
+
29
+ function augmentLogData(originalLog, agent, nameFromLevel) {
30
+ // shallow copy, since we're modifying things
31
+ const newLog = Object.assign({}, originalLog)
32
+ newLog.timestamp = Date.now()
33
+ newLog.level = nameFromLevel[originalLog.level]
34
+
35
+ // put log message into a consistent spot and ensure it's not too long
36
+ newLog.message = truncate(newLog.msg)
37
+
38
+ // tidy up the error output to help with max length restrictions
39
+ if (newLog.err) {
40
+ newLog['error.message'] = truncate(newLog.err.message)
41
+ newLog['error.stack'] = truncate(newLog.err.stack)
42
+ newLog['error.class'] =
43
+ newLog.err.name === 'Error' ? newLog.err.constructor.name : newLog.err.name
44
+ // clear out the old error message
45
+ delete newLog.err
46
+ }
47
+
48
+ // Add the metadata to the object being logged
49
+ const metadata = agent.getLinkingMetadata(true)
50
+ Object.keys(metadata).forEach((m) => {
51
+ newLog[m] = metadata[m]
52
+ })
53
+
54
+ return newLog
55
+ }
56
+
57
+ function createLoggerWrapper(shim, fn, fnName, bunyanLogger, nameFromLevel) {
58
+ const agent = shim.agent
59
+
60
+ createModuleUsageMetric('bunyan', agent.metrics)
61
+
62
+ // forward logs via the agent logs aggregator
63
+ bunyanLogger.addStream({
64
+ name: 'NRLogForwarder',
65
+ type: 'raw',
66
+ level: bunyanLogger.level(),
67
+ stream: {
68
+ write: function nrLogWrite(logLine) {
69
+ agent.logs.add(augmentLogData(logLine, agent, nameFromLevel))
70
+ }
71
+ }
72
+ })
73
+ // no return here means the original return value is preserved
74
+ }
75
+
76
+ module.exports = function instrument(agent, bunyan, _, shim) {
77
+ const config = agent.config
78
+
79
+ if (!isApplicationLoggingEnabled(config)) {
80
+ logger.debug('Application logging not enabled. Not instrumenting bunyan.')
81
+ return
82
+ }
83
+
84
+ const logForwardingEnabled = isLogForwardingEnabled(config, agent)
85
+ const localDecoratingEnabled = isLocalDecoratingEnabled(config)
86
+ const metricsEnabled = isMetricsEnabled(config)
87
+
88
+ if (logForwardingEnabled) {
89
+ shim.wrapReturn(bunyan, 'createLogger', createLoggerWrapper, [bunyan.nameFromLevel])
90
+ }
91
+
92
+ if (metricsEnabled || localDecoratingEnabled) {
93
+ shim.wrap(bunyan.prototype, '_emit', function wrapEmit(_shim, emit) {
94
+ return function wrappedEmit() {
95
+ const args = shim.argsToArray.apply(shim, arguments)
96
+ const rec = args[0]
97
+
98
+ if (metricsEnabled) {
99
+ incrementLoggingLinesMetrics(bunyan.nameFromLevel[rec.level], agent.metrics)
100
+ }
101
+
102
+ if (localDecoratingEnabled) {
103
+ rec.message = truncate(rec.msg) + agent.getNRLinkingMetadata()
104
+ }
105
+ args[0] = rec
106
+ return emit.apply(this, args)
107
+ }
108
+ })
109
+ }
110
+ }
@@ -15,6 +15,7 @@ module.exports = function instrumentations() {
15
15
  'cassandra-driver': { type: MODULE_TYPE.DATASTORE },
16
16
  'connect': { type: MODULE_TYPE.WEB_FRAMEWORK },
17
17
  'bluebird': { type: MODULE_TYPE.PROMISE },
18
+ 'bunyan': { type: MODULE_TYPE.GENERIC },
18
19
  'director': { type: MODULE_TYPE.WEB_FRAMEWORK },
19
20
  'express': { type: MODULE_TYPE.WEB_FRAMEWORK },
20
21
  'fastify': { type: MODULE_TYPE.WEB_FRAMEWORK },
@@ -277,11 +277,13 @@ const LOGGING = {
277
277
  INFO: `${LOGGING_LINES_PREFIX}/INFO`,
278
278
  WARN: `${LOGGING_LINES_PREFIX}/WARN`,
279
279
  ERROR: `${LOGGING_LINES_PREFIX}/ERROR`,
280
+ FATAL: `${LOGGING_LINES_PREFIX}/FATAL`,
280
281
  DEBUG: `${LOGGING_LINES_PREFIX}/DEBUG`,
281
282
  TRACE: `${LOGGING_LINES_PREFIX}/TRACE`,
282
283
  UNKNOWN: `${LOGGING_LINES_PREFIX}/UNKNOWN`
283
284
  },
284
285
  LIBS: {
286
+ BUNYAN: `${SUPPORTABILITY.LOGGING}/${NODEJS.PREFIX}bunyan/enabled`,
285
287
  PINO: `${SUPPORTABILITY.LOGGING}/${NODEJS.PREFIX}pino/enabled`,
286
288
  WINSTON: `${SUPPORTABILITY.LOGGING}/${NODEJS.PREFIX}winston/enabled`
287
289
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "9.2.0",
3
+ "version": "9.3.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -146,7 +146,7 @@
146
146
  "bench": "node ./bin/run-bench.js",
147
147
  "docker-env": "./bin/docker-env-vars.sh",
148
148
  "docs": "npm ci && jsdoc -c ./jsdoc-conf.json --private -r .",
149
- "integration": "npm run prepare-test && npm run sub-install && time tap test/integration/**/**/*.tap.js --timeout=180 --no-coverage --reporter classic",
149
+ "integration": "npm run prepare-test && npm run sub-install && time c8 -o ./coverage/integration tap test/integration/**/**/*.tap.js --timeout=180 --no-coverage --reporter classic",
150
150
  "prepare-test": "npm run ssl && npm run docker-env",
151
151
  "lint": "eslint ./*.{js,mjs} lib test bin examples",
152
152
  "lint:fix": "eslint --fix, ./*.{js,mjs} lib test bin examples",
@@ -158,8 +158,8 @@
158
158
  "sub-install": "node test/bin/install_sub_deps",
159
159
  "test": "npm run integration && npm run unit && npm run unit:esm",
160
160
  "third-party-updates": "oss third-party manifest --includeOptDeps && oss third-party notices --includeOptDeps && git add THIRD_PARTY_NOTICES.md third_party_manifest.json",
161
- "unit": "rm -f newrelic_agent.log && time tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.js)$' --timeout=180 --no-coverage --reporter classic",
162
- "unit:esm": "rm -f newrelic_agent.log && time tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.mjs)$' --timeout=180 --no-coverage --reporter classic --node-arg=--experimental-loader=testdouble",
161
+ "unit": "rm -f newrelic_agent.log && time c8 -o ./coverage/unit tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.js)$' --timeout=180 --no-coverage --reporter classic",
162
+ "unit:esm": "rm -f newrelic_agent.log && time c8 -o ./coverage/esm-unit --include *.mjs --include **/*.mjs --exclude false tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.mjs)$' --timeout=180 --no-coverage --reporter classic --node-arg=--experimental-loader=testdouble",
163
163
  "update-cross-agent-tests": "./bin/update-cats.sh",
164
164
  "versioned-tests": "./bin/run-versioned-tests.sh",
165
165
  "update-changelog-version": "node ./bin/update-changelog-version",
@@ -199,6 +199,7 @@
199
199
  "@slack/bolt": "^3.7.0",
200
200
  "ajv": "^6.12.6",
201
201
  "async": "^3.2.4",
202
+ "c8": "^7.12.0",
202
203
  "chai": "^4.1.2",
203
204
  "commander": "^7.0.0",
204
205
  "eslint": "^8.24.0",