newrelic 6.5.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/NEWS.md +20 -0
- package/THIRD_PARTY_NOTICES.md +420 -0
- package/lib/agent.js +22 -8
- package/lib/collector/serverless.js +8 -0
- package/lib/config/default.js +23 -1
- package/lib/config/env.js +7 -1
- package/lib/config/index.js +7 -0
- 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/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-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/trace/index.js +11 -8
- package/package.json +5 -3
|
@@ -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
|
+
}
|
package/lib/metrics/names.js
CHANGED
|
@@ -16,7 +16,8 @@ const SUPPORTABILITY = {
|
|
|
16
16
|
DEPENDENCIES: 'Supportability/InstalledDependencies',
|
|
17
17
|
NODEJS: 'Supportability/Nodejs',
|
|
18
18
|
REGISTRATION: 'Supportability/Registration',
|
|
19
|
-
EVENT_HARVEST: 'Supportability/EventHarvest'
|
|
19
|
+
EVENT_HARVEST: 'Supportability/EventHarvest',
|
|
20
|
+
INFINITE_TRACING: 'Supportability/InfiniteTracing'
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const ERRORS = {
|
|
@@ -231,6 +232,15 @@ const SPAN_EVENTS = {
|
|
|
231
232
|
DROPPED: SUPPORTABILITY.PREFIX + 'SpanEvent/Discarded'
|
|
232
233
|
}
|
|
233
234
|
|
|
235
|
+
const INFINITE_TRACING = {
|
|
236
|
+
SEEN: SUPPORTABILITY.INFINITE_TRACING + '/Span/Seen',
|
|
237
|
+
SENT: SUPPORTABILITY.INFINITE_TRACING + '/Span/Sent',
|
|
238
|
+
SPAN_RESPONSE_ERROR: SUPPORTABILITY.INFINITE_TRACING + '/Span/Response/Error',
|
|
239
|
+
SPAN_RESPONSE_GRPC_UNIMPLEMENTED: SUPPORTABILITY.INFINITE_TRACING + '/Span/gRPC/UNIMPLEMENTED',
|
|
240
|
+
SPAN_RESPONSE_GRPC_STATUS: SUPPORTABILITY.INFINITE_TRACING + '/Span/gRPC/%s',
|
|
241
|
+
DRAIN_DURATION: SUPPORTABILITY.INFINITE_TRACING + '/Drain/Duration'
|
|
242
|
+
}
|
|
243
|
+
|
|
234
244
|
module.exports = {
|
|
235
245
|
ACTION_DELIMITER: '/',
|
|
236
246
|
ALL: ALL,
|
|
@@ -253,6 +263,7 @@ module.exports = {
|
|
|
253
263
|
GC: GC,
|
|
254
264
|
HAPI: HAPI,
|
|
255
265
|
HTTP: 'HttpDispatcher',
|
|
266
|
+
INFINITE_TRACING: INFINITE_TRACING,
|
|
256
267
|
LOOP: LOOP,
|
|
257
268
|
MEMCACHE: MEMCACHE,
|
|
258
269
|
MEMORY: MEMORY,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
class ProxyGrpc {
|
|
3
|
+
constructor(grpcLibrary = '@grpc/grpc-js') {
|
|
4
|
+
this.library = require(grpcLibrary)
|
|
5
|
+
|
|
6
|
+
// add methods or objets from base grpc class that we need as needed
|
|
7
|
+
this.credentials = this.library.credentials
|
|
8
|
+
this.Metadata = this.library.Metadata
|
|
9
|
+
this.loadPackageDefinition = this.library.loadPackageDefinition
|
|
10
|
+
this.status = this.library.status
|
|
11
|
+
this.Server = this.library.Server
|
|
12
|
+
this.ServerCredentials = this.library.ServerCredentials
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
module.exports = (new ProxyGrpc)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const psemver = require('../util/process-version')
|
|
4
|
+
const logger = require('../logger')
|
|
5
|
+
const SpanEventAggregator = require('./span-event-aggregator')
|
|
6
|
+
const StreamingSpanEventAggregator = require('./streaming-span-event-aggregator')
|
|
7
|
+
|
|
8
|
+
function createSpanEventAggregator(config, collector, metrics) {
|
|
9
|
+
let shouldCreateStreaming = false
|
|
10
|
+
if (config.infinite_tracing.trace_observer.host) {
|
|
11
|
+
// TODO: ideally this validation and configuration clearing would happen
|
|
12
|
+
// in the config. Since we don't currently have a way to generate
|
|
13
|
+
// support metrics in the config, keeping this related logic together here.
|
|
14
|
+
// If logic happened prior, could merely check for existance of trace_observer.host.
|
|
15
|
+
shouldCreateStreaming = validateInfiniteTracing(config.infinite_tracing.trace_observer)
|
|
16
|
+
|
|
17
|
+
if (!shouldCreateStreaming) {
|
|
18
|
+
// Explicitly disable for any downstream consumers
|
|
19
|
+
config.infinite_tracing.trace_observer.host = ''
|
|
20
|
+
config.infinite_tracing.trace_observer.port = ''
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (shouldCreateStreaming) {
|
|
25
|
+
return createStreamingAggregator(config, collector, metrics)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return createStandardAggregator(config, collector, metrics)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createStreamingAggregator(config, collector, metrics) {
|
|
32
|
+
logger.trace('Creating streaming span event aggregator for infinite tracing.')
|
|
33
|
+
|
|
34
|
+
// loading the class here to ensure its behind a feature flag
|
|
35
|
+
// and won't trigger a grpc load in node 8
|
|
36
|
+
const GrpcConnection = require('../grpc/connection')
|
|
37
|
+
const connection = new GrpcConnection(config.infinite_tracing.trace_observer, metrics)
|
|
38
|
+
const SpanStreamer = require('./span-streamer')
|
|
39
|
+
const spanStreamer = new SpanStreamer(
|
|
40
|
+
config.license_key,
|
|
41
|
+
connection,
|
|
42
|
+
metrics
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const opts = {
|
|
46
|
+
periodMs: 1000,
|
|
47
|
+
limit: 50000,
|
|
48
|
+
span_streamer: spanStreamer
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const aggregator = new StreamingSpanEventAggregator(opts, collector, metrics)
|
|
52
|
+
|
|
53
|
+
return aggregator
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createStandardAggregator(config, collector, metrics) {
|
|
57
|
+
logger.trace('Creating standard span event aggregator.')
|
|
58
|
+
|
|
59
|
+
const opts = {
|
|
60
|
+
periodMs: config.event_harvest_config.report_period_ms,
|
|
61
|
+
limit: config.event_harvest_config.harvest_limits.span_event_data
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const aggregator = new SpanEventAggregator(opts, collector, metrics)
|
|
65
|
+
return aggregator
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function validateInfiniteTracing(trace_observer) {
|
|
69
|
+
if (!psemver.satisfies('>=10.10.0')) {
|
|
70
|
+
logger.warn(
|
|
71
|
+
'Infinite tracing disabled: this version of Node is not supported (must be >=10.10.0)'
|
|
72
|
+
)
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
trace_observer.host = trace_observer.host.trim()
|
|
77
|
+
|
|
78
|
+
if (!validateHostName(trace_observer.host)) {
|
|
79
|
+
logger.warn('Infinite tracing disabled: invalid infinite_tracing.trace_observer.host value')
|
|
80
|
+
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof trace_observer.port !== 'string') {
|
|
85
|
+
trace_observer.port = String(trace_observer.port)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
trace_observer.port = trace_observer.port.trim()
|
|
89
|
+
|
|
90
|
+
if (!validatePortValue(trace_observer.port)) {
|
|
91
|
+
logger.warn('Infinite tracing disabled: invalid infinite_tracing.trace_observer.port value')
|
|
92
|
+
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function validateHostName(host) {
|
|
100
|
+
// Regular expression for validating a hostname
|
|
101
|
+
const hostReg = /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/
|
|
102
|
+
|
|
103
|
+
return hostReg.test(host)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function validatePortValue(port) {
|
|
107
|
+
if (port.length === 0) return false
|
|
108
|
+
|
|
109
|
+
return !isNaN(port)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = createSpanEventAggregator
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const STRING_TYPE = 'string_value'
|
|
4
|
+
const BOOL_TYPE = 'bool_value'
|
|
5
|
+
const INT_TYPE = 'int_value'
|
|
6
|
+
const DOUBLE_TYPE = 'double_value'
|
|
7
|
+
|
|
8
|
+
function mapToStreamingType(value) {
|
|
9
|
+
if (value === null || value === undefined) {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const valueType = typeof value
|
|
14
|
+
|
|
15
|
+
let protoTypeString = null
|
|
16
|
+
switch (valueType) {
|
|
17
|
+
case 'string': {
|
|
18
|
+
protoTypeString = STRING_TYPE
|
|
19
|
+
break
|
|
20
|
+
}
|
|
21
|
+
case 'boolean': {
|
|
22
|
+
protoTypeString = BOOL_TYPE
|
|
23
|
+
break
|
|
24
|
+
}
|
|
25
|
+
case 'number': {
|
|
26
|
+
const isInteger = Number.isInteger(value)
|
|
27
|
+
protoTypeString = isInteger ? INT_TYPE : DOUBLE_TYPE
|
|
28
|
+
break
|
|
29
|
+
}
|
|
30
|
+
default: {
|
|
31
|
+
protoTypeString = null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (protoTypeString) {
|
|
36
|
+
return {
|
|
37
|
+
[protoTypeString]: value
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = mapToStreamingType
|
|
@@ -35,6 +35,11 @@ class SpanEventAggregator extends EventAggregator {
|
|
|
35
35
|
return [this.runId, metrics, eventData]
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
start() {
|
|
39
|
+
logger.debug('starting SpanEventAggregator')
|
|
40
|
+
return super.start()
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
send() {
|
|
39
44
|
if (spanLogger.traceEnabled()) {
|
|
40
45
|
spanLogger.trace({
|