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.
- package/NEWS.md +5416 -5363
- package/THIRD_PARTY_NOTICES.md +5 -5
- package/index.js +2 -2
- package/lib/collector/http-agents.js +4 -21
- package/lib/collector/remote-method.js +0 -16
- package/lib/config/default.js +12 -0
- package/lib/config/env.js +4 -0
- package/lib/config/index.js +0 -7
- package/lib/feature_flags.js +3 -3
- package/lib/instrumentation/core/async_hooks.js +1 -38
- package/lib/instrumentation/core/http-outbound.js +8 -12
- package/lib/instrumentation/grpc-js/grpc.js +189 -52
- package/lib/instrumentation/grpc-js/nr-hooks.js +1 -1
- package/lib/instrumentation/hapi/{hapi-17.js → hapi.js} +0 -0
- package/lib/instrumentation/hapi.js +1 -23
- package/lib/instrumentation/pg.js +3 -18
- package/lib/instrumentations.js +0 -2
- package/lib/metrics/names.js +0 -8
- package/lib/util/application-logging.js +1 -1
- package/package.json +13 -9
- package/lib/collector/ssl/certificates.js +0 -1165
- package/lib/db/parse-sql.js +0 -62
- package/lib/instrumentation/oracle.js +0 -130
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -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) ([
|
|
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) ([
|
|
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) ([
|
|
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) ([
|
|
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) ([
|
|
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
|
|
57
|
-
if (psemver.satisfies('>=
|
|
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
|
}
|
package/lib/config/default.js
CHANGED
|
@@ -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',
|
package/lib/config/index.js
CHANGED
|
@@ -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
|
*
|
package/lib/feature_flags.js
CHANGED
|
@@ -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
|
|
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
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
-
const authorityName = (channel.target && channel.target.path) || channel.getDefaultAuthority
|
|
71
|
+
args[0] = nrMetadata
|
|
21
72
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
88
|
+
segment.addAttribute('component', 'gRPC')
|
|
29
89
|
|
|
30
|
-
|
|
31
|
-
const args = shim.argsToArray.apply(shim, arguments)
|
|
90
|
+
const protocol = 'grpc'
|
|
32
91
|
|
|
33
|
-
const
|
|
92
|
+
const url = `${protocol}://${authorityName}${this.methodName}`
|
|
34
93
|
|
|
35
|
-
|
|
36
|
-
|
|
94
|
+
segment.addAttribute('http.url', url)
|
|
95
|
+
segment.addAttribute('http.method', this.methodName)
|
|
37
96
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
104
|
+
args[1] = nrListener
|
|
49
105
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
106
|
+
return original.apply(this, args)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
54
110
|
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
128
|
+
const name = args[0]
|
|
129
|
+
const handler = args[1]
|
|
130
|
+
const type = args[args.length - 1]
|
|
66
131
|
|
|
67
|
-
|
|
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
|
-
|
|
139
|
+
args[1] = shim.bindCreateTransaction(instrumentedHandler, { type: shim.WEB })
|
|
70
140
|
|
|
71
|
-
|
|
72
|
-
segment.addAttribute('http.method', this.methodName)
|
|
141
|
+
return original.apply(this, args)
|
|
73
142
|
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
150
|
+
function createTransaction() {
|
|
151
|
+
const parent = shim.getActiveSegment()
|
|
152
|
+
const transaction = parent.transaction
|
|
78
153
|
|
|
79
|
-
|
|
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
|
-
|
|
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
|
}
|
|
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
|
|
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
|