newrelic 6.4.0 → 6.6.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/.eslintignore +1 -1
- package/{.eslintrc → .eslintrc.js} +2 -1
- package/.travis.yml +5 -6
- package/NEWS.md +105 -1
- package/THIRD_PARTY_NOTICES.md +420 -0
- package/api.js +72 -1
- package/index.js +3 -3
- package/lib/agent.js +22 -8
- package/lib/aggregators/base-aggregator.js +1 -1
- package/lib/attributes.js +15 -6
- package/lib/collector/serverless.js +8 -0
- package/lib/config/default.js +23 -1
- package/lib/config/env.js +7 -1
- package/lib/config/hsm.js +6 -1
- package/lib/config/index.js +36 -8
- package/lib/config/lasp.js +16 -1
- package/lib/errors/error-collector.js +51 -90
- package/lib/errors/helper.js +13 -13
- package/lib/errors/index.js +36 -18
- package/lib/grpc/connection/states.js +9 -0
- package/lib/grpc/connection.js +344 -0
- package/lib/grpc/endpoints/infinite-tracing/v1.proto +29 -0
- package/lib/header-processing.js +75 -0
- package/lib/instrumentation/core/child_process.js +12 -0
- package/lib/instrumentation/core/http-outbound.js +4 -0
- package/lib/instrumentation/core/http.js +98 -145
- package/lib/instrumentation/hapi/hapi-17.js +10 -3
- package/lib/metrics/names.js +12 -1
- package/lib/proxy/grpc.js +15 -0
- package/lib/serverless/aws-lambda.js +2 -0
- package/lib/spans/create-span-event-aggregator.js +112 -0
- package/lib/spans/map-to-streaming-type.js +44 -0
- package/lib/spans/span-event-aggregator.js +5 -0
- package/lib/spans/span-event.js +22 -9
- package/lib/spans/span-streamer.js +109 -0
- package/lib/spans/streaming-span-attributes.js +59 -0
- package/lib/spans/streaming-span-event-aggregator.js +98 -0
- package/lib/spans/streaming-span-event.js +261 -0
- package/lib/transaction/index.js +36 -20
- package/lib/transaction/trace/index.js +13 -10
- package/lib/transaction/trace/segment.js +12 -1
- package/lib/transaction/tracecontext.js +4 -0
- package/package.json +10 -10
- package/stub_api.js +5 -0
package/lib/errors/index.js
CHANGED
|
@@ -6,8 +6,18 @@ var props = require('../util/properties')
|
|
|
6
6
|
var urltils = require('../util/urltils')
|
|
7
7
|
const errorHelper = require('../errors/helper')
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
class Exception {
|
|
10
|
+
constructor({error, timestamp, customAttributes, agentAttributes}) {
|
|
11
|
+
this.error = error
|
|
12
|
+
this.timestamp = timestamp || 0
|
|
13
|
+
this.customAttributes = customAttributes || {}
|
|
14
|
+
this.agentAttributes = agentAttributes || {}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getErrorDetails(config) {
|
|
18
|
+
return errorHelper.extractErrorInformation(null, this.error, config)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
|
|
12
22
|
/**
|
|
13
23
|
* Given either or both of a transaction and an exception, generate an error
|
|
@@ -17,21 +27,20 @@ module.exports.createEvent = createEvent
|
|
|
17
27
|
* handler, which traps actual instances of Error, try to set sensible
|
|
18
28
|
* defaults for everything.
|
|
19
29
|
*
|
|
20
|
-
* @param {Transaction} transaction
|
|
21
|
-
*
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {object} customAttributes Any custom attributes associated with
|
|
24
|
-
* the request (optional).
|
|
30
|
+
* @param {Transaction} transaction The agent transaction, coming from the instrumentatation
|
|
31
|
+
* @param {Exception} exception An custom Exception object with the error and other information
|
|
32
|
+
* @param {object} config The configuration to use when creating the object
|
|
25
33
|
*/
|
|
26
|
-
function createError(transaction, exception,
|
|
34
|
+
function createError(transaction, exception, config) {
|
|
35
|
+
const error = exception.error
|
|
27
36
|
let {name, message, type} = errorHelper.extractErrorInformation(
|
|
28
37
|
transaction,
|
|
29
|
-
|
|
38
|
+
error,
|
|
30
39
|
config,
|
|
31
40
|
urltils
|
|
32
41
|
)
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
let params = {
|
|
35
44
|
userAttributes: Object.create(null),
|
|
36
45
|
agentAttributes: Object.create(null),
|
|
37
46
|
intrinsics: Object.create(null)
|
|
@@ -39,20 +48,26 @@ function createError(transaction, exception, customAttributes, config) {
|
|
|
39
48
|
|
|
40
49
|
if (transaction) {
|
|
41
50
|
// Copy all of the parameters off of the transaction.
|
|
42
|
-
params.agentAttributes = transaction.trace.attributes.get(DESTINATIONS.ERROR_EVENT)
|
|
43
51
|
params.intrinsics = transaction.getIntrinsicAttributes()
|
|
52
|
+
let transactionAgentAttributes = transaction.trace.attributes.get(DESTINATIONS.ERROR_EVENT)
|
|
53
|
+
transactionAgentAttributes = transactionAgentAttributes || {}
|
|
54
|
+
|
|
55
|
+
// Merge the agent attributes specific to this error event with the transaction attributes
|
|
56
|
+
const agentAttributes = Object.assign(exception.agentAttributes, transactionAgentAttributes)
|
|
57
|
+
params.agentAttributes = agentAttributes
|
|
44
58
|
|
|
45
59
|
// There should be no attributes to copy in HSM, but check status anyway
|
|
46
60
|
if (!config.high_security) {
|
|
47
|
-
|
|
61
|
+
const custom = transaction.trace.custom.get(DESTINATIONS.ERROR_EVENT)
|
|
48
62
|
urltils.overwriteParameters(custom, params.userAttributes)
|
|
49
63
|
}
|
|
50
64
|
}
|
|
51
65
|
|
|
66
|
+
const customAttributes = exception.customAttributes
|
|
52
67
|
if (!config.high_security && config.api.custom_attributes_enabled && customAttributes) {
|
|
53
|
-
for (
|
|
68
|
+
for (let key in customAttributes) {
|
|
54
69
|
if (props.hasOwn(customAttributes, key)) {
|
|
55
|
-
|
|
70
|
+
const dest = config.attributeFilter.filterTransaction(
|
|
56
71
|
DESTINATIONS.ERROR_EVENT,
|
|
57
72
|
key
|
|
58
73
|
)
|
|
@@ -63,12 +78,11 @@ function createError(transaction, exception, customAttributes, config) {
|
|
|
63
78
|
}
|
|
64
79
|
}
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
var stack = exception && exception.stack
|
|
81
|
+
const stack = exception.error && exception.error.stack
|
|
68
82
|
if (stack) {
|
|
69
83
|
params.stack_trace = ('' + stack).split(/[\n\r]/g)
|
|
70
84
|
if (config.high_security || config.strip_exception_messages.enabled) {
|
|
71
|
-
params.stack_trace[0] = exception.name + ': <redacted>'
|
|
85
|
+
params.stack_trace[0] = exception.error.name + ': <redacted>'
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
|
|
@@ -77,7 +91,7 @@ function createError(transaction, exception, customAttributes, config) {
|
|
|
77
91
|
params.intrinsics['error.expected'] = true
|
|
78
92
|
}
|
|
79
93
|
|
|
80
|
-
|
|
94
|
+
let res = [0, name, message, type, params]
|
|
81
95
|
if (transaction) {
|
|
82
96
|
res.transaction = transaction.id
|
|
83
97
|
}
|
|
@@ -180,3 +194,7 @@ function _getErrorEventIntrinsicAttrs(
|
|
|
180
194
|
|
|
181
195
|
return attributes
|
|
182
196
|
}
|
|
197
|
+
|
|
198
|
+
module.exports.createError = createError
|
|
199
|
+
module.exports.createEvent = createEvent
|
|
200
|
+
module.exports.Exception = Exception
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const protoLoader = require('@grpc/proto-loader')
|
|
4
|
+
const grpc = require('../proxy/grpc')
|
|
5
|
+
const logger = require('../logger').child({component: 'grpc_connection'})
|
|
6
|
+
const EventEmitter = require('events')
|
|
7
|
+
const NAMES = require('../metrics/names')
|
|
8
|
+
const util = require('util')
|
|
9
|
+
|
|
10
|
+
const connectionStates = require('./connection/states')
|
|
11
|
+
|
|
12
|
+
const protoOptions = {keepCase: true,
|
|
13
|
+
longs: String,
|
|
14
|
+
enums: String,
|
|
15
|
+
defaults: true,
|
|
16
|
+
oneofs: true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const pathProtoDefinition = __dirname +
|
|
20
|
+
'../../../lib/grpc/endpoints/infinite-tracing/v1.proto'
|
|
21
|
+
|
|
22
|
+
const defaultBackoffOpts = {
|
|
23
|
+
initialSeconds: 0,
|
|
24
|
+
seconds:15
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Class for managing the GRPC connection
|
|
29
|
+
*
|
|
30
|
+
* Both @grpc/grpc-js and grpc will manage the http2 connections
|
|
31
|
+
* for us -- this class manages the _stream_ connection logic.
|
|
32
|
+
*
|
|
33
|
+
* Will emit events based on the connectionStates (see above
|
|
34
|
+
*/
|
|
35
|
+
class GrpcConnection extends EventEmitter {
|
|
36
|
+
/**
|
|
37
|
+
* GrpcConnection constructor
|
|
38
|
+
*
|
|
39
|
+
* Standard property setting/initilization, and sets an initial
|
|
40
|
+
* connection state of disconnected
|
|
41
|
+
*
|
|
42
|
+
* @param {Object} traceObserverConfig config item config.infinite_tracing.trace_observer
|
|
43
|
+
* @param {MetricAggregator} metrics metric aggregator, for supportability metrics
|
|
44
|
+
* @param {Object} backoffOpts allows injecting specific backoff times
|
|
45
|
+
*/
|
|
46
|
+
constructor(traceObserverConfig, metrics, backoffOpts = defaultBackoffOpts) {
|
|
47
|
+
super()
|
|
48
|
+
|
|
49
|
+
// only set opts if the object is valid
|
|
50
|
+
if ( (backoffOpts.initialSeconds || backoffOpts.initialSeconds === 0) &&
|
|
51
|
+
(backoffOpts.seconds || backoffOpts.seconds === 0)) {
|
|
52
|
+
this._backoffOpts = backoffOpts
|
|
53
|
+
} else {
|
|
54
|
+
this._backoffOpts = defaultBackoffOpts
|
|
55
|
+
logger.trace(
|
|
56
|
+
'invalid backoff information passed to constructor, using default values'
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// initial "stream connection" has a 0 second backoff, unless
|
|
61
|
+
// different values are injected via _backoffOpts
|
|
62
|
+
this._streamBackoffSeconds = this._backoffOpts.initialSeconds
|
|
63
|
+
this._metrics = metrics
|
|
64
|
+
this._setState(connectionStates.disconnected)
|
|
65
|
+
|
|
66
|
+
this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
|
|
67
|
+
this._licensekey = null
|
|
68
|
+
this._runId = null
|
|
69
|
+
|
|
70
|
+
this.stream = null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Sets connection details
|
|
75
|
+
*
|
|
76
|
+
* Allows setting of connection details _after_ object constructions
|
|
77
|
+
* but before the actual connection.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} endpoint the GRPC server's endpoint
|
|
80
|
+
* @param {string} license_key the agent license key
|
|
81
|
+
* @param {string} run_id the current agent run id (also called agent run token)
|
|
82
|
+
* @param {string} [rootCerts] string of root (ca) certificates to attach to the connection.
|
|
83
|
+
*/
|
|
84
|
+
setConnectionDetails(license_key, run_id, rootCerts) {
|
|
85
|
+
this._licenseKey = license_key
|
|
86
|
+
this._runId = run_id
|
|
87
|
+
this._rootCerts = rootCerts
|
|
88
|
+
|
|
89
|
+
return this
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getTraceObserverEndpoint(traceObserverConfig) {
|
|
93
|
+
return `${traceObserverConfig.host}:${traceObserverConfig.port}`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Sets the connection state
|
|
98
|
+
*
|
|
99
|
+
* Used to indicate a transition from one connection state to
|
|
100
|
+
* the next. Also responsible for emitting the connect state event
|
|
101
|
+
*
|
|
102
|
+
* @param {int} state The connection state (See connectionStates above)
|
|
103
|
+
* @param {ClientDuplexStreamImpl} state The GRPC stream, when defined
|
|
104
|
+
*/
|
|
105
|
+
_setState(state, stream = null) {
|
|
106
|
+
this._state = state
|
|
107
|
+
this.emit(connectionStates[state], stream)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Start the Connection
|
|
112
|
+
*
|
|
113
|
+
* Public Entry point -- initiates a connection
|
|
114
|
+
*/
|
|
115
|
+
connectSpans() {
|
|
116
|
+
if (this._state !== connectionStates.disconnected) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this._setState(connectionStates.connecting)
|
|
121
|
+
logger.trace('connecting to grpc endpoint in [%s] seconds', this._getBackoffSeconds())
|
|
122
|
+
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
try {
|
|
125
|
+
this.stream = this._connectSpans(this._endpoint, this._licenseKey, this._runId)
|
|
126
|
+
|
|
127
|
+
// May not actually be "connected" at this point but we can write to the stream
|
|
128
|
+
// immediately.
|
|
129
|
+
this._setState(connectionStates.connected, this.stream)
|
|
130
|
+
|
|
131
|
+
// any future stream connect/disconnect after initial connection
|
|
132
|
+
// should have a 15 second backoff
|
|
133
|
+
this._setStreamBackoffAfterInitialStreamSetup()
|
|
134
|
+
} catch (err) {
|
|
135
|
+
logger.trace(
|
|
136
|
+
err,
|
|
137
|
+
'Unexpected error establishing GRPC stream, will not attempt reconnect.'
|
|
138
|
+
)
|
|
139
|
+
this._disconnectWithoutReconnect()
|
|
140
|
+
}
|
|
141
|
+
}, this._getBackoffSeconds() * 1000)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_setStreamBackoffAfterInitialStreamSetup() {
|
|
145
|
+
this._streamBackoffSeconds = this._backoffOpts.seconds
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
_setStreamBackoffToInitialValue() {
|
|
149
|
+
this._streamBackoffSeconds = this._backoffOpts.initialSeconds
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* End the current stream and set state to disconnected.
|
|
154
|
+
*
|
|
155
|
+
* No more data can be sent until connected again.
|
|
156
|
+
*/
|
|
157
|
+
disconnect() {
|
|
158
|
+
if (this._state === connectionStates.disconnected) {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this._disconnectWithoutReconnect()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Calculates backoff seconds
|
|
167
|
+
*/
|
|
168
|
+
_getBackoffSeconds() {
|
|
169
|
+
return this._streamBackoffSeconds
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Method returns GRPC metadata for initial connection
|
|
174
|
+
*
|
|
175
|
+
* @param {string} license_key
|
|
176
|
+
* @param {string} run_id
|
|
177
|
+
*/
|
|
178
|
+
_getMetadata(license_key, run_id, env) {
|
|
179
|
+
const metadata = new grpc.Metadata()
|
|
180
|
+
metadata.add('license_key', license_key)
|
|
181
|
+
metadata.add('agent_run_token', run_id)
|
|
182
|
+
|
|
183
|
+
// check environment variables for testing parameters and
|
|
184
|
+
// pass to server via meta-data.
|
|
185
|
+
const flaky = parseInt(env.NEWRELIC_GRPCCONNECTION_METADATA_FLAKY, 10)
|
|
186
|
+
const delay = parseInt(env.NEWRELIC_GRPCCONNECTION_METADATA_DELAY, 10)
|
|
187
|
+
if (flaky) {
|
|
188
|
+
logger.trace('Adding flaky metadata: %s', flaky)
|
|
189
|
+
metadata.add('flaky', flaky)
|
|
190
|
+
}
|
|
191
|
+
if (delay) {
|
|
192
|
+
logger.trace('Adding delay metadata: %s', delay)
|
|
193
|
+
metadata.add('delay',delay)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return metadata
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Sets internal object state to disconnected initiates a new connection
|
|
201
|
+
*
|
|
202
|
+
* Called when we receive a stream status that indicates a potential
|
|
203
|
+
* problem with the stream. We set the state (which will emit an event),
|
|
204
|
+
* increment tries to honor the backoff, and then reconnect.
|
|
205
|
+
*/
|
|
206
|
+
_reconnect() {
|
|
207
|
+
this._disconnect()
|
|
208
|
+
this.connectSpans()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
_disconnect() {
|
|
212
|
+
if (this.stream) {
|
|
213
|
+
this.stream.removeAllListeners()
|
|
214
|
+
|
|
215
|
+
// Indicates to server we are done.
|
|
216
|
+
// Server officially closes the stream.
|
|
217
|
+
this.stream.end()
|
|
218
|
+
this.stream = null
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
this._setState(connectionStates.disconnected)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Disconnects Without Reconnect
|
|
226
|
+
*
|
|
227
|
+
* Certain GRPC statuses require us to disconnect and _not_
|
|
228
|
+
* attempt a stream reconnect.
|
|
229
|
+
*/
|
|
230
|
+
_disconnectWithoutReconnect() {
|
|
231
|
+
this._disconnect()
|
|
232
|
+
this._setStreamBackoffToInitialValue()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Central location to setup stream observers
|
|
237
|
+
*
|
|
238
|
+
* Events from the GRPC stream (a ClientDuplexStreamImpl) are the main way
|
|
239
|
+
* we communicate with the GRPC server.
|
|
240
|
+
*
|
|
241
|
+
* @param {ClientDuplexStreamImpl} stream
|
|
242
|
+
*/
|
|
243
|
+
_setupSpanStreamObservers(stream) {
|
|
244
|
+
// listen for responses from server and log
|
|
245
|
+
if (logger.traceEnabled()) {
|
|
246
|
+
stream.on('data', function data(response) {
|
|
247
|
+
logger.trace("grpc span response stream: %s", JSON.stringify(response))
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// listen for status that indicate stream has ended,
|
|
252
|
+
// and we need to disconnect
|
|
253
|
+
stream.on('status', (grpcStatus) => {
|
|
254
|
+
logger.trace('GRPC Status Received [%s]: %s', grpcStatus.code, grpcStatus.details)
|
|
255
|
+
const grpcStatusName =
|
|
256
|
+
grpc.status[grpcStatus.code] ? grpc.status[grpcStatus.code] : 'UNKNOWN'
|
|
257
|
+
|
|
258
|
+
if (grpc.status[grpc.status.UNIMPLEMENTED] === grpcStatusName) {
|
|
259
|
+
this._metrics.getOrCreateMetric(
|
|
260
|
+
NAMES.INFINITE_TRACING.SPAN_RESPONSE_GRPC_UNIMPLEMENTED
|
|
261
|
+
).incrementCallCount()
|
|
262
|
+
|
|
263
|
+
// per the spec, An UNIMPLEMENTED status code from gRPC indicates
|
|
264
|
+
// that the versioned Trace Observer is no longer available. Agents
|
|
265
|
+
// MUST NOT attempt to reconnect in this case
|
|
266
|
+
this._disconnectWithoutReconnect()
|
|
267
|
+
} else {
|
|
268
|
+
// all statuses are treated as "bad stuff" happened, and
|
|
269
|
+
// as a signal we need to reconnect (except UNIMPLEMENTED).
|
|
270
|
+
// Even an "OK status" indicates the call completed successfully,
|
|
271
|
+
// which indicates the stream is over.
|
|
272
|
+
if (grpc.status[grpc.status.OK] !== grpcStatusName) {
|
|
273
|
+
this._metrics.getOrCreateMetric(
|
|
274
|
+
util.format(NAMES.INFINITE_TRACING.SPAN_RESPONSE_GRPC_STATUS, grpcStatusName)
|
|
275
|
+
).incrementCallCount()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this._reconnect()
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
// if we don't listen for the errors they'll bubble
|
|
283
|
+
// up and crash the application
|
|
284
|
+
stream.on('error', (err) => {
|
|
285
|
+
this._metrics.getOrCreateMetric(NAMES.INFINITE_TRACING.SPAN_RESPONSE_ERROR)
|
|
286
|
+
.incrementCallCount()
|
|
287
|
+
|
|
288
|
+
logger.trace('span stream error. Code: [%s]: %s',err.code, err.details)
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Creates the GRPC credentials needed
|
|
294
|
+
*/
|
|
295
|
+
_generateCredentials(grpcApi) {
|
|
296
|
+
let certBuffer = null
|
|
297
|
+
|
|
298
|
+
// Current settable value for testing. If allowed to be overriden via
|
|
299
|
+
// configuration, this should be removed in place of setting
|
|
300
|
+
// this._rootCerts from config via normal configuration precedence.
|
|
301
|
+
const envTestCerts = process.env.NEWRELIC_GRPCCONNECTION_CA
|
|
302
|
+
const rootCerts = this._rootCerts || envTestCerts
|
|
303
|
+
if (rootCerts) {
|
|
304
|
+
logger.debug('Infinite tracing root certificates found to attach to requests.')
|
|
305
|
+
try {
|
|
306
|
+
certBuffer = Buffer.from(rootCerts, 'utf-8')
|
|
307
|
+
} catch (err) {
|
|
308
|
+
logger.warn('Failed to create buffer from rootCerts, proceeding without.', err)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// null/undefined ca treated same as calling createSsl()
|
|
313
|
+
return grpcApi.credentials.createSsl(certBuffer)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Internal/private method for connection
|
|
318
|
+
*
|
|
319
|
+
* Contains the actual logic that connects to the GRPC service.
|
|
320
|
+
* "Connection" can be a somewhat misleading term here. This method
|
|
321
|
+
* invokes the "recordSpan" remote proceduce call. Behind the scenes
|
|
322
|
+
* this makes an http2 request with the metadata, and then returns
|
|
323
|
+
* a stream for further writing.
|
|
324
|
+
*/
|
|
325
|
+
_connectSpans(endpoint, license_key, run_id) {
|
|
326
|
+
const packageDefinition = protoLoader.loadSync(pathProtoDefinition, protoOptions)
|
|
327
|
+
|
|
328
|
+
const serviceDefinition = grpc.loadPackageDefinition(
|
|
329
|
+
packageDefinition
|
|
330
|
+
).com.newrelic.trace.v1
|
|
331
|
+
|
|
332
|
+
const credentials = this._generateCredentials(grpc)
|
|
333
|
+
const client = new serviceDefinition.IngestService(
|
|
334
|
+
endpoint,
|
|
335
|
+
credentials
|
|
336
|
+
)
|
|
337
|
+
const metadata = this._getMetadata(license_key, run_id, process.env)
|
|
338
|
+
const stream = client.recordSpan(metadata)
|
|
339
|
+
this._setupSpanStreamObservers(stream)
|
|
340
|
+
|
|
341
|
+
return stream
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
module.exports = GrpcConnection
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package com.newrelic.trace.v1;
|
|
4
|
+
|
|
5
|
+
service IngestService {
|
|
6
|
+
// Accepts a stream of Span messages, and returns an irregular stream of
|
|
7
|
+
// RecordStatus messages.
|
|
8
|
+
rpc RecordSpan(stream Span) returns (stream RecordStatus) {}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
message Span {
|
|
12
|
+
string trace_id = 1;
|
|
13
|
+
map<string, AttributeValue> intrinsics = 2;
|
|
14
|
+
map<string, AttributeValue> user_attributes = 3;
|
|
15
|
+
map<string, AttributeValue> agent_attributes = 4;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
message AttributeValue {
|
|
19
|
+
oneof value {
|
|
20
|
+
string string_value = 1;
|
|
21
|
+
bool bool_value = 2;
|
|
22
|
+
int64 int_value = 3;
|
|
23
|
+
double double_value = 4;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
message RecordStatus {
|
|
28
|
+
uint64 messages_seen = 1;
|
|
29
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const REQUEST_START_HEADER = 'x-request-start'
|
|
4
|
+
const QUEUE_HEADER = 'x-queue-start'
|
|
5
|
+
const CONTENT_LENGTH_REGEX = /^Content-Length$/i
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extracts queue time from the incoming request headers.
|
|
9
|
+
*
|
|
10
|
+
* Queue time is provided by certain providers by stamping the request
|
|
11
|
+
* header with the time the request arrived at the router.
|
|
12
|
+
* @param {*} logger
|
|
13
|
+
* @param {*} requestHeaders
|
|
14
|
+
*/
|
|
15
|
+
function getQueueTime(logger, requestHeaders) {
|
|
16
|
+
const headerValue = requestHeaders[REQUEST_START_HEADER] || requestHeaders[QUEUE_HEADER]
|
|
17
|
+
if (!headerValue) {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const split = headerValue.split('=')
|
|
22
|
+
const rawQueueTime = split.length > 1 ? split[1] : headerValue
|
|
23
|
+
|
|
24
|
+
const parsedQueueTime = parseFloat(rawQueueTime)
|
|
25
|
+
if (isNaN(parsedQueueTime)) {
|
|
26
|
+
logger.warn('Queue time header parsed as NaN. See trace level log for value.')
|
|
27
|
+
|
|
28
|
+
// This header can hold up to 4096 bytes which could quickly fill up logs.
|
|
29
|
+
// Do not log a level higher than debug.
|
|
30
|
+
logger.trace('Queue time: %s', rawQueueTime)
|
|
31
|
+
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const convertedQueueTime = convertUnit(parsedQueueTime)
|
|
36
|
+
return convertedQueueTime
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function convertUnit(time) {
|
|
40
|
+
let convertedTime = time
|
|
41
|
+
if (convertedTime > 1e18) {
|
|
42
|
+
// nano seconds
|
|
43
|
+
convertedTime = convertedTime / 1e6
|
|
44
|
+
} else if (convertedTime > 1e15) {
|
|
45
|
+
// micro seconds
|
|
46
|
+
convertedTime = convertedTime / 1e3
|
|
47
|
+
} else if (convertedTime < 1e12) {
|
|
48
|
+
// seconds
|
|
49
|
+
convertedTime = convertedTime * 1e3
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return convertedTime
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns the value of the Content-Length header
|
|
57
|
+
*
|
|
58
|
+
* If no header is found, returns -1
|
|
59
|
+
*
|
|
60
|
+
* @param {*} headers
|
|
61
|
+
*/
|
|
62
|
+
function getContentLengthFromHeaders(headers) {
|
|
63
|
+
let contentLength = -1
|
|
64
|
+
for (const [headerName, headerValue] of Object.entries(headers)) {
|
|
65
|
+
if (CONTENT_LENGTH_REGEX.test(headerName)) {
|
|
66
|
+
return headerValue
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return contentLength
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
getQueueTime,
|
|
74
|
+
getContentLengthFromHeaders
|
|
75
|
+
}
|
|
@@ -33,8 +33,20 @@ function initialize(agent, childProcess, moduleName, shim) {
|
|
|
33
33
|
const args = shim.argsToArray.apply(shim, arguments)
|
|
34
34
|
const cbIndex = args.length - 1
|
|
35
35
|
|
|
36
|
+
const originalListener = args[cbIndex]
|
|
37
|
+
if (!shim.isFunction(originalListener)) {
|
|
38
|
+
return fn.apply(this, arguments)
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
shim.bindSegment(args, cbIndex)
|
|
37
42
|
|
|
43
|
+
// Leverage events.removeListener() mechanism that checks listener
|
|
44
|
+
// property to allow our wrapped listeners to match and remove appropriately.
|
|
45
|
+
// Avoids having to instrument removeListener() and potentially doubling
|
|
46
|
+
// lookup. Since our wrapping will only be referenced by the events
|
|
47
|
+
// collection, we should not need to unwrap.
|
|
48
|
+
args[cbIndex].listener = originalListener
|
|
49
|
+
|
|
38
50
|
return fn.apply(this, args)
|
|
39
51
|
}
|
|
40
52
|
}
|
|
@@ -195,6 +195,10 @@ function handleError(segment, req, error) {
|
|
|
195
195
|
* @param {http.IncomingMessage} res
|
|
196
196
|
*/
|
|
197
197
|
function handleResponse(segment, hostname, req, res) {
|
|
198
|
+
// Add response attributes for spans
|
|
199
|
+
segment.addAttribute('http.statusCode', res.statusCode)
|
|
200
|
+
segment.addAttribute('http.statusText', res.statusMessage)
|
|
201
|
+
|
|
198
202
|
// If CAT is enabled, grab those headers!
|
|
199
203
|
const agent = segment.transaction.agent
|
|
200
204
|
if (
|