newrelic 8.16.0 → 9.0.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.
@@ -504,7 +504,7 @@ This product includes source derived from [@grpc/proto-loader](https://github.co
504
504
 
505
505
  ### @newrelic/aws-sdk
506
506
 
507
- This product includes source derived from [@newrelic/aws-sdk](https://github.com/newrelic/node-newrelic-aws-sdk) ([v4.1.1](https://github.com/newrelic/node-newrelic-aws-sdk/tree/v4.1.1)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-aws-sdk/blob/v4.1.1/LICENSE):
507
+ This product includes source derived from [@newrelic/aws-sdk](https://github.com/newrelic/node-newrelic-aws-sdk) ([v5.0.0](https://github.com/newrelic/node-newrelic-aws-sdk/tree/v5.0.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-aws-sdk/blob/v5.0.0/LICENSE):
508
508
 
509
509
  ```
510
510
  Apache License
@@ -712,7 +712,7 @@ This product includes source derived from [@newrelic/aws-sdk](https://github.com
712
712
 
713
713
  ### @newrelic/koa
714
714
 
715
- This product includes source derived from [@newrelic/koa](https://github.com/newrelic/node-newrelic-koa) ([v6.1.1](https://github.com/newrelic/node-newrelic-koa/tree/v6.1.1)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-koa/blob/v6.1.1/LICENSE):
715
+ This product includes source derived from [@newrelic/koa](https://github.com/newrelic/node-newrelic-koa) ([v7.0.0](https://github.com/newrelic/node-newrelic-koa/tree/v7.0.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-koa/blob/v7.0.0/LICENSE):
716
716
 
717
717
  ```
718
718
  Apache License
@@ -921,7 +921,7 @@ Apache License
921
921
 
922
922
  ### @newrelic/superagent
923
923
 
924
- This product includes source derived from [@newrelic/superagent](https://github.com/newrelic/node-newrelic-superagent) ([v5.1.0](https://github.com/newrelic/node-newrelic-superagent/tree/v5.1.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-superagent/blob/v5.1.0/LICENSE):
924
+ This product includes source derived from [@newrelic/superagent](https://github.com/newrelic/node-newrelic-superagent) ([v6.0.0](https://github.com/newrelic/node-newrelic-superagent/tree/v6.0.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-superagent/blob/v6.0.0/LICENSE):
925
925
 
926
926
  ```
927
927
  Apache License
@@ -1800,7 +1800,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1800
1800
 
1801
1801
  ### @newrelic/test-utilities
1802
1802
 
1803
- 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):
1803
+ This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v7.0.0](https://github.com/newrelic/node-test-utilities/tree/v7.0.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v7.0.0/LICENSE):
1804
1804
 
1805
1805
  ```
1806
1806
  Apache License
@@ -3380,7 +3380,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3380
3380
 
3381
3381
  ### @newrelic/native-metrics
3382
3382
 
3383
- This product includes source derived from [@newrelic/native-metrics](https://github.com/newrelic/node-native-metrics) ([v8.0.0](https://github.com/newrelic/node-native-metrics/tree/v8.0.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-native-metrics/blob/v8.0.0/LICENSE):
3383
+ This product includes source derived from [@newrelic/native-metrics](https://github.com/newrelic/node-native-metrics) ([v9.0.0](https://github.com/newrelic/node-native-metrics/tree/v9.0.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-native-metrics/blob/v9.0.0/LICENSE):
3384
3384
 
3385
3385
  ```
3386
3386
  Apache License
package/index.js CHANGED
@@ -53,8 +53,8 @@ function initialize() {
53
53
  throw new Error(message)
54
54
  }
55
55
 
56
- // TODO: Update this check when Node v18 support is added
57
- if (psemver.satisfies('>=17.0.0')) {
56
+ // TODO: Update this check when Node v20 support is added
57
+ if (psemver.satisfies('>=19.0.0')) {
58
58
  logger.warn(
59
59
  'New Relic for Node.js %s has not been tested on Node.js %s. Please ' +
60
60
  'update the agent or downgrade your version of Node.js',
@@ -8,8 +8,6 @@
8
8
  const parse = require('url').parse
9
9
  const ProxyAgent = require('https-proxy-agent')
10
10
  const logger = require('../logger').child({ component: 'http-agent' })
11
- const certificates = require('./ssl/certificates.js')
12
-
13
11
  const https = require('https')
14
12
 
15
13
  // poor person's single-instance-objects. We
@@ -21,6 +19,8 @@ let agentProxyWithKeepAlive = null
21
19
 
22
20
  /**
23
21
  * Returns an HTTP agent with keep-alive enabled
22
+ *
23
+ * @param config
24
24
  */
25
25
  exports.keepAliveAgent = function keepAliveAgent(config) {
26
26
  config = config ? config : {}
@@ -45,6 +45,8 @@ exports.keepAliveAgent = function keepAliveAgent(config) {
45
45
  * Include keep-alive configuration, but ultimately its up
46
46
  * to the proxy server as to how its connection is made
47
47
  * with New Relic's servers.
48
+ *
49
+ * @param config
48
50
  */
49
51
  exports.proxyAgent = function proxyAgent(config) {
50
52
  if (null !== agentProxyWithKeepAlive) {
@@ -109,25 +111,6 @@ function proxyOptions(config) {
109
111
 
110
112
  if (config.certificates && config.certificates.length > 0) {
111
113
  opts.certificates = config.certificates
112
-
113
- // merge user certificates with built-in certs
114
- if (config.feature_flag.certificate_bundle) {
115
- logger.info(
116
- 'Using a proxy with a special cert. This enables our cert bundle which, combined ' +
117
- 'with some versions of node, exacerbates a leak in node core TLS.'
118
- )
119
-
120
- const certWarningMessage = [
121
- 'Deprecation Warning: The certificate bundle included by New Relic will be ',
122
- 'disabled by default and then fully removed in later major versions. We recommend ',
123
- 'testing with the certificate_bundle feature flag set to `false` to determine if ',
124
- 'you will need to modify your environment or setup your own appropriate bundle. ',
125
- 'Example configuration: feature_flag: { certificate_bundle: false }.'
126
- ].join('')
127
- logger.warnOnce('CERT_WARNING', certWarningMessage)
128
-
129
- opts.certificates = config.certificates.concat(certificates)
130
- }
131
114
  }
132
115
 
133
116
  return opts
@@ -14,7 +14,6 @@ const parse = require('./parse-response')
14
14
  const stringify = require('json-stringify-safe')
15
15
  const Sink = require('../util/stream-sink')
16
16
  const agents = require('./http-agents')
17
- const certificates = require('./ssl/certificates')
18
17
  const isValidLength = require('../util/byte-limit').isValidLength
19
18
  const { DATA_USAGE } = require('../metrics/names')
20
19
 
@@ -306,21 +305,6 @@ RemoteMethod.prototype._request = function _request(options) {
306
305
  } else {
307
306
  if (this._config.certificates && this._config.certificates.length > 0) {
308
307
  requestOptions.ca = this._config.certificates
309
-
310
- if (this._config.feature_flag.certificate_bundle) {
311
- logger.debug('Adding custom certificate to the cert bundle.')
312
-
313
- const certWarningMessage = [
314
- 'Deprecation Warning: The certificate bundle included by New Relic will be ',
315
- 'disabled by default and then fully removed in later major versions. We recommend ',
316
- 'testing with the certificate_bundle feature flag set to `false` to determine if ',
317
- 'you will need to modify your environment or setup your own appropriate bundle. ',
318
- 'Example configuration: feature_flag: { certificate_bundle: false }.'
319
- ].join('')
320
- logger.warnOnce('CERT_WARNING', certWarningMessage)
321
-
322
- requestOptions.ca = this._config.certificates.concat(certificates)
323
- }
324
308
  }
325
309
  request = https.request(requestOptions)
326
310
  }
@@ -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
  */
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',
@@ -858,13 +858,6 @@ Config.prototype.logUnknown = function logUnknown(json, key) {
858
858
  logger.debug('New Relic sent unknown configuration parameter %s with value %s.', key, value)
859
859
  }
860
860
 
861
- /**
862
- * Return the availability of async_hook for use by the agent.
863
- */
864
- Config.prototype.checkAsyncHookStatus = function checkAsyncHookStatus() {
865
- return this.feature_flag.await_support
866
- }
867
-
868
861
  /**
869
862
  * Gets the user set host display name. If not provided, it returns the default value.
870
863
  *
@@ -7,8 +7,6 @@
7
7
 
8
8
  // unreleased flags gating an active feature
9
9
  exports.prerelease = {
10
- await_support: true,
11
- certificate_bundle: false,
12
10
  express5: false,
13
11
  new_promise_tracking: false,
14
12
  promise_segments: false,
@@ -31,7 +29,9 @@ exports.released = [
31
29
  'send_request_uri_attribute',
32
30
  'synthetics',
33
31
  'dt_format_w3c',
34
- 'fastify_instrumentation'
32
+ 'fastify_instrumentation',
33
+ 'await_support',
34
+ 'certificate_bundle'
35
35
  ]
36
36
 
37
37
  // flags that are no longer used for unreleased features
@@ -6,48 +6,11 @@
6
6
  'use strict'
7
7
 
8
8
  const logger = require('../../logger').child({ component: 'async_hooks' })
9
- const promInit = require('../promise')
9
+ const asyncHooks = require('async_hooks')
10
10
 
11
11
  module.exports = initialize
12
12
 
13
- /**
14
- * The spec for the native `Promise` class.
15
- */
16
- const STATIC_PROMISE_METHODS = ['accept', 'all', 'defer', 'race', 'reject', 'resolve']
17
- const NATIVE_PROMISE_SPEC = {
18
- name: 'global',
19
- constructor: 'Promise',
20
- executor: true,
21
- useFinally: false,
22
- $proto: {
23
- then: ['then', 'chain'],
24
- catch: ['catch']
25
- },
26
- $static: {
27
- $copy: STATIC_PROMISE_METHODS,
28
- cast: STATIC_PROMISE_METHODS
29
- }
30
- }
31
-
32
13
  function initialize(agent, shim) {
33
- const enableHooks = agent.config.checkAsyncHookStatus()
34
- if (enableHooks && tryAsyncHooks(agent, shim)) {
35
- logger.debug('Using async_hooks.')
36
- } else {
37
- logger.debug('Using promise instrumentation.')
38
- promInit(agent, global, NATIVE_PROMISE_SPEC)
39
- }
40
- }
41
-
42
- function tryAsyncHooks(agent, shim) {
43
- let asyncHooks = null
44
- try {
45
- asyncHooks = require('async_hooks')
46
- } catch (e) {
47
- logger.info(e, 'Not using async_hooks module.')
48
- return false
49
- }
50
-
51
14
  // this map is reused to track the segment that was active when
52
15
  // the before callback is called to be replaced in the after callback
53
16
  const segmentMap = new Map()
@@ -17,7 +17,7 @@ const NAMES = require('../../metrics/names')
17
17
  const SHIM_SYMBOLS = require('../../shim/constants').SYMBOLS
18
18
 
19
19
  const DEFAULT_HOST = 'localhost'
20
- const DEFAULT_PORT = 80
20
+ const DEFAULT_HTTP_PORT = 80
21
21
  const DEFAULT_SSL_PORT = 443
22
22
 
23
23
  const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
@@ -27,9 +27,8 @@ const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
27
27
  *
28
28
  * @param {Agent} agent
29
29
  * @param {object} opts
30
- * @param {function} makeRequest
31
- *
32
- * @return {http.ClientRequest} The instrumented outbound request.
30
+ * @param {Function} makeRequest
31
+ * @returns {http.ClientRequest} The instrumented outbound request.
33
32
  */
34
33
  module.exports = function instrumentOutbound(agent, opts, makeRequest) {
35
34
  if (typeof opts === 'string') {
@@ -38,10 +37,12 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
38
37
  opts = copy.shallow(opts)
39
38
  }
40
39
 
40
+ const defaultPort =
41
+ !opts.protocol || opts.protocol === 'http:' ? DEFAULT_HTTP_PORT : DEFAULT_SSL_PORT
41
42
  let hostname = opts.hostname || opts.host || DEFAULT_HOST
42
43
  let port = opts.port || opts.defaultPort
43
44
  if (!port) {
44
- port = !opts.protocol || opts.protocol === 'http:' ? DEFAULT_PORT : DEFAULT_SSL_PORT
45
+ port = defaultPort
45
46
  }
46
47
 
47
48
  if (!hostname || port < 1) {
@@ -49,11 +50,7 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
49
50
  return makeRequest(opts)
50
51
  }
51
52
 
52
- // Technically we shouldn't append the port if this is an https request on 443
53
- // but due to legacy issues we can't do that without moving customer's cheese.
54
- //
55
- // TODO: Move customers cheese by not appending the default port for https.
56
- if (port && port !== DEFAULT_PORT) {
53
+ if (port && port !== defaultPort) {
57
54
  hostname += ':' + port
58
55
  }
59
56
 
@@ -159,8 +156,7 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
159
156
  * @param {TraceSegment} segment
160
157
  * @param {http.ClientRequest} req
161
158
  * @param {Error} error
162
- *
163
- * @return {bool} True if the error will be collected by New Relic.
159
+ * @returns {bool} True if the error will be collected by New Relic.
164
160
  */
165
161
  function handleError(segment, req, error) {
166
162
  if (req.listenerCount('error') > 0) {
@@ -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
  }
File without changes
@@ -27,16 +27,7 @@ module.exports = function initialize(agent, hapi, moduleName, shim) {
27
27
  } else if (hapi.Server) {
28
28
  // Server.connection was removed in v17
29
29
  if (!hapi.Server.prototype.connection) {
30
- return require('./hapi/hapi-17')(agent, hapi, moduleName, shim)
31
- }
32
-
33
- // See if we can find the plugin class. This should be the super class of
34
- // Server and will cover more scenarios.
35
- const Plugin = hapi.Server.super_
36
- if (_isPluginClass(Plugin)) {
37
- wrapServer(shim, Plugin)
38
- } else {
39
- wrapServer(shim, hapi.Server)
30
+ return require('./hapi/hapi')(agent, hapi, moduleName, shim)
40
31
  }
41
32
  }
42
33
  }
@@ -251,19 +242,6 @@ function wrapMiddleware(shim, middleware, event) {
251
242
  return shim.recordMiddleware(middleware, spec)
252
243
  }
253
244
 
254
- function _isPluginClass(Plugin) {
255
- if (typeof Plugin !== 'function' || !Plugin.prototype) {
256
- return false
257
- }
258
-
259
- const proto = Plugin.prototype
260
- return (
261
- typeof proto.handler === 'function' &&
262
- typeof proto.route === 'function' &&
263
- typeof proto.ext === 'function'
264
- )
265
- }
266
-
267
245
  function wrapReply(wrap, reply, isPreHandler) {
268
246
  const isFinal = !isPreHandler
269
247
  // The only reply method that is actually used by pre-handlers is `response`. All