newrelic 8.16.0 → 8.17.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 +4 -0
- package/lib/config/default.js +12 -0
- package/lib/config/env.js +4 -0
- package/lib/instrumentation/grpc-js/grpc.js +188 -53
- package/lib/instrumentation/grpc-js/nr-hooks.js +1 -1
- package/package.json +1 -1
package/NEWS.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
### v8.17.0 (2022-07-27)
|
|
2
|
+
|
|
3
|
+
* Added instrumentation for `grpc-js` server unary, client-streaming, server-streaming and bidirectional streaming handlers.
|
|
4
|
+
|
|
1
5
|
### v8.16.0 (2022-07-21)
|
|
2
6
|
|
|
3
7
|
* 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.
|
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',
|
|
@@ -6,80 +6,215 @@
|
|
|
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
|
-
|
|
17
|
-
|
|
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
|
+
}
|
|
18
41
|
|
|
19
|
-
|
|
20
|
-
|
|
42
|
+
const channel = this.channel
|
|
43
|
+
const authorityName = (channel.target && channel.target.path) || channel.getDefaultAuthority
|
|
21
44
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
const segment = shim.createSegment({
|
|
46
|
+
name: `External/${authorityName}${this.methodName}`,
|
|
47
|
+
opaque: true,
|
|
48
|
+
recorder: recordExternal(authorityName, 'gRPC')
|
|
49
|
+
})
|
|
27
50
|
|
|
28
|
-
|
|
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.')
|
|
69
|
+
}
|
|
29
70
|
|
|
30
|
-
|
|
31
|
-
const args = shim.argsToArray.apply(shim, arguments)
|
|
71
|
+
args[0] = nrMetadata
|
|
32
72
|
|
|
33
|
-
|
|
73
|
+
const originalListener = args[1]
|
|
74
|
+
const nrListener = Object.assign({}, originalListener)
|
|
75
|
+
nrListener.onReceiveStatus = (status) => {
|
|
76
|
+
const { code, details } = status
|
|
34
77
|
|
|
35
|
-
|
|
36
|
-
|
|
78
|
+
segment.addAttribute('grpc.statusCode', code)
|
|
79
|
+
segment.addAttribute('grpc.statusText', details)
|
|
37
80
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
})
|
|
44
|
-
} else {
|
|
45
|
-
shim.logger.debug('Distributed tracing disabled by instrumentation.')
|
|
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)
|
|
46
86
|
}
|
|
47
87
|
|
|
48
|
-
|
|
88
|
+
segment.addAttribute('component', 'gRPC')
|
|
49
89
|
|
|
50
|
-
const
|
|
51
|
-
const nrListener = Object.assign({}, originalListener)
|
|
52
|
-
nrListener.onReceiveStatus = (status) => {
|
|
53
|
-
const { code, details } = status
|
|
90
|
+
const protocol = 'grpc'
|
|
54
91
|
|
|
55
|
-
|
|
56
|
-
segment.addAttribute('grpc.statusText', details)
|
|
92
|
+
const url = `${protocol}://${authorityName}${this.methodName}`
|
|
57
93
|
|
|
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
|
-
}
|
|
94
|
+
segment.addAttribute('http.url', url)
|
|
95
|
+
segment.addAttribute('http.method', this.methodName)
|
|
64
96
|
|
|
65
|
-
|
|
97
|
+
segment.end()
|
|
66
98
|
|
|
67
|
-
|
|
99
|
+
originalListener && originalListener.onReceiveStatus(status)
|
|
100
|
+
}
|
|
68
101
|
|
|
69
|
-
|
|
102
|
+
args[1] = nrListener
|
|
70
103
|
|
|
71
|
-
|
|
72
|
-
|
|
104
|
+
return original.apply(this, args)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
73
108
|
|
|
74
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Instruments the grpc-js server by intercepting the moment when
|
|
111
|
+
* server methods are registered from the method implementations
|
|
112
|
+
* provided to grpc-js. This handles all four types of server
|
|
113
|
+
* invocations: unary, client-streaming, server-streaming, and
|
|
114
|
+
* bidirectional streaming.
|
|
115
|
+
*
|
|
116
|
+
* @param {object} shim the web shim to instrument with
|
|
117
|
+
* @param {Function} original the original function
|
|
118
|
+
* @returns {Function} the instrumented function
|
|
119
|
+
*/
|
|
120
|
+
function wrapRegister(shim, original) {
|
|
121
|
+
const constants = shim.require('./build/src/constants')
|
|
75
122
|
|
|
76
|
-
|
|
77
|
-
|
|
123
|
+
return function wrappedRegister() {
|
|
124
|
+
const args = shim.argsToArray.apply(shim, arguments)
|
|
78
125
|
|
|
79
|
-
|
|
126
|
+
const name = args[0]
|
|
127
|
+
const handler = args[1]
|
|
128
|
+
const type = args[args.length - 1]
|
|
80
129
|
|
|
81
|
-
|
|
82
|
-
|
|
130
|
+
if (this.handlers.has(name)) {
|
|
131
|
+
shim.logger.debug(
|
|
132
|
+
`Not re-instrumenting gRPC method handler for ${name}: it is already registered in the server.`
|
|
133
|
+
)
|
|
134
|
+
return original.apply(this, arguments)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
args[1] = shim.bindCreateTransaction(instrumentedHandler, { type: shim.WEB })
|
|
138
|
+
|
|
139
|
+
return original.apply(this, args)
|
|
140
|
+
|
|
141
|
+
function instrumentedHandler(stream) {
|
|
142
|
+
const { transaction, segment } = createTransaction()
|
|
143
|
+
acceptDTHeaders(stream, transaction)
|
|
144
|
+
instrumentEventListeners(stream, transaction)
|
|
145
|
+
return shim.applySegment(handler, segment, true, this, arguments)
|
|
83
146
|
}
|
|
84
|
-
|
|
147
|
+
|
|
148
|
+
function createTransaction() {
|
|
149
|
+
const parent = shim.getActiveSegment()
|
|
150
|
+
const transaction = parent.transaction
|
|
151
|
+
|
|
152
|
+
// Create the transaction segment using the request URL for now. Once a
|
|
153
|
+
// better name can be determined this segment will be renamed to that.
|
|
154
|
+
const segment = shim.createSegment(name, recordHttp)
|
|
155
|
+
segment.start()
|
|
156
|
+
|
|
157
|
+
transaction.type = 'web'
|
|
158
|
+
transaction.baseSegment = segment
|
|
159
|
+
transaction.url = name
|
|
160
|
+
|
|
161
|
+
transaction.trace.attributes.addAttribute(DESTINATION, 'request.method', transaction.url)
|
|
162
|
+
|
|
163
|
+
transaction.trace.attributes.addAttribute(DESTINATION, 'request.uri', transaction.url)
|
|
164
|
+
|
|
165
|
+
shim.setTransactionUri(transaction.url)
|
|
166
|
+
|
|
167
|
+
// This attribute isn't required by the spec, but it seems useful to have
|
|
168
|
+
transaction.trace.attributes.addAttribute(DESTINATION, 'grpc.type', type)
|
|
169
|
+
return { transaction, segment }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function acceptDTHeaders(stream, transaction) {
|
|
173
|
+
const metadata = stream.metadata
|
|
174
|
+
Object.entries(metadata.getMap()).forEach(([key, value]) => {
|
|
175
|
+
transaction.trace.attributes.addAttribute(DESTINATION, `request.headers.${key}`, value)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const headers = Object.create(null)
|
|
179
|
+
headers.tracestate = metadata.get('tracestate').join(',')
|
|
180
|
+
headers.traceparent = metadata.get('traceparent').join(',')
|
|
181
|
+
headers.newrelic = metadata.get('newrelic').join(',')
|
|
182
|
+
transaction.acceptDistributedTraceHeaders('HTTP', headers)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function instrumentEventListeners(stream, transaction) {
|
|
186
|
+
const agent = shim.agent
|
|
187
|
+
stream.call.once('callEnd', (statusCode) => {
|
|
188
|
+
transaction.trace.attributes.addAttribute(DESTINATION, 'response.status', statusCode)
|
|
189
|
+
if (statusCode !== 0 && agent.config.grpc.record_errors) {
|
|
190
|
+
const status = constants.Status[statusCode]
|
|
191
|
+
const error = new Error(`gRPC status code ${statusCode}: ${status}`)
|
|
192
|
+
agent.errors.add(transaction, error)
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
stream.call.once('streamEnd', () => {
|
|
196
|
+
transaction.end()
|
|
197
|
+
})
|
|
198
|
+
// TODO should also instrument the 'data' event on the stream
|
|
199
|
+
// object, as that can ensue in lots of processing when the
|
|
200
|
+
// client is streaming. https://issues.newrelic.com/browse/NEWRELIC-1460
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = function instrument(shim) {
|
|
206
|
+
const genericShim = shim.makeSpecializedShim(shim.GENERIC, 'call-stream')
|
|
207
|
+
const callStream = genericShim.require('./build/src/call-stream')
|
|
208
|
+
genericShim.wrap(callStream.Http2CallStream.prototype, 'start', wrapStart)
|
|
209
|
+
|
|
210
|
+
const webFrameworkShim = shim.makeSpecializedShim(shim.WEB_FRAMEWORK, 'server')
|
|
211
|
+
const grpcVersion = shim.require('./package.json').version
|
|
212
|
+
if (semver.lt(grpcVersion, '1.4.0')) {
|
|
213
|
+
shim.logger.debug('gRPC server-side instrumentation only supported on grpc-js >=1.4.0')
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const server = webFrameworkShim.require('./build/src/server')
|
|
218
|
+
webFrameworkShim.setFramework('gRPC')
|
|
219
|
+
webFrameworkShim.wrap(server.Server.prototype, 'register', wrapRegister)
|
|
85
220
|
}
|