newrelic 8.15.0 → 8.17.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,22 @@
1
+ ### v8.17.1 (2022-08-02)
2
+ * Fixed issue where instrumetned code invoked within a @grpc/grpc-js client callback would not get tracked by the agent.
3
+
4
+ Bound the external client segment to the onReceiveStatus listener to propagate transaction context to the grpc client callbacks.
5
+
6
+ * Fixed issue with truncate in `lib/util/application-logging.js`. It now checks that the argument is a string before checking its length.
7
+
8
+ ### v8.17.0 (2022-07-27)
9
+
10
+ * Added instrumentation for `grpc-js` server unary, client-streaming, server-streaming and bidirectional streaming handlers.
11
+
12
+ ### v8.16.0 (2022-07-21)
13
+
14
+ * Automatic application log forwarding is now enabled by default. This version of the agent will automatically send enriched application logs to New Relic. To learn more about about this feature, see the [APM logs in context documentation](https://docs.newrelic.com/docs/apm/new-relic-apm/getting-started/get-started-logs-context/). For additional configuration options, see the [Node.js logs in context documentation](https://docs.newrelic.com/docs/logs/logs-context/configure-logs-context-nodejs). To learn about how to toggle log ingestion on or off by account, see our documentation to [disable automatic logging](https://docs.newrelic.com/docs/logs/logs-context/disable-automatic-logging) via the UI or API.
15
+
16
+ * Added a support statement to our release notes
17
+
18
+ * Added node 18 to CI workflows.
19
+
1
20
  ### v8.15.0 (2022-07-07)
2
21
 
3
22
  * Added instrumentation for grpc-js unary, streaming, and bidirectional client calls.
@@ -83,7 +102,7 @@
83
102
  * Added sent, seen and dropped metrics that collected on every harvest cycle around log lines.
84
103
 
85
104
  * Added supportability metrics for some popular logging frameworks.
86
-
105
+
87
106
  * Added supportability metrics to record if the logging features are enabled.
88
107
 
89
108
  * Added a storage mechanisms to transactions to keep logs until transaction ends.
@@ -148,7 +167,7 @@
148
167
 
149
168
  * Removed upper-bound testing from restify versioned tests so future major versions will be covered.
150
169
 
151
- * Removed upper-bound testing from mysql2 versioned tests to cover existing and future major versions.
170
+ * Removed upper-bound testing from mysql2 versioned tests to cover existing and future major versions.
152
171
 
153
172
  Continues to skip version 1.6.2 which had a bug that broke tests which was resolved in 1.6.3.
154
173
 
@@ -26,7 +26,6 @@ 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)
30
29
 
31
30
  **[devDependencies](#devDependencies)**
32
31
 
@@ -66,7 +65,6 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
66
65
  * [q](#q)
67
66
  * [redis](#redis)
68
67
  * [request](#request)
69
- * [restify](#restify)
70
68
  * [rimraf](#rimraf)
71
69
  * [should](#should)
72
70
  * [sinon](#sinon)
@@ -296,7 +294,7 @@ This product includes source derived from [@grpc/grpc-js](https://github.com/grp
296
294
 
297
295
  ### @grpc/proto-loader
298
296
 
299
- This product includes source derived from [@grpc/proto-loader](https://github.com/grpc/grpc-node) ([v0.6.13](https://github.com/grpc/grpc-node/tree/v0.6.13)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/blob/v0.6.13/LICENSE):
297
+ 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):
300
298
 
301
299
  ```
302
300
  Apache License
@@ -1331,36 +1329,6 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1331
1329
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
1332
1330
  IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1333
1331
 
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
-
1364
1332
  ```
1365
1333
 
1366
1334
 
@@ -1801,7 +1769,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1801
1769
 
1802
1770
  ### @newrelic/test-utilities
1803
1771
 
1804
- This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v6.5.5](https://github.com/newrelic/node-test-utilities/tree/v6.5.5)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v6.5.5/LICENSE):
1772
+ 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):
1805
1773
 
1806
1774
  ```
1807
1775
  Apache License
@@ -3208,33 +3176,6 @@ If the Work includes a "NOTICE" text file as part of its distribution, then any
3208
3176
  END OF TERMS AND CONDITIONS
3209
3177
  ```
3210
3178
 
3211
- ### restify
3212
-
3213
- This product includes source derived from [restify](https://github.com/restify/node-restify) ([v8.5.1](https://github.com/restify/node-restify/tree/v8.5.1)), distributed under the [MIT License](https://github.com/restify/node-restify/blob/v8.5.1/LICENSE):
3214
-
3215
- ```
3216
- Copyright (c) 2011 Mark Cavage, All rights reserved.
3217
-
3218
- Permission is hereby granted, free of charge, to any person obtaining a copy
3219
- of this software and associated documentation files (the "Software"), to deal
3220
- in the Software without restriction, including without limitation the rights
3221
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3222
- copies of the Software, and to permit persons to whom the Software is
3223
- furnished to do so, subject to the following conditions:
3224
-
3225
- The above copyright notice and this permission notice shall be included in
3226
- all copies or substantial portions of the Software.
3227
-
3228
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3229
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3230
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3231
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3232
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3233
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
3234
- THE SOFTWARE
3235
-
3236
- ```
3237
-
3238
3179
  ### rimraf
3239
3180
 
3240
3181
  This product includes source derived from [rimraf](https://github.com/isaacs/rimraf) ([v2.7.1](https://github.com/isaacs/rimraf/tree/v2.7.1)), distributed under the [ISC License](https://github.com/isaacs/rimraf/blob/v2.7.1/LICENSE):
@@ -3323,12 +3264,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3323
3264
 
3324
3265
  ### tap
3325
3266
 
3326
- This product includes source derived from [tap](https://github.com/tapjs/node-tap) ([v16.0.1](https://github.com/tapjs/node-tap/tree/v16.0.1)), distributed under the [ISC License](https://github.com/tapjs/node-tap/blob/v16.0.1/LICENSE):
3267
+ This product includes source derived from [tap](https://github.com/tapjs/node-tap) ([v15.1.6](https://github.com/tapjs/node-tap/tree/v15.1.6)), distributed under the [ISC License](https://github.com/tapjs/node-tap/blob/v15.1.6/LICENSE):
3327
3268
 
3328
3269
  ```
3329
3270
  The ISC License
3330
3271
 
3331
- Copyright (c) 2011-2022 Isaac Z. Schlueter and Contributors
3272
+ Copyright (c) Isaac Z. Schlueter and Contributors
3332
3273
 
3333
3274
  Permission to use, copy, modify, and/or distribute this software for any
3334
3275
  purpose with or without fee is hereby granted, provided that the above
@@ -770,6 +770,18 @@ exports.config = () => ({
770
770
  database_name_reporting: { enabled: true }
771
771
  },
772
772
 
773
+ /**
774
+ * Controls behavior of gRPC server instrumentation.
775
+ */
776
+ grpc: {
777
+ /**
778
+ * Enables recording of non-zero gRPC status codes. Default is `true`.
779
+ *
780
+ * @env NEW_RELIC_GRPC_RECORD_ERRORS
781
+ */
782
+ record_errors: true
783
+ },
784
+
773
785
  /**
774
786
  * Controls the behavior of span events produced by the agent.
775
787
  */
@@ -950,7 +962,7 @@ exports.config = () => ({
950
962
  *
951
963
  * @env NEW_RELIC_APPLICATION_LOGGING_FORWARDING_ENABLED
952
964
  */
953
- enabled: false,
965
+ enabled: true,
954
966
  /**
955
967
  * Number of log records to send per minute to New Relic.
956
968
  *
package/lib/config/env.js CHANGED
@@ -123,6 +123,9 @@ const ENV_MAPPING = {
123
123
  enabled: 'NEW_RELIC_DATASTORE_DATABASE_NAME_REPORTING_ENABLED'
124
124
  }
125
125
  },
126
+ grpc: {
127
+ record_errors: 'NEW_RELIC_GRPC_RECORD_ERRORS'
128
+ },
126
129
  span_events: {
127
130
  enabled: 'NEW_RELIC_SPAN_EVENTS_ENABLED',
128
131
  attributes: {
@@ -240,6 +243,7 @@ const BOOLEAN_VARS = new Set([
240
243
  'NEW_RELIC_BROWSER_MONITOR_DEBUG',
241
244
  'NEW_RELIC_HIGH_SECURITY',
242
245
  'NEW_RELIC_SLOW_SQL_ENABLED',
246
+ 'NEW_RELIC_GRPC_RECORD_ERRORS',
243
247
  'NEW_RELIC_SPAN_EVENTS_ENABLED',
244
248
  'NEW_RELIC_SPAN_EVENTS_ATTRIBUTES_ENABLED',
245
249
  'NEW_RELIC_DISTRIBUTED_TRACING_ENABLED',
@@ -6,80 +6,217 @@
6
6
  'use strict'
7
7
 
8
8
  const recordExternal = require('../../metrics/recorders/http_external')
9
+ const recordHttp = require('../../metrics/recorders/http')
10
+ const { DESTINATIONS } = require('../../config/attribute-filter')
11
+ const DESTINATION = DESTINATIONS.TRANS_EVENT | DESTINATIONS.ERROR_EVENT
12
+ const semver = require('semver')
9
13
 
10
14
  module.exports = function instrument(shim) {
11
- const callStream = shim.require('./build/src/call-stream')
12
- shim.wrap(callStream.Http2CallStream.prototype, 'start', (shim, original) => {
13
- return function wrappedStart() {
14
- const activeSegment = shim.getActiveSegment()
15
- if (!activeSegment) {
16
- return original.apply(this, arguments)
15
+ const genericShim = shim.makeSpecializedShim(shim.GENERIC, 'call-stream')
16
+ const callStream = genericShim.require('./build/src/call-stream')
17
+ genericShim.wrap(callStream.Http2CallStream.prototype, 'start', wrapStart)
18
+
19
+ const webFrameworkShim = shim.makeSpecializedShim(shim.WEB_FRAMEWORK, 'server')
20
+ const server = webFrameworkShim.require('./build/src/server')
21
+ webFrameworkShim.setFramework('gRPC')
22
+ webFrameworkShim.wrap(server.Server.prototype, 'register', wrapRegister)
23
+ }
24
+
25
+ /**
26
+ * Instruments grpc-js client by intercepting the function that
27
+ * initiates client requests. This handles all four types of client
28
+ * invocations: unary, client-streaming, server-streaming, and
29
+ * bidirectional streaming.
30
+ *
31
+ * @param {object} shim the generic shim to instrument with
32
+ * @param {Function} original the original function
33
+ * @returns {Function} the instrumented function
34
+ */
35
+ function wrapStart(shim, original) {
36
+ return function wrappedStart() {
37
+ const activeSegment = shim.getActiveSegment()
38
+ if (!activeSegment) {
39
+ return original.apply(this, arguments)
40
+ }
41
+
42
+ const channel = this.channel
43
+ const authorityName = (channel.target && channel.target.path) || channel.getDefaultAuthority
44
+
45
+ const segment = shim.createSegment({
46
+ name: `External/${authorityName}${this.methodName}`,
47
+ opaque: true,
48
+ recorder: recordExternal(authorityName, 'gRPC')
49
+ })
50
+
51
+ return shim.applySegment(callStart, segment, true, this, arguments)
52
+
53
+ function callStart() {
54
+ const args = shim.argsToArray.apply(shim, arguments)
55
+
56
+ const transaction = segment.transaction
57
+
58
+ const originalMetadata = args[0]
59
+ const nrMetadata = originalMetadata.clone()
60
+
61
+ const outboundAgentHeaders = Object.create(null)
62
+ if (shim.agent.config.distributed_tracing.enabled) {
63
+ transaction.insertDistributedTraceHeaders(outboundAgentHeaders)
64
+ Object.keys(outboundAgentHeaders).forEach((key) => {
65
+ nrMetadata.add(key, outboundAgentHeaders[key])
66
+ })
67
+ } else {
68
+ shim.logger.debug('Distributed tracing disabled by instrumentation.')
17
69
  }
18
70
 
19
- const channel = this.channel
20
- const authorityName = (channel.target && channel.target.path) || channel.getDefaultAuthority
71
+ args[0] = nrMetadata
21
72
 
22
- const segment = shim.createSegment({
23
- name: `External/${authorityName}${this.methodName}`,
24
- opaque: true,
25
- recorder: recordExternal(authorityName, 'gRPC')
26
- })
73
+ const originalListener = args[1]
74
+ const nrListener = Object.assign({}, originalListener)
75
+ nrListener.onReceiveStatus = (status) => {
76
+ const { code, details } = status
77
+
78
+ segment.addAttribute('grpc.statusCode', code)
79
+ segment.addAttribute('grpc.statusText', details)
80
+
81
+ // Java captures client errors based on status code
82
+ // but has specific gRPC configuration to turn off
83
+ if (code !== 0) {
84
+ // this is currently just creating an error from the details string
85
+ shim.agent.errors.add(segment.transaction, details)
86
+ }
27
87
 
28
- return shim.applySegment(callStart, segment, true, this, arguments)
88
+ segment.addAttribute('component', 'gRPC')
29
89
 
30
- function callStart() {
31
- const args = shim.argsToArray.apply(shim, arguments)
90
+ const protocol = 'grpc'
32
91
 
33
- const transaction = segment.transaction
92
+ const url = `${protocol}://${authorityName}${this.methodName}`
34
93
 
35
- const originalMetadata = args[0]
36
- const nrMetadata = originalMetadata.clone()
94
+ segment.addAttribute('http.url', url)
95
+ segment.addAttribute('http.method', this.methodName)
37
96
 
38
- const outboundAgentHeaders = Object.create(null)
39
- if (shim.agent.config.distributed_tracing.enabled) {
40
- transaction.insertDistributedTraceHeaders(outboundAgentHeaders)
41
- Object.keys(outboundAgentHeaders).forEach((key) => {
42
- nrMetadata.add(key, outboundAgentHeaders[key])
43
- })
44
- } else {
45
- shim.logger.debug('Distributed tracing disabled by instrumentation.')
97
+ if (originalListener && originalListener.onReceiveStatus) {
98
+ const onReceiveStatuts = shim.bindSegment(originalListener.onReceiveStatus, segment)
99
+ onReceiveStatuts(status)
46
100
  }
101
+ segment.end()
102
+ }
47
103
 
48
- args[0] = nrMetadata
104
+ args[1] = nrListener
49
105
 
50
- const originalListener = args[1]
51
- const nrListener = Object.assign({}, originalListener)
52
- nrListener.onReceiveStatus = (status) => {
53
- const { code, details } = status
106
+ return original.apply(this, args)
107
+ }
108
+ }
109
+ }
54
110
 
55
- segment.addAttribute('grpc.statusCode', code)
56
- segment.addAttribute('grpc.statusText', details)
111
+ /**
112
+ * Instruments the grpc-js server by intercepting the moment when
113
+ * server methods are registered from the method implementations
114
+ * provided to grpc-js. This handles all four types of server
115
+ * invocations: unary, client-streaming, server-streaming, and
116
+ * bidirectional streaming.
117
+ *
118
+ * @param {object} shim the web shim to instrument with
119
+ * @param {Function} original the original function
120
+ * @returns {Function} the instrumented function
121
+ */
122
+ function wrapRegister(shim, original) {
123
+ const constants = shim.require('./build/src/constants')
57
124
 
58
- // Java captures client errors based on status code
59
- // but has specific gRPC configuration to turn off
60
- if (code !== 0) {
61
- // this is currently just creating an error from the details string
62
- shim.agent.errors.add(segment.transaction, details)
63
- }
125
+ return function wrappedRegister() {
126
+ const args = shim.argsToArray.apply(shim, arguments)
64
127
 
65
- segment.addAttribute('component', 'gRPC')
128
+ const name = args[0]
129
+ const handler = args[1]
130
+ const type = args[args.length - 1]
66
131
 
67
- const protocol = 'grpc'
132
+ if (this.handlers.has(name)) {
133
+ shim.logger.debug(
134
+ `Not re-instrumenting gRPC method handler for ${name}: it is already registered in the server.`
135
+ )
136
+ return original.apply(this, arguments)
137
+ }
68
138
 
69
- const url = `${protocol}://${authorityName}${this.methodName}`
139
+ args[1] = shim.bindCreateTransaction(instrumentedHandler, { type: shim.WEB })
70
140
 
71
- segment.addAttribute('http.url', url)
72
- segment.addAttribute('http.method', this.methodName)
141
+ return original.apply(this, args)
73
142
 
74
- segment.end()
143
+ function instrumentedHandler(stream) {
144
+ const { transaction, segment } = createTransaction()
145
+ acceptDTHeaders(stream, transaction)
146
+ instrumentEventListeners(stream, transaction)
147
+ return shim.applySegment(handler, segment, true, this, arguments)
148
+ }
75
149
 
76
- originalListener && originalListener.onReceiveStatus(status)
77
- }
150
+ function createTransaction() {
151
+ const parent = shim.getActiveSegment()
152
+ const transaction = parent.transaction
78
153
 
79
- args[1] = nrListener
154
+ // Create the transaction segment using the request URL for now. Once a
155
+ // better name can be determined this segment will be renamed to that.
156
+ const segment = shim.createSegment(name, recordHttp)
157
+ segment.start()
80
158
 
81
- return original.apply(this, args)
82
- }
159
+ transaction.type = 'web'
160
+ transaction.baseSegment = segment
161
+ transaction.url = name
162
+
163
+ transaction.trace.attributes.addAttribute(DESTINATION, 'request.method', transaction.url)
164
+
165
+ transaction.trace.attributes.addAttribute(DESTINATION, 'request.uri', transaction.url)
166
+
167
+ shim.setTransactionUri(transaction.url)
168
+
169
+ // This attribute isn't required by the spec, but it seems useful to have
170
+ transaction.trace.attributes.addAttribute(DESTINATION, 'grpc.type', type)
171
+ return { transaction, segment }
83
172
  }
84
- })
173
+
174
+ function acceptDTHeaders(stream, transaction) {
175
+ const metadata = stream.metadata
176
+ Object.entries(metadata.getMap()).forEach(([key, value]) => {
177
+ transaction.trace.attributes.addAttribute(DESTINATION, `request.headers.${key}`, value)
178
+ })
179
+
180
+ const headers = Object.create(null)
181
+ headers.tracestate = metadata.get('tracestate').join(',')
182
+ headers.traceparent = metadata.get('traceparent').join(',')
183
+ headers.newrelic = metadata.get('newrelic').join(',')
184
+ transaction.acceptDistributedTraceHeaders('HTTP', headers)
185
+ }
186
+
187
+ function instrumentEventListeners(stream, transaction) {
188
+ const agent = shim.agent
189
+ stream.call.once('callEnd', (statusCode) => {
190
+ transaction.trace.attributes.addAttribute(DESTINATION, 'response.status', statusCode)
191
+ if (statusCode !== 0 && agent.config.grpc.record_errors) {
192
+ const status = constants.Status[statusCode]
193
+ const error = new Error(`gRPC status code ${statusCode}: ${status}`)
194
+ agent.errors.add(transaction, error)
195
+ }
196
+ })
197
+ stream.call.once('streamEnd', () => {
198
+ transaction.end()
199
+ })
200
+ // TODO should also instrument the 'data' event on the stream
201
+ // object, as that can ensue in lots of processing when the
202
+ // client is streaming. https://issues.newrelic.com/browse/NEWRELIC-1460
203
+ }
204
+ }
205
+ }
206
+
207
+ module.exports = function instrument(shim) {
208
+ const genericShim = shim.makeSpecializedShim(shim.GENERIC, 'call-stream')
209
+ const callStream = genericShim.require('./build/src/call-stream')
210
+ genericShim.wrap(callStream.Http2CallStream.prototype, 'start', wrapStart)
211
+
212
+ const webFrameworkShim = shim.makeSpecializedShim(shim.WEB_FRAMEWORK, 'server')
213
+ const grpcVersion = shim.require('./package.json').version
214
+ if (semver.lt(grpcVersion, '1.4.0')) {
215
+ shim.logger.debug('gRPC server-side instrumentation only supported on grpc-js >=1.4.0')
216
+ return
217
+ }
218
+
219
+ const server = webFrameworkShim.require('./build/src/server')
220
+ webFrameworkShim.setFramework('gRPC')
221
+ webFrameworkShim.wrap(server.Server.prototype, 'register', wrapRegister)
85
222
  }
@@ -12,7 +12,7 @@ const grpc = require('./grpc')
12
12
  */
13
13
  module.exports = [
14
14
  {
15
- type: 'generic',
15
+ type: 'conglomerate', // generic shim for client, web framework shim for server
16
16
  moduleName: '@grpc/grpc-js',
17
17
  onResolved: grpc
18
18
  }
@@ -127,13 +127,22 @@ common.makeBulkDescFunc = function makeBulkDescFunc(shim) {
127
127
  *
128
128
  * @param {Shim} shim
129
129
  * @param {Instrumentation} instrumenter instance of mongo APM class
130
+ * @param {object} [options={}] provide command names to skip updating host/port as they are unrelated to the active query. This is only in v3 because after every command is runs `endSessions` which runs on the admin database
130
131
  */
131
- common.captureAttributesOnStarted = function captureAttributesOnStarted(shim, instrumenter) {
132
+ common.captureAttributesOnStarted = function captureAttributesOnStarted(
133
+ shim,
134
+ instrumenter,
135
+ options = { skipCommands: [] }
136
+ ) {
132
137
  instrumenter.on('started', function onMongoEventStarted(evnt) {
138
+ if (options.skipCommands.includes(evnt.commandName)) {
139
+ return
140
+ }
133
141
  // This assumes that this `started` event is fired _after_ our wrapper
134
142
  // starts and creates the segment. We perform a check of the segment name
135
143
  // out of an excess of caution.
136
144
  const connId = evnt.connectionId
145
+
137
146
  if (connId) {
138
147
  // used in v3 when connection is a cluster pool
139
148
  if (typeof connId === 'number') {
@@ -165,7 +174,7 @@ common.captureAttributesOnStarted = function captureAttributesOnStarted(shim, in
165
174
  * @param {object} client mongo client instance
166
175
  */
167
176
  function setHostPort(shim, connStr, db, client) {
168
- const parts = connStr.split(':')
177
+ const parts = common.parseAddress(connStr)
169
178
  // in v3 when running with a cluster of socket connections
170
179
  // the address is `undefined:undefined`. we will instead attempt
171
180
  // to get connection details from the client symbol NR_ATTRS
@@ -189,7 +198,7 @@ function setHostPort(shim, connStr, db, client) {
189
198
 
190
199
  /**
191
200
  * Get the database_name, host, port_path_or_id
192
- * for the query segment. v4 refactored where the toplogy is stored.
201
+ * for the query segment. v4 refactored where the topology is stored.
193
202
  * You can now get the details via the client obj that's deeply nested
194
203
  * See: https://github.com/mongodb/node-mongodb-native/pull/2594/files#diff-1d214e57ddda9095d296e5700ebce701333bfefcf417e234c584d14091b2f50dR168
195
204
  *
@@ -202,8 +211,8 @@ function getInstanceAttributeParameters(shim, obj) {
202
211
  const databaseName =
203
212
  (obj.s.db && obj.s.db.databaseName) || (obj.s.namespace && obj.s.namespace.db) || null
204
213
  const topology = obj.s.topology
205
- if (topology.s && topology.s.options) {
206
- return doCapture(topology.s.options, databaseName)
214
+ if (topology) {
215
+ return doCapture(topology, databaseName)
207
216
  }
208
217
  } else if (
209
218
  obj.s &&
@@ -241,13 +250,13 @@ function getInstanceAttributeParameters(shim, obj) {
241
250
 
242
251
  function doCapture(conf, database) {
243
252
  // in older versions of 3.x the host/port
244
- // lived directly on the topology.s.options
253
+ // lived directly on the topology
245
254
  let host = conf.host
246
255
  let port = conf.port
247
256
 
248
257
  // servers is an array but we will always pull the first for consistency
249
- if (conf.servers && conf.servers.length) {
250
- ;[{ host, port }] = conf.servers
258
+ if (conf.s && conf.s.options && conf.s.options.servers && conf.s.options.servers.length) {
259
+ ;[{ host, port }] = conf.s.options.servers
251
260
  }
252
261
 
253
262
  // host is a domain socket. set host as localhost and use the domain
@@ -264,3 +273,16 @@ function getInstanceAttributeParameters(shim, obj) {
264
273
  }
265
274
  }
266
275
  }
276
+
277
+ /**
278
+ * Parses mongo address that accounts for IPv6
279
+ *
280
+ * @param {string} address mongo address string
281
+ * @returns {Array} host/port of address string
282
+ */
283
+ common.parseAddress = function parseAddress(address) {
284
+ const lastColon = address.lastIndexOf(':')
285
+ const host = address.slice(0, lastColon)
286
+ const port = address.slice(lastColon + 1)
287
+ return [host, port]
288
+ }
@@ -18,7 +18,7 @@ const {
18
18
  * parser used to grab the collection and operation
19
19
  * on every mongo operation
20
20
  *
21
- * @param {Object} operation
21
+ * @param {object} operation
22
22
  */
23
23
  function queryParser(operation) {
24
24
  let collection = this.collectionName || 'unknown'
@@ -40,7 +40,7 @@ function queryParser(operation) {
40
40
  * is a cluster of domain sockets
41
41
  *
42
42
  * @param {Shim} shim
43
- * @param {Object} mongodb resolved package
43
+ * @param {object} mongodb resolved package
44
44
  */
45
45
  function instrumentClient(shim, mongodb) {
46
46
  shim.recordOperation(mongodb.MongoClient, 'connect', function wrappedConnect(shim, _, __, args) {
@@ -61,13 +61,16 @@ function instrumentClient(shim, mongodb) {
61
61
  * add necessary attributes to segments
62
62
  *
63
63
  * @param {Shim} shim
64
- * @param {Object} mongodb resolved package
64
+ * @param {object} mongodb resolved package
65
65
  */
66
66
  module.exports = function instrument(shim, mongodb) {
67
67
  shim.setParser(queryParser)
68
68
  instrumentClient(shim, mongodb)
69
69
  const instrumenter = mongodb.instrument(Object.create(null), () => {})
70
- captureAttributesOnStarted(shim, instrumenter)
70
+ // in v3 of mongo endSessions fires after every command and it updates the active segment
71
+ // attributes with the admin database name which stomps on the database name where the original
72
+ // command runs on
73
+ captureAttributesOnStarted(shim, instrumenter, { skipCommands: ['endSessions'] })
71
74
  instrumentCursor(shim, mongodb.Cursor)
72
75
  instrumentCursor(shim, shim.require('./lib/aggregation_cursor'))
73
76
  instrumentCursor(shim, shim.require('./lib/command_cursor'))
@@ -5,13 +5,13 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const { instrumentCollection, instrumentCursor, instrumentDb } = require('./common')
8
+ const { instrumentCollection, instrumentCursor, instrumentDb, parseAddress } = require('./common')
9
9
 
10
10
  /**
11
11
  * parser used to grab the collection and operation
12
12
  * from a running query
13
13
  *
14
- * @param {Object} operation
14
+ * @param {object} operation
15
15
  */
16
16
  function queryParser(operation) {
17
17
  let collection = this.collectionName || 'unknown'
@@ -34,7 +34,7 @@ function queryParser(operation) {
34
34
  */
35
35
  function cmdStartedHandler(shim, evnt) {
36
36
  if (evnt.connectionId) {
37
- let [host, port] = evnt.address.split(':')
37
+ let [host, port] = parseAddress(evnt.address)
38
38
 
39
39
  // connection via socket get port from 1st host
40
40
  // socketPath property
@@ -45,7 +45,7 @@ function cmdStartedHandler(shim, evnt) {
45
45
  if (hosts && hosts.length && hosts[0].socketPath) {
46
46
  port = hosts[0].socketPath
47
47
  }
48
- } else if (host === '127.0.0.1') {
48
+ } else if (['127.0.0.1', '::1'].includes(host)) {
49
49
  host = 'localhost'
50
50
  }
51
51
 
@@ -16,7 +16,7 @@ const { LOGGING } = require('../metrics/names')
16
16
  * @returns {string}
17
17
  */
18
18
  utils.truncate = function truncate(str) {
19
- if (str.length > OUTPUT_LENGTH) {
19
+ if (typeof str === 'string' && str.length > OUTPUT_LENGTH) {
20
20
  return str.substring(0, MAX_LENGTH) + '...'
21
21
  }
22
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "8.15.0",
3
+ "version": "8.17.1",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -213,7 +213,6 @@
213
213
  "q": "*",
214
214
  "redis": "^1.0.0",
215
215
  "request": "^2.88.0",
216
- "restify": "^8.4.0",
217
216
  "rimraf": "^2.6.3",
218
217
  "should": "*",
219
218
  "sinon": "^4.5.0",