newrelic 6.9.0 → 6.10.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 +29 -0
- package/THIRD_PARTY_NOTICES.md +2 -0
- package/api.js +26 -12
- package/lib/config/index.js +5 -0
- package/lib/grpc/connection.js +60 -90
- package/lib/header-attributes.js +4 -0
- package/lib/instrumentation/core/http-outbound.js +3 -3
- package/lib/instrumentation/core/http.js +16 -0
- package/lib/prioritized-attributes.js +196 -0
- package/lib/serverless/aws-lambda.js +37 -4
- package/lib/shim/message-shim.js +16 -0
- package/lib/spans/span-context.js +24 -1
- package/lib/spans/span-event.js +4 -2
- package/lib/spans/span-streamer.js +3 -2
- package/lib/spans/streaming-span-event-aggregator.js +11 -1
- package/lib/spans/streaming-span-event.js +3 -2
- package/lib/transaction/index.js +40 -3
- package/lib/transaction/trace/index.js +22 -0
- package/lib/transaction/trace/segment.js +16 -9
- package/package.json +2 -2
package/NEWS.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
### 6.10.0 (2020-06-22):
|
|
2
|
+
|
|
3
|
+
* Additional Transaction Information applied to Span Events
|
|
4
|
+
* When Distributed Tracing and/or Infinite Tracing are enabled, the Agent will now incorporate additional information from the Transaction Event on to the currently available Span Event of the transaction.
|
|
5
|
+
* The following items are affected:
|
|
6
|
+
* `aws-lambda` related attributes
|
|
7
|
+
* `error.message`
|
|
8
|
+
* `error.class`
|
|
9
|
+
* `error.expected`
|
|
10
|
+
* `http.statusCode`
|
|
11
|
+
* `http.statusText`
|
|
12
|
+
* `message.*`
|
|
13
|
+
* `parent.type`
|
|
14
|
+
* `parent.app`
|
|
15
|
+
* `parent.account`
|
|
16
|
+
* `parent.transportType`
|
|
17
|
+
* `parent.transportDuration`
|
|
18
|
+
* Request Parameters `request.parameters.*`
|
|
19
|
+
* `request.header.*`
|
|
20
|
+
* `request.method`
|
|
21
|
+
* `request.uri`
|
|
22
|
+
* Custom Attributes
|
|
23
|
+
* Custom transaction attributes added via `API.addCustomAttribute` or `API.addCustomAttributes` will now be propagated to the currently active span, if available.
|
|
24
|
+
* **Security Recommendation:**
|
|
25
|
+
* Review your Transaction Event attributes configuration. Any attribute include or exclude setting specific to Transaction Events should be applied to your Span Attributes configuration or global attributes configuration. Please see [Node.js agent attributes](https://docs.newrelic.com/docs/agents/nodejs-agent/attributes/nodejs-agent-attributes#configure-attributes) for more on how to configure.
|
|
26
|
+
* Upgraded @grpc/grpc-js from 1.0.3 to 1.0.4
|
|
27
|
+
* Modified redis callback-less versioned test to use `commandQueueLength` as indicator redis command has completed and test can continue. This is in effort to further reduce these test flickers. Additionally, added wait for client 'ready' before moving on to tests.
|
|
28
|
+
* Updated force secret test runs to run on branch pushes to the main repository.
|
|
29
|
+
|
|
1
30
|
### 6.9.0 (2020-06-08):
|
|
2
31
|
|
|
3
32
|
* Added AWS API Gateway V2 Support to lambda instrumentation.
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -18,6 +18,8 @@ can be found at https://github.com/newrelic/node-newrelic.
|
|
|
18
18
|
* [json-stringify-safe](#json-stringify-safe)
|
|
19
19
|
* [readable-stream](#readable-stream)
|
|
20
20
|
* [semver](#semver)
|
|
21
|
+
* [@grpc/grpc-js](#@grpc/grpc-js)
|
|
22
|
+
* [@grpc/proto-loader](#@grpc/proto-loader)
|
|
21
23
|
|
|
22
24
|
## async
|
|
23
25
|
|
package/api.js
CHANGED
|
@@ -272,7 +272,7 @@ API.prototype.setControllerName = function setControllerName(name, action) {
|
|
|
272
272
|
* @param {string} value The value you want displayed. Must be serializable.
|
|
273
273
|
*/
|
|
274
274
|
API.prototype.addCustomAttribute = function addCustomAttribute(key, value) {
|
|
275
|
-
|
|
275
|
+
const metric = this.agent.metrics.getOrCreateMetric(
|
|
276
276
|
NAMES.SUPPORTABILITY.API + '/addCustomAttribute'
|
|
277
277
|
)
|
|
278
278
|
metric.incrementCallCount()
|
|
@@ -291,24 +291,36 @@ API.prototype.addCustomAttribute = function addCustomAttribute(key, value) {
|
|
|
291
291
|
return false
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
const transaction = this.agent.tracer.getTransaction()
|
|
295
295
|
if (!transaction) {
|
|
296
|
-
|
|
296
|
+
logger.warn('No transaction found for custom attributes.')
|
|
297
|
+
return false
|
|
297
298
|
}
|
|
298
299
|
|
|
299
|
-
|
|
300
|
+
const trace = transaction.trace
|
|
300
301
|
if (!trace.custom) {
|
|
301
|
-
|
|
302
|
+
logger.warn(
|
|
302
303
|
'Could not add attribute %s to nonexistent custom attributes.',
|
|
303
304
|
key
|
|
304
305
|
)
|
|
306
|
+
return false
|
|
305
307
|
}
|
|
306
308
|
|
|
307
309
|
if (CUSTOM_DENYLIST.has(key)) {
|
|
308
|
-
|
|
310
|
+
logger.warn('Not overwriting value of NR-only attribute %s.', key)
|
|
311
|
+
return false
|
|
309
312
|
}
|
|
310
313
|
|
|
311
314
|
trace.addCustomAttribute(key, value)
|
|
315
|
+
|
|
316
|
+
const spanContext = this.agent.tracer.getSpanContext()
|
|
317
|
+
if (!spanContext) {
|
|
318
|
+
logger.debug('No span found for custom attributes.')
|
|
319
|
+
// success/failure is ambiguous here. since at least 1 attempt tried, not returning false
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
spanContext.addCustomAttribute(key, value, spanContext.ATTRIBUTE_PRIORITY.LOW)
|
|
312
324
|
}
|
|
313
325
|
|
|
314
326
|
/**
|
|
@@ -395,20 +407,22 @@ API.prototype.addCustomSpanAttribute = function addCustomSpanAttribute(key, valu
|
|
|
395
407
|
return false
|
|
396
408
|
}
|
|
397
409
|
|
|
398
|
-
const
|
|
410
|
+
const spanContext = this.agent.tracer.getSpanContext()
|
|
399
411
|
|
|
400
|
-
if (!
|
|
401
|
-
|
|
402
|
-
'Could not add attribute %s. No available span
|
|
412
|
+
if (!spanContext) {
|
|
413
|
+
logger.debug(
|
|
414
|
+
'Could not add attribute %s. No available span.',
|
|
403
415
|
key
|
|
404
416
|
)
|
|
417
|
+
return false
|
|
405
418
|
}
|
|
406
419
|
|
|
407
420
|
if (CUSTOM_DENYLIST.has(key)) {
|
|
408
|
-
|
|
421
|
+
logger.warn('Not overwriting value of NR-only attribute %s.', key)
|
|
422
|
+
return false
|
|
409
423
|
}
|
|
410
424
|
|
|
411
|
-
|
|
425
|
+
spanContext.addCustomAttribute(key, value)
|
|
412
426
|
}
|
|
413
427
|
|
|
414
428
|
API.prototype.setIgnoreTransaction = util.deprecate(
|
package/lib/config/index.js
CHANGED
|
@@ -281,6 +281,11 @@ Config.prototype._fromServer = function _fromServer(params, key) {
|
|
|
281
281
|
this.run_id = params.agent_run_id
|
|
282
282
|
break
|
|
283
283
|
|
|
284
|
+
// if it's undefined or null, so be it
|
|
285
|
+
case 'request_headers_map':
|
|
286
|
+
this.request_headers_map = params.request_headers_map
|
|
287
|
+
break
|
|
288
|
+
|
|
284
289
|
// handled by config.onConnect
|
|
285
290
|
case 'high_security':
|
|
286
291
|
break
|
package/lib/grpc/connection.js
CHANGED
|
@@ -19,10 +19,7 @@ const protoOptions = {keepCase: true,
|
|
|
19
19
|
const pathProtoDefinition = __dirname +
|
|
20
20
|
'../../../lib/grpc/endpoints/infinite-tracing/v1.proto'
|
|
21
21
|
|
|
22
|
-
const
|
|
23
|
-
initialSeconds: 0,
|
|
24
|
-
seconds:15
|
|
25
|
-
}
|
|
22
|
+
const DEFAULT_RECONNECT_DELAY_MS = 15 * 1000
|
|
26
23
|
|
|
27
24
|
/**
|
|
28
25
|
* Class for managing the GRPC connection
|
|
@@ -41,31 +38,21 @@ class GrpcConnection extends EventEmitter {
|
|
|
41
38
|
*
|
|
42
39
|
* @param {Object} traceObserverConfig config item config.infinite_tracing.trace_observer
|
|
43
40
|
* @param {MetricAggregator} metrics metric aggregator, for supportability metrics
|
|
44
|
-
* @param {
|
|
41
|
+
* @param {Number} [reconnectDelayMs=15000] number of milliseconds to wait before reconnecting
|
|
42
|
+
* for error states that require a reconnect delay.
|
|
45
43
|
*/
|
|
46
|
-
constructor(traceObserverConfig, metrics,
|
|
44
|
+
constructor(traceObserverConfig, metrics, reconnectDelayMs) {
|
|
47
45
|
super()
|
|
48
46
|
|
|
49
|
-
|
|
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
|
-
}
|
|
47
|
+
this._reconnectDelayMs = reconnectDelayMs || DEFAULT_RECONNECT_DELAY_MS
|
|
59
48
|
|
|
60
|
-
// initial "stream connection" has a 0 second backoff, unless
|
|
61
|
-
// different values are injected via _backoffOpts
|
|
62
|
-
this._streamBackoffSeconds = this._backoffOpts.initialSeconds
|
|
63
49
|
this._metrics = metrics
|
|
64
50
|
this._setState(connectionStates.disconnected)
|
|
65
51
|
|
|
66
52
|
this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
|
|
67
53
|
this._licensekey = null
|
|
68
54
|
this._runId = null
|
|
55
|
+
this._requestHeadersMap = null
|
|
69
56
|
|
|
70
57
|
this.stream = null
|
|
71
58
|
}
|
|
@@ -79,11 +66,13 @@ class GrpcConnection extends EventEmitter {
|
|
|
79
66
|
* @param {string} endpoint the GRPC server's endpoint
|
|
80
67
|
* @param {string} license_key the agent license key
|
|
81
68
|
* @param {string} run_id the current agent run id (also called agent run token)
|
|
69
|
+
* @param {object} requestHeadersMap request headers map received from server connect.
|
|
82
70
|
* @param {string} [rootCerts] string of root (ca) certificates to attach to the connection.
|
|
83
71
|
*/
|
|
84
|
-
setConnectionDetails(license_key, run_id, rootCerts) {
|
|
72
|
+
setConnectionDetails(license_key, run_id, requestHeadersMap, rootCerts) {
|
|
85
73
|
this._licenseKey = license_key
|
|
86
74
|
this._runId = run_id
|
|
75
|
+
this._requestHeadersMap = requestHeadersMap
|
|
87
76
|
this._rootCerts = rootCerts
|
|
88
77
|
|
|
89
78
|
return this
|
|
@@ -118,35 +107,21 @@ class GrpcConnection extends EventEmitter {
|
|
|
118
107
|
}
|
|
119
108
|
|
|
120
109
|
this._setState(connectionStates.connecting)
|
|
121
|
-
logger.trace('
|
|
122
|
-
|
|
123
|
-
setTimeout(() => {
|
|
124
|
-
try {
|
|
125
|
-
this.stream = this._connectSpans(this._endpoint, this._licenseKey, this._runId)
|
|
110
|
+
logger.trace('Connecting to gRPC endpoint.')
|
|
126
111
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this._setState(connectionStates.connected, this.stream)
|
|
112
|
+
try {
|
|
113
|
+
this.stream = this._connectSpans()
|
|
130
114
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
115
|
+
// May not actually be "connected" at this point but we can write to the stream
|
|
116
|
+
// immediately.
|
|
117
|
+
this._setState(connectionStates.connected, this.stream)
|
|
118
|
+
} catch (err) {
|
|
119
|
+
logger.trace(
|
|
120
|
+
err,
|
|
121
|
+
'Unexpected error establishing gRPC stream, will not attempt reconnect.'
|
|
122
|
+
)
|
|
123
|
+
this._disconnect()
|
|
124
|
+
}
|
|
150
125
|
}
|
|
151
126
|
|
|
152
127
|
/**
|
|
@@ -159,14 +134,7 @@ class GrpcConnection extends EventEmitter {
|
|
|
159
134
|
return
|
|
160
135
|
}
|
|
161
136
|
|
|
162
|
-
this.
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Calculates backoff seconds
|
|
167
|
-
*/
|
|
168
|
-
_getBackoffSeconds() {
|
|
169
|
-
return this._streamBackoffSeconds
|
|
137
|
+
this._disconnect()
|
|
170
138
|
}
|
|
171
139
|
|
|
172
140
|
/**
|
|
@@ -175,11 +143,19 @@ class GrpcConnection extends EventEmitter {
|
|
|
175
143
|
* @param {string} license_key
|
|
176
144
|
* @param {string} run_id
|
|
177
145
|
*/
|
|
178
|
-
_getMetadata(license_key, run_id, env) {
|
|
146
|
+
_getMetadata(license_key, run_id, requestHeadersMap, env) {
|
|
179
147
|
const metadata = new grpc.Metadata()
|
|
180
148
|
metadata.add('license_key', license_key)
|
|
181
149
|
metadata.add('agent_run_token', run_id)
|
|
182
150
|
|
|
151
|
+
// p17 spec: If request_headers_map is empty or absent,
|
|
152
|
+
// the agent SHOULD NOT apply anything to its requests.
|
|
153
|
+
if (requestHeadersMap) {
|
|
154
|
+
for (const [key, value] of Object.entries(requestHeadersMap)) {
|
|
155
|
+
metadata.add(key.toLowerCase(), value) // keys MUST be lowercase for Infinite Tracing
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
183
159
|
// check environment variables for testing parameters and
|
|
184
160
|
// pass to server via meta-data.
|
|
185
161
|
const flaky = parseInt(env.NEWRELIC_GRPCCONNECTION_METADATA_FLAKY, 10)
|
|
@@ -197,18 +173,23 @@ class GrpcConnection extends EventEmitter {
|
|
|
197
173
|
}
|
|
198
174
|
|
|
199
175
|
/**
|
|
200
|
-
*
|
|
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.
|
|
176
|
+
* Disconnects from gRPC endpoint and schedules establishing a new connection.
|
|
177
|
+
* @param {number} reconnectDelayMs number of milliseconds to wait before reconnecting.
|
|
205
178
|
*/
|
|
206
|
-
_reconnect() {
|
|
179
|
+
_reconnect(reconnectDelayMs = 0) {
|
|
207
180
|
this._disconnect()
|
|
208
|
-
|
|
181
|
+
|
|
182
|
+
logger.trace('Reconnecting to gRPC endpoint in [%s] seconds', reconnectDelayMs)
|
|
183
|
+
|
|
184
|
+
setTimeout(
|
|
185
|
+
this.connectSpans.bind(this),
|
|
186
|
+
reconnectDelayMs
|
|
187
|
+
)
|
|
209
188
|
}
|
|
210
189
|
|
|
211
190
|
_disconnect() {
|
|
191
|
+
logger.trace('Disconnecting from gRPC endpoint.')
|
|
192
|
+
|
|
212
193
|
if (this.stream) {
|
|
213
194
|
this.stream.removeAllListeners()
|
|
214
195
|
|
|
@@ -221,17 +202,6 @@ class GrpcConnection extends EventEmitter {
|
|
|
221
202
|
this._setState(connectionStates.disconnected)
|
|
222
203
|
}
|
|
223
204
|
|
|
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
205
|
/**
|
|
236
206
|
* Central location to setup stream observers
|
|
237
207
|
*
|
|
@@ -244,14 +214,14 @@ class GrpcConnection extends EventEmitter {
|
|
|
244
214
|
// listen for responses from server and log
|
|
245
215
|
if (logger.traceEnabled()) {
|
|
246
216
|
stream.on('data', function data(response) {
|
|
247
|
-
logger.trace("
|
|
217
|
+
logger.trace("gRPC span response stream: %s", JSON.stringify(response))
|
|
248
218
|
})
|
|
249
219
|
}
|
|
250
220
|
|
|
251
221
|
// listen for status that indicate stream has ended,
|
|
252
222
|
// and we need to disconnect
|
|
253
223
|
stream.on('status', (grpcStatus) => {
|
|
254
|
-
logger.trace('
|
|
224
|
+
logger.trace('gRPC Status Received [%s]: %s', grpcStatus.code, grpcStatus.details)
|
|
255
225
|
const grpcStatusName =
|
|
256
226
|
grpc.status[grpcStatus.code] ? grpc.status[grpcStatus.code] : 'UNKNOWN'
|
|
257
227
|
|
|
@@ -263,19 +233,15 @@ class GrpcConnection extends EventEmitter {
|
|
|
263
233
|
// per the spec, An UNIMPLEMENTED status code from gRPC indicates
|
|
264
234
|
// that the versioned Trace Observer is no longer available. Agents
|
|
265
235
|
// MUST NOT attempt to reconnect in this case
|
|
266
|
-
this.
|
|
236
|
+
this._disconnect()
|
|
237
|
+
} else if (grpc.status[grpc.status.OK] === grpcStatusName) {
|
|
238
|
+
this._reconnect()
|
|
267
239
|
} else {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
}
|
|
240
|
+
this._metrics.getOrCreateMetric(
|
|
241
|
+
util.format(NAMES.INFINITE_TRACING.SPAN_RESPONSE_GRPC_STATUS, grpcStatusName)
|
|
242
|
+
).incrementCallCount()
|
|
277
243
|
|
|
278
|
-
this._reconnect()
|
|
244
|
+
this._reconnect(this._reconnectDelayMs)
|
|
279
245
|
}
|
|
280
246
|
})
|
|
281
247
|
|
|
@@ -322,7 +288,7 @@ class GrpcConnection extends EventEmitter {
|
|
|
322
288
|
* this makes an http2 request with the metadata, and then returns
|
|
323
289
|
* a stream for further writing.
|
|
324
290
|
*/
|
|
325
|
-
_connectSpans(
|
|
291
|
+
_connectSpans() {
|
|
326
292
|
const packageDefinition = protoLoader.loadSync(pathProtoDefinition, protoOptions)
|
|
327
293
|
|
|
328
294
|
const serviceDefinition = grpc.loadPackageDefinition(
|
|
@@ -331,14 +297,18 @@ class GrpcConnection extends EventEmitter {
|
|
|
331
297
|
|
|
332
298
|
const credentials = this._generateCredentials(grpc)
|
|
333
299
|
const client = new serviceDefinition.IngestService(
|
|
334
|
-
|
|
300
|
+
this._endpoint,
|
|
335
301
|
credentials
|
|
336
302
|
)
|
|
337
|
-
|
|
303
|
+
|
|
304
|
+
const metadata =
|
|
305
|
+
this._getMetadata(this._licenseKey, this._runId, this._requestHeadersMap, process.env)
|
|
306
|
+
|
|
338
307
|
const stream = client.recordSpan(metadata)
|
|
339
308
|
this._setupSpanStreamObservers(stream)
|
|
340
309
|
|
|
341
310
|
return stream
|
|
342
311
|
}
|
|
343
312
|
}
|
|
313
|
+
|
|
344
314
|
module.exports = GrpcConnection
|
package/lib/header-attributes.js
CHANGED
|
@@ -110,6 +110,8 @@ function _collectHeaders(headers, nameMap, prefix, transaction) {
|
|
|
110
110
|
? COLLECTED_REQUEST_HEADERS
|
|
111
111
|
: Object.keys(headers)
|
|
112
112
|
|
|
113
|
+
const segment = transaction.agent.tracer.getSegment()
|
|
114
|
+
|
|
113
115
|
for (var i = 0; i < headerKeys.length; i++) {
|
|
114
116
|
var headerKey = headerKeys[i]
|
|
115
117
|
var header = headers[headerKey]
|
|
@@ -128,6 +130,8 @@ function _collectHeaders(headers, nameMap, prefix, transaction) {
|
|
|
128
130
|
attributeName,
|
|
129
131
|
header
|
|
130
132
|
)
|
|
133
|
+
|
|
134
|
+
segment.addSpanAttribute(attributeName, header)
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
137
|
}
|
|
@@ -133,7 +133,7 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
|
|
|
133
133
|
if (parsed.parameters) {
|
|
134
134
|
// Scrub and parse returns on object with a null prototype.
|
|
135
135
|
for (let key in parsed.parameters) { // eslint-disable-line guard-for-in
|
|
136
|
-
segment.
|
|
136
|
+
segment.addSpanAttribute(`request.parameters.${key}`, parsed.parameters[key])
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
segment.addAttribute('url', `${proto}//${hostname}${parsed.path}`)
|
|
@@ -196,8 +196,8 @@ function handleError(segment, req, error) {
|
|
|
196
196
|
*/
|
|
197
197
|
function handleResponse(segment, hostname, req, res) {
|
|
198
198
|
// Add response attributes for spans
|
|
199
|
-
segment.
|
|
200
|
-
segment.
|
|
199
|
+
segment.addSpanAttribute('http.statusCode', res.statusCode)
|
|
200
|
+
segment.addSpanAttribute('http.statusText', res.statusMessage)
|
|
201
201
|
|
|
202
202
|
// If CAT is enabled, grab those headers!
|
|
203
203
|
const agent = segment.transaction.agent
|
|
@@ -71,6 +71,13 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
|
|
|
71
71
|
const segment = tracer.createSegment(request.url, recordWeb)
|
|
72
72
|
segment.start()
|
|
73
73
|
|
|
74
|
+
if (request.method != null) {
|
|
75
|
+
segment.addSpanAttribute(
|
|
76
|
+
'request.method',
|
|
77
|
+
request.method
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
if (txInfo) {
|
|
75
82
|
// Seed segment stack to enable parenting logic leveraged by
|
|
76
83
|
// web framework instrumentations.
|
|
@@ -98,6 +105,11 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
|
|
|
98
105
|
transaction.url
|
|
99
106
|
)
|
|
100
107
|
|
|
108
|
+
segment.addSpanAttribute(
|
|
109
|
+
'request.uri',
|
|
110
|
+
transaction.url
|
|
111
|
+
)
|
|
112
|
+
|
|
101
113
|
// store the port on which this transaction runs
|
|
102
114
|
if (this.address instanceof Function) {
|
|
103
115
|
var address = this.address()
|
|
@@ -172,6 +184,8 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
|
|
|
172
184
|
'http.statusCode',
|
|
173
185
|
responseCode
|
|
174
186
|
)
|
|
187
|
+
|
|
188
|
+
segment.addSpanAttribute('http.statusCode', responseCode)
|
|
175
189
|
|
|
176
190
|
/*
|
|
177
191
|
TODO: remove with next major release
|
|
@@ -192,6 +206,8 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
|
|
|
192
206
|
response.statusMessage
|
|
193
207
|
)
|
|
194
208
|
|
|
209
|
+
segment.addSpanAttribute('http.statusText', response.statusMessage)
|
|
210
|
+
|
|
195
211
|
/*
|
|
196
212
|
TODO: remove with next major release
|
|
197
213
|
httpResponseMessage attribute is deprecated
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Config = require('./config')
|
|
4
|
+
const logger = require('./logger').child({component: 'attributes'})
|
|
5
|
+
const isValidType = require('./util/attribute-types')
|
|
6
|
+
const byteUtils = require('./util/byte-limit')
|
|
7
|
+
const properties = require('./util/properties')
|
|
8
|
+
|
|
9
|
+
const ATTRIBUTE_PRIORITY = {
|
|
10
|
+
HIGH: Infinity,
|
|
11
|
+
LOW: -Infinity
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class PrioritizedAttributes {
|
|
15
|
+
constructor(scope, limit = Infinity) {
|
|
16
|
+
this.filter = makeFilter(scope)
|
|
17
|
+
this.limit = limit
|
|
18
|
+
|
|
19
|
+
this.attributes = new Map()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
isValidLength(str) {
|
|
24
|
+
return typeof str === 'number' || byteUtils.isValidLength(str, 255)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_set(destinations, key, value, truncateExempt, priority) {
|
|
28
|
+
this.attributes.set(key, {value, destinations, truncateExempt, priority})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get(dest) {
|
|
32
|
+
const attrs = Object.create(null)
|
|
33
|
+
|
|
34
|
+
for (let [key, attr] of this.attributes) {
|
|
35
|
+
if (!(attr.destinations & dest)) {
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
attrs[key] = typeof attr.value === 'string' && !attr.truncateExempt
|
|
40
|
+
? byteUtils.truncate(attr.value, 255)
|
|
41
|
+
: attr.value
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return attrs
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
has(key) {
|
|
48
|
+
this.attributes.has(key)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
reset() {
|
|
52
|
+
this.attributes = new Map()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
addAttribute(
|
|
56
|
+
destinations,
|
|
57
|
+
key,
|
|
58
|
+
value,
|
|
59
|
+
truncateExempt = false,
|
|
60
|
+
priority = ATTRIBUTE_PRIORITY.HIGH
|
|
61
|
+
) {
|
|
62
|
+
const existingAttribute = this.attributes.get(key)
|
|
63
|
+
|
|
64
|
+
let droppableAttributeKey = null
|
|
65
|
+
if (!existingAttribute && this.attributes.size === this.limit) {
|
|
66
|
+
droppableAttributeKey = this._getDroppableAttributeKey(priority)
|
|
67
|
+
|
|
68
|
+
if (!droppableAttributeKey) {
|
|
69
|
+
logger.debug(
|
|
70
|
+
`Maximum number of custom attributes have been added.
|
|
71
|
+
Dropping attribute ${key} with ${value} type.`
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (existingAttribute && priority < existingAttribute.priority) {
|
|
79
|
+
logger.debug('incoming priority for \'%s\' is lower than existing, not updating.', key)
|
|
80
|
+
logger.trace(
|
|
81
|
+
'%s attribute retained value: %s, ignored value: %s',
|
|
82
|
+
key,
|
|
83
|
+
existingAttribute.value,
|
|
84
|
+
value
|
|
85
|
+
)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!isValidType(value)) {
|
|
90
|
+
logger.debug(
|
|
91
|
+
'Not adding attribute %s with %s value type. This is expected for undefined' +
|
|
92
|
+
'attributes and only an issue if an attribute is not expected to be undefined' +
|
|
93
|
+
'or not of the type expected.',
|
|
94
|
+
key,
|
|
95
|
+
typeof value
|
|
96
|
+
)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!this.isValidLength(key)) {
|
|
101
|
+
logger.warn(
|
|
102
|
+
'Length limit exceeded for attribute name, not adding: %s',
|
|
103
|
+
key
|
|
104
|
+
)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
// Only set the attribute if at least one destination passed
|
|
110
|
+
const validDestinations = this.filter(destinations, key)
|
|
111
|
+
if (!validDestinations) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (droppableAttributeKey) {
|
|
116
|
+
logger.trace(
|
|
117
|
+
'dropping existing lower priority attribute %s ' +
|
|
118
|
+
'to add higher priority attribute %s',
|
|
119
|
+
droppableAttributeKey,
|
|
120
|
+
key
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
this.attributes.delete(droppableAttributeKey)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this._set(validDestinations, key, value, truncateExempt, priority)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
addAttributes(destinations, attrs) {
|
|
130
|
+
for (let key in attrs) {
|
|
131
|
+
if (properties.hasOwn(attrs, key)) {
|
|
132
|
+
this.addAttribute(destinations, key, attrs[key])
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
_getDroppableAttributeKey(incomingPriority) {
|
|
138
|
+
// There will never be anything lower priority to drop
|
|
139
|
+
if (incomingPriority === ATTRIBUTE_PRIORITY.LOW) {
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.lastFoundIndexCache = this.lastFoundIndexCache || Object.create(null)
|
|
144
|
+
const lastFoundIndex = this.lastFoundIndexCache[incomingPriority]
|
|
145
|
+
|
|
146
|
+
// We've already dropped all items lower than incomingPriority.
|
|
147
|
+
// We can honor the cache because at the point by which we've dropped
|
|
148
|
+
// all lower priority items, due to being at max capacity, there will never be another
|
|
149
|
+
// lower-priority item added. Lower priority items are unable to drop higher priority items.
|
|
150
|
+
if (lastFoundIndex === -1) {
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// We can't reverse iterate w/o creating an array that will iterate,
|
|
155
|
+
// so we just iterate forward stopping once we've checked the last cached index.
|
|
156
|
+
let lowerPriorityAttributeName = null
|
|
157
|
+
let foundIndex = -1
|
|
158
|
+
|
|
159
|
+
let index = 0
|
|
160
|
+
for (const [key, attribute] of this.attributes) {
|
|
161
|
+
// Don't search past last found lower priority item.
|
|
162
|
+
// At the point of dropping items for this priority,
|
|
163
|
+
// lower priority items will never be added.
|
|
164
|
+
if (lastFoundIndex && index > lastFoundIndex) {
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (attribute.priority < incomingPriority) {
|
|
169
|
+
lowerPriorityAttributeName = key
|
|
170
|
+
foundIndex = index
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
index++
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Item may not get dropped, so we simply store the index as
|
|
177
|
+
// an upper maximum and allow a future pass to clear out.
|
|
178
|
+
this.lastFoundIndexCache[incomingPriority] = foundIndex
|
|
179
|
+
|
|
180
|
+
return lowerPriorityAttributeName
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function makeFilter(scope) {
|
|
185
|
+
const {attributeFilter} = Config.getInstance()
|
|
186
|
+
if (scope === 'transaction') {
|
|
187
|
+
return (d, k) => attributeFilter.filterTransaction(d, k)
|
|
188
|
+
} else if (scope === 'segment') {
|
|
189
|
+
return (d, k) => attributeFilter.filterSegment(d, k)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
PrioritizedAttributes: PrioritizedAttributes,
|
|
195
|
+
ATTRIBUTE_PRIORITY: ATTRIBUTE_PRIORITY
|
|
196
|
+
}
|
|
@@ -121,7 +121,10 @@ class AwsLambda {
|
|
|
121
121
|
transaction.setPartialName(transactionName)
|
|
122
122
|
|
|
123
123
|
const isApiGatewayLambdaProxy = apiGateway.isLambdaProxyEvent(event)
|
|
124
|
-
|
|
124
|
+
const segmentRecorder = isApiGatewayLambdaProxy ? recordWeb : recordBackground
|
|
125
|
+
const segment = shim.createSegment(functionName, segmentRecorder)
|
|
126
|
+
transaction.baseSegment = segment
|
|
127
|
+
|
|
125
128
|
// resultProcessor is used to execute additional logic based on the
|
|
126
129
|
// payload supplied to the callback.
|
|
127
130
|
let resultProcessor
|
|
@@ -131,9 +134,6 @@ class AwsLambda {
|
|
|
131
134
|
resultProcessor = getApiGatewayLambdaProxyResultProcessor(transaction)
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
const segmentRecorder = isApiGatewayLambdaProxy ? recordWeb : recordBackground
|
|
135
|
-
const segment = shim.createSegment(functionName, segmentRecorder)
|
|
136
|
-
transaction.baseSegment = segment
|
|
137
137
|
|
|
138
138
|
const cbIndex = args.length - 1
|
|
139
139
|
|
|
@@ -175,6 +175,8 @@ class AwsLambda {
|
|
|
175
175
|
shim.agent.setLambdaArn(context.invokedFunctionArn)
|
|
176
176
|
|
|
177
177
|
shim.agent.setLambdaFunctionVersion(context.functionVersion)
|
|
178
|
+
|
|
179
|
+
segment.addSpanAttributes(awsAttributes)
|
|
178
180
|
|
|
179
181
|
segment.start()
|
|
180
182
|
|
|
@@ -301,6 +303,8 @@ function getApiGatewayLambdaProxyResultProcessor(transaction) {
|
|
|
301
303
|
function setWebRequest(shim, transaction, request) {
|
|
302
304
|
transaction.type = shim.WEB
|
|
303
305
|
|
|
306
|
+
const segment = transaction.baseSegment
|
|
307
|
+
|
|
304
308
|
transaction.url = urltils.scrub(request.url.path)
|
|
305
309
|
transaction.verb = request.method
|
|
306
310
|
transaction.trace.attributes.addAttribute(
|
|
@@ -308,6 +312,12 @@ function setWebRequest(shim, transaction, request) {
|
|
|
308
312
|
'request.method',
|
|
309
313
|
request.method
|
|
310
314
|
)
|
|
315
|
+
|
|
316
|
+
segment.addSpanAttribute(
|
|
317
|
+
'request.method',
|
|
318
|
+
request.method
|
|
319
|
+
)
|
|
320
|
+
|
|
311
321
|
transaction.port = request.url.port
|
|
312
322
|
|
|
313
323
|
transaction.addRequestParameters(request.url.requestParameters)
|
|
@@ -319,6 +329,11 @@ function setWebRequest(shim, transaction, request) {
|
|
|
319
329
|
request.url.path
|
|
320
330
|
)
|
|
321
331
|
|
|
332
|
+
segment.addSpanAttribute(
|
|
333
|
+
'request.uri',
|
|
334
|
+
request.url.path
|
|
335
|
+
)
|
|
336
|
+
|
|
322
337
|
headerAttributes.collectRequestHeaders(request.headers, transaction)
|
|
323
338
|
|
|
324
339
|
if (shim.agent.config.distributed_tracing.enabled) {
|
|
@@ -369,6 +384,10 @@ function setWebResponse(transaction, response) {
|
|
|
369
384
|
transaction.statusCode = response.statusCode
|
|
370
385
|
|
|
371
386
|
const responseCode = String(response.statusCode)
|
|
387
|
+
/*
|
|
388
|
+
TODO: remove with next major release
|
|
389
|
+
httpResponseCode attribute is deprecated
|
|
390
|
+
*/
|
|
372
391
|
transaction.trace.attributes.addAttribute(
|
|
373
392
|
ATTR_DEST.TRANS_COMMON,
|
|
374
393
|
'httpResponseCode',
|
|
@@ -376,10 +395,24 @@ function setWebResponse(transaction, response) {
|
|
|
376
395
|
)
|
|
377
396
|
|
|
378
397
|
if (/^\d+$/.test(responseCode)) {
|
|
398
|
+
/*
|
|
399
|
+
TODO: remove with next major release
|
|
400
|
+
response.status attribute is deprecated
|
|
401
|
+
*/
|
|
379
402
|
transaction.trace.attributes.addAttribute(
|
|
380
403
|
ATTR_DEST.TRANS_COMMON,
|
|
381
404
|
'response.status',
|
|
382
405
|
responseCode)
|
|
406
|
+
|
|
407
|
+
transaction.trace.attributes.addAttribute(
|
|
408
|
+
ATTR_DEST.TRANS_COMMON,
|
|
409
|
+
'http.statusCode',
|
|
410
|
+
responseCode
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
const segment = transaction.agent.tracer.getSegment()
|
|
414
|
+
|
|
415
|
+
segment.addSpanAttribute('http.statusCode', responseCode)
|
|
383
416
|
}
|
|
384
417
|
|
|
385
418
|
headerAttributes.collectResponseHeaders(response.headers, transaction)
|
package/lib/shim/message-shim.js
CHANGED
|
@@ -750,6 +750,12 @@ function recordSubscribedConsume(nodule, properties, spec) {
|
|
|
750
750
|
ATTR_DESTS.NONE,
|
|
751
751
|
'message.parameters.' + key,
|
|
752
752
|
msgDesc.parameters[key])
|
|
753
|
+
|
|
754
|
+
tx.baseSegment.attributes.addAttribute(
|
|
755
|
+
ATTR_DESTS.NONE,
|
|
756
|
+
'message.parameters.' + key,
|
|
757
|
+
msgDesc.parameters[key]
|
|
758
|
+
)
|
|
753
759
|
}
|
|
754
760
|
}
|
|
755
761
|
|
|
@@ -762,6 +768,11 @@ function recordSubscribedConsume(nodule, properties, spec) {
|
|
|
762
768
|
'message.routingKey',
|
|
763
769
|
msgDesc.routingKey
|
|
764
770
|
)
|
|
771
|
+
|
|
772
|
+
tx.baseSegment.addSpanAttribute(
|
|
773
|
+
'message.routingKey',
|
|
774
|
+
msgDesc.routingKey
|
|
775
|
+
)
|
|
765
776
|
}
|
|
766
777
|
if (shim.isString(msgDesc.queue)) {
|
|
767
778
|
tx.trace.attributes.addAttribute(
|
|
@@ -769,6 +780,11 @@ function recordSubscribedConsume(nodule, properties, spec) {
|
|
|
769
780
|
'message.queueName',
|
|
770
781
|
msgDesc.queue
|
|
771
782
|
)
|
|
783
|
+
|
|
784
|
+
tx.baseSegment.addSpanAttribute(
|
|
785
|
+
'message.queueName',
|
|
786
|
+
msgDesc.queue
|
|
787
|
+
)
|
|
772
788
|
}
|
|
773
789
|
}
|
|
774
790
|
if (msgDesc.headers) {
|
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const {MAXIMUM_CUSTOM_ATTRIBUTES} = require('../attributes')
|
|
4
|
+
const {PrioritizedAttributes, ATTRIBUTE_PRIORITY} = require('../prioritized-attributes')
|
|
5
|
+
const {DESTINATIONS} = require('../config/attribute-filter')
|
|
6
|
+
|
|
7
|
+
// Scoping impacts memoization. We could decide to add a scope instead of including
|
|
8
|
+
// spans in segment scope in the future.
|
|
9
|
+
const ATTRIBUTE_SCOPE = 'segment'
|
|
10
|
+
|
|
3
11
|
class SpanContext {
|
|
4
|
-
constructor(intrinsicAttributes) {
|
|
12
|
+
constructor(intrinsicAttributes, customAttributes) {
|
|
5
13
|
this.intrinsicAttributes = intrinsicAttributes || Object.create(null)
|
|
14
|
+
|
|
15
|
+
this.customAttributes =
|
|
16
|
+
customAttributes || new PrioritizedAttributes(ATTRIBUTE_SCOPE, MAXIMUM_CUSTOM_ATTRIBUTES)
|
|
17
|
+
|
|
18
|
+
this.ATTRIBUTE_PRIORITY = ATTRIBUTE_PRIORITY
|
|
6
19
|
}
|
|
7
20
|
|
|
8
21
|
addIntrinsicAttribute(key, value) {
|
|
9
22
|
this.intrinsicAttributes[key] = value
|
|
10
23
|
}
|
|
24
|
+
|
|
25
|
+
addCustomAttribute(key, value, priority) {
|
|
26
|
+
this.customAttributes.addAttribute(
|
|
27
|
+
DESTINATIONS.SPAN_EVENT,
|
|
28
|
+
key,
|
|
29
|
+
value,
|
|
30
|
+
false,
|
|
31
|
+
priority
|
|
32
|
+
)
|
|
33
|
+
}
|
|
11
34
|
}
|
|
12
35
|
|
|
13
36
|
module.exports = SpanContext
|
package/lib/spans/span-event.js
CHANGED
|
@@ -80,7 +80,9 @@ class SpanEvent {
|
|
|
80
80
|
*/
|
|
81
81
|
static fromSegment(segment, parentId = null, isRoot = false) {
|
|
82
82
|
const attributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
|
|
83
|
-
|
|
83
|
+
|
|
84
|
+
const spanContext = segment.getSpanContext()
|
|
85
|
+
const customAttributes = spanContext.customAttributes.get(DESTINATIONS.SPAN_EVENT)
|
|
84
86
|
|
|
85
87
|
let span = null
|
|
86
88
|
if (HttpSpanEvent.testSegment(segment)) {
|
|
@@ -91,7 +93,7 @@ class SpanEvent {
|
|
|
91
93
|
span = new SpanEvent(attributes, customAttributes)
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
|
|
95
97
|
for (const [key, value] of Object.entries(spanContext.intrinsicAttributes)) {
|
|
96
98
|
span.intrinsics[key] = value
|
|
97
99
|
}
|
|
@@ -126,10 +126,11 @@ class SpanStreamer {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
connect(agent_run_id) {
|
|
129
|
+
connect(agent_run_id, requestHeadersMap) {
|
|
130
130
|
this.connection.setConnectionDetails(
|
|
131
131
|
this.license_key,
|
|
132
|
-
agent_run_id
|
|
132
|
+
agent_run_id,
|
|
133
|
+
requestHeadersMap
|
|
133
134
|
)
|
|
134
135
|
|
|
135
136
|
this.connection.connectSpans()
|
|
@@ -31,8 +31,10 @@ class StreamingSpanEventAggregator extends Aggregator {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
logger.trace('StreamingSpanEventAggregator starting up')
|
|
34
|
-
this.stream.connect(this.runId)
|
|
34
|
+
this.stream.connect(this.runId, this.requestHeadersMap)
|
|
35
35
|
this.started = true
|
|
36
|
+
|
|
37
|
+
this.emit('started')
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
stop() {
|
|
@@ -43,6 +45,8 @@ class StreamingSpanEventAggregator extends Aggregator {
|
|
|
43
45
|
logger.trace('StreamingSpanEventAggregator stopping')
|
|
44
46
|
this.stream.disconnect()
|
|
45
47
|
this.started = false
|
|
48
|
+
|
|
49
|
+
this.emit('stopped')
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
send() {
|
|
@@ -83,6 +87,12 @@ class StreamingSpanEventAggregator extends Aggregator {
|
|
|
83
87
|
const span = StreamingSpanEvent.fromSegment(segment, parentId, isRoot)
|
|
84
88
|
this.stream.write(span)
|
|
85
89
|
}
|
|
90
|
+
|
|
91
|
+
reconfigure(config) {
|
|
92
|
+
super.reconfigure(config)
|
|
93
|
+
|
|
94
|
+
this.requestHeadersMap = config.request_headers_map
|
|
95
|
+
}
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
module.exports = StreamingSpanEventAggregator
|
|
@@ -98,7 +98,9 @@ class StreamingSpanEvent {
|
|
|
98
98
|
|
|
99
99
|
static fromSegment(segment, parentId = null, isRoot = false) {
|
|
100
100
|
const agentAttributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
|
|
101
|
-
|
|
101
|
+
|
|
102
|
+
const spanContext = segment.getSpanContext()
|
|
103
|
+
const customAttributes = spanContext.customAttributes.get(DESTINATIONS.SPAN_EVENT)
|
|
102
104
|
|
|
103
105
|
const transaction = segment.transaction
|
|
104
106
|
const traceId = transaction.traceId
|
|
@@ -112,7 +114,6 @@ class StreamingSpanEvent {
|
|
|
112
114
|
span = new StreamingSpanEvent(traceId, agentAttributes, customAttributes)
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
const spanContext = segment.getSpanContext()
|
|
116
117
|
for (const [key, value] of Object.entries(spanContext.intrinsicAttributes)) {
|
|
117
118
|
span.addIntrinsicAttribute(key, value)
|
|
118
119
|
}
|
package/lib/transaction/index.js
CHANGED
|
@@ -406,6 +406,20 @@ function finalizeNameFromUri(requestURL, statusCode) {
|
|
|
406
406
|
'request.parameters.' + key,
|
|
407
407
|
params[key]
|
|
408
408
|
)
|
|
409
|
+
|
|
410
|
+
const segment = this.agent.tracer.getSegment()
|
|
411
|
+
|
|
412
|
+
if (!segment) {
|
|
413
|
+
logger
|
|
414
|
+
.trace('Active segment not available, not adding request.parameters attribute for %s',
|
|
415
|
+
key)
|
|
416
|
+
} else {
|
|
417
|
+
segment.attributes.addAttribute(
|
|
418
|
+
DESTS.NONE,
|
|
419
|
+
'request.parameters.' + key,
|
|
420
|
+
params[key]
|
|
421
|
+
)
|
|
422
|
+
}
|
|
409
423
|
}
|
|
410
424
|
}
|
|
411
425
|
}, this)
|
|
@@ -466,6 +480,21 @@ Transaction.prototype._markAsWeb = function _markAsWeb(rawURL) {
|
|
|
466
480
|
'request.parameters.' + key,
|
|
467
481
|
params[key]
|
|
468
482
|
)
|
|
483
|
+
|
|
484
|
+
const segment = this.agent.tracer.getSegment()
|
|
485
|
+
|
|
486
|
+
if (!segment) {
|
|
487
|
+
logger
|
|
488
|
+
.trace(
|
|
489
|
+
'Active segment not available, not adding request.parameters span attribute for %s',
|
|
490
|
+
key)
|
|
491
|
+
} else {
|
|
492
|
+
segment.attributes.addAttribute(
|
|
493
|
+
DESTS.NONE,
|
|
494
|
+
'request.parameters.' + key,
|
|
495
|
+
params[key]
|
|
496
|
+
)
|
|
497
|
+
}
|
|
469
498
|
}
|
|
470
499
|
}
|
|
471
500
|
this.baseSegment.markAsWeb()
|
|
@@ -717,15 +746,15 @@ function _linkExceptionToSegment(exception) {
|
|
|
717
746
|
|
|
718
747
|
// Add error attributes to the span
|
|
719
748
|
const details = exception.getErrorDetails(config)
|
|
720
|
-
segment.
|
|
721
|
-
segment.
|
|
749
|
+
segment.addSpanAttribute('error.message', details.message)
|
|
750
|
+
segment.addSpanAttribute('error.class', details.type)
|
|
722
751
|
|
|
723
752
|
const isExpected =
|
|
724
753
|
errorHelper.isExpectedErrorClass(config, details.type) ||
|
|
725
754
|
errorHelper.isExpectedErrorMessage(config, details.type, details.message)
|
|
726
755
|
|
|
727
756
|
if (isExpected) {
|
|
728
|
-
segment.
|
|
757
|
+
segment.addSpanAttribute('error.expected', isExpected)
|
|
729
758
|
}
|
|
730
759
|
|
|
731
760
|
// Add the span/segment ID to the exception as agent attributes
|
|
@@ -1264,6 +1293,14 @@ function addRequestParameters(requestParameters) {
|
|
|
1264
1293
|
'request.parameters.' + key,
|
|
1265
1294
|
requestParameters[key]
|
|
1266
1295
|
)
|
|
1296
|
+
|
|
1297
|
+
const segment = this.baseSegment
|
|
1298
|
+
|
|
1299
|
+
segment.attributes.addAttribute(
|
|
1300
|
+
DESTS.NONE,
|
|
1301
|
+
'request.parameters.' + key,
|
|
1302
|
+
requestParameters[key]
|
|
1303
|
+
)
|
|
1267
1304
|
}
|
|
1268
1305
|
}
|
|
1269
1306
|
}
|
|
@@ -41,6 +41,8 @@ function Trace(transaction) {
|
|
|
41
41
|
'host.displayName',
|
|
42
42
|
displayName
|
|
43
43
|
)
|
|
44
|
+
|
|
45
|
+
this.displayName = displayName
|
|
44
46
|
}
|
|
45
47
|
this.domain = null
|
|
46
48
|
}
|
|
@@ -82,6 +84,26 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents() {
|
|
|
82
84
|
const spanAggregator = this.transaction.agent.spanEventAggregator
|
|
83
85
|
|
|
84
86
|
const children = this.root.getChildren()
|
|
87
|
+
|
|
88
|
+
if (children.length > 0) {
|
|
89
|
+
// At the point where these attributes are available, we only have a
|
|
90
|
+
// root span. Adding attributes to first non-root span here.
|
|
91
|
+
const attributeMap = {
|
|
92
|
+
'host.displayName': this.displayName,
|
|
93
|
+
'parent.type': this.transaction.parentType,
|
|
94
|
+
'parent.app': this.transaction.parentApp,
|
|
95
|
+
'parent.account': this.transaction.parentAcct,
|
|
96
|
+
'parent.transportType': this.transaction.parentTransportType,
|
|
97
|
+
'parent.transportDuration': this.transaction.parentTransportDuration
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const [key, value] of Object.entries(attributeMap)) {
|
|
101
|
+
if (value !== null) {
|
|
102
|
+
children[0].addSpanAttribute(key, value)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
85
107
|
for (let i = 0; i < children.length; ++i) {
|
|
86
108
|
toProcess.push(new DTTraceNode(children[i], this.transaction.parentSpanId, true))
|
|
87
109
|
}
|
|
@@ -6,7 +6,7 @@ const Timer = require('../../timer')
|
|
|
6
6
|
const urltils = require('../../util/urltils')
|
|
7
7
|
const hashes = require('../../util/hashes')
|
|
8
8
|
|
|
9
|
-
const {Attributes
|
|
9
|
+
const {Attributes} = require('../../attributes')
|
|
10
10
|
const ExclusiveCalculator = require('./exclusive-time-calculator')
|
|
11
11
|
const SpanContext = require('../../spans/span-context')
|
|
12
12
|
|
|
@@ -53,7 +53,6 @@ function TraceSegment(transaction, name, recorder) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
this.attributes = new Attributes(ATTRIBUTE_SCOPE)
|
|
56
|
-
this.customAttributes = new Attributes(ATTRIBUTE_SCOPE, MAXIMUM_CUSTOM_ATTRIBUTES)
|
|
57
56
|
|
|
58
57
|
this.children = []
|
|
59
58
|
|
|
@@ -99,13 +98,9 @@ function addAttribute(key, value, truncateExempt = false) {
|
|
|
99
98
|
)
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
TraceSegment.prototype.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
TraceSegment.prototype.addCustomSpanAttribute =
|
|
107
|
-
function addCustomSpanAttribute(key, value, truncateExempt = false) {
|
|
108
|
-
this.customAttributes.addAttribute(
|
|
101
|
+
TraceSegment.prototype.addSpanAttribute =
|
|
102
|
+
function addSpanAttribute(key, value, truncateExempt = false) {
|
|
103
|
+
this.attributes.addAttribute(
|
|
109
104
|
DESTINATIONS.SPAN_EVENT,
|
|
110
105
|
key,
|
|
111
106
|
value,
|
|
@@ -113,6 +108,18 @@ function addCustomSpanAttribute(key, value, truncateExempt = false) {
|
|
|
113
108
|
)
|
|
114
109
|
}
|
|
115
110
|
|
|
111
|
+
TraceSegment.prototype.addSpanAttributes =
|
|
112
|
+
function addSpanAttributes(attributes) {
|
|
113
|
+
this.attributes.addAttributes(
|
|
114
|
+
DESTINATIONS.SPAN_EVENT,
|
|
115
|
+
attributes
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
TraceSegment.prototype.getAttributes = function getAttributes() {
|
|
120
|
+
return this.attributes.get(DESTINATIONS.TRANS_SEGMENT)
|
|
121
|
+
}
|
|
122
|
+
|
|
116
123
|
TraceSegment.prototype.getSpanId = function getSpanId() {
|
|
117
124
|
const conf = this.transaction.agent.config
|
|
118
125
|
const enabled = conf.span_events.enabled && conf.distributed_tracing.enabled
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newrelic",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.10.0",
|
|
4
4
|
"author": "New Relic Node.js agent team <nodejs@newrelic.com>",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"contributors": [
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
"newrelic-naming-rules": "./bin/test-naming-rules.js"
|
|
143
143
|
},
|
|
144
144
|
"dependencies": {
|
|
145
|
-
"@grpc/grpc-js": "1.0.
|
|
145
|
+
"@grpc/grpc-js": "1.0.4",
|
|
146
146
|
"@grpc/proto-loader": "^0.5.4",
|
|
147
147
|
"@newrelic/aws-sdk": "^1.1.1",
|
|
148
148
|
"@newrelic/koa": "^3.0.0",
|