newrelic 6.3.0 → 6.5.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/{.eslintrc → .eslintrc.js} +4 -2
- package/.travis.yml +0 -1
- package/NEWS.md +136 -0
- package/README.md +1 -0
- package/api.js +75 -4
- package/bin/publish-docs.sh +1 -1
- package/bin/travis-node.sh +4 -1
- package/index.js +3 -3
- package/lib/aggregators/base-aggregator.js +1 -1
- package/lib/attributes.js +15 -6
- package/lib/config/default.js +11 -1
- package/lib/config/env.js +3 -1
- package/lib/config/hsm.js +6 -1
- package/lib/config/index.js +29 -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/feature_flags.js +3 -3
- package/lib/header-processing.js +75 -0
- package/lib/instrumentation/core/child_process.js +12 -0
- package/lib/instrumentation/core/http-outbound.js +6 -28
- package/lib/instrumentation/core/http.js +100 -170
- package/lib/instrumentation/hapi/hapi-17.js +10 -3
- package/lib/metrics/names.js +1 -0
- package/lib/serverless/aws-lambda.js +79 -33
- package/lib/serverless/event-sources.json +128 -0
- package/lib/shim/transaction-shim.js +10 -33
- package/lib/spans/span-event-aggregator.js +2 -2
- package/lib/spans/span-event.js +33 -11
- package/lib/transaction/handle.js +73 -4
- package/lib/transaction/index.js +182 -34
- package/lib/transaction/trace/index.js +6 -5
- package/lib/transaction/trace/segment.js +12 -1
- package/lib/transaction/tracecontext.js +409 -163
- package/lib/util/get.js +16 -0
- package/lib/util/hashes.js +55 -6
- package/package.json +8 -10
- package/stub_api.js +5 -0
|
@@ -12,10 +12,7 @@ const urltils = require('../../util/urltils')
|
|
|
12
12
|
const properties = require('../../util/properties')
|
|
13
13
|
const psemver = require('../../util/process-version')
|
|
14
14
|
const headerAttributes = require('../../header-attributes')
|
|
15
|
-
const
|
|
16
|
-
= require('../../transaction/tracecontext').TRACE_CONTEXT_PARENT_HEADER
|
|
17
|
-
const TRACE_CONTEXT_STATE_HEADER
|
|
18
|
-
= require('../../transaction/tracecontext').TRACE_CONTEXT_STATE_HEADER
|
|
15
|
+
const headerProcessing = require('../../header-processing')
|
|
19
16
|
|
|
20
17
|
const NAMES = require('../../metrics/names')
|
|
21
18
|
const DESTS = require('../../config/attribute-filter').DESTINATIONS
|
|
@@ -26,15 +23,12 @@ const DESTS = require('../../config/attribute-filter').DESTINATIONS
|
|
|
26
23
|
*
|
|
27
24
|
*/
|
|
28
25
|
const SHOULD_WRAP_HTTPS = psemver.satisfies('>=9.0.0 || 8.9.0')
|
|
26
|
+
const SHOULD_FORMAT_ARGS = psemver.satisfies('>=10.0.0')
|
|
29
27
|
const NR_CONNECTION_PROP = '__NR__connection'
|
|
30
|
-
const REQUEST_HEADER = 'x-request-start'
|
|
31
|
-
const QUEUE_HEADER = 'x-queue-start'
|
|
32
28
|
const NEWRELIC_ID_HEADER = 'x-newrelic-id'
|
|
33
29
|
const NEWRELIC_APP_DATA_HEADER = 'x-newrelic-app-data'
|
|
34
|
-
const NEWRELIC_TRACE_HEADER = 'newrelic'
|
|
35
30
|
const NEWRELIC_TRANSACTION_HEADER = 'x-newrelic-transaction'
|
|
36
31
|
const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
|
|
37
|
-
const CONTENT_LENGTH_REGEX = /^Content-Length$/i
|
|
38
32
|
const TRANSACTION_INFO_KEY = '__NR_transactionInfo'
|
|
39
33
|
|
|
40
34
|
|
|
@@ -118,57 +112,14 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
|
|
|
118
112
|
'Applying user naming rules for RUM.')
|
|
119
113
|
transaction.applyUserNamingRules(request.url)
|
|
120
114
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
* Queue time is provided by certain providers by stamping the request
|
|
125
|
-
* header with the time the request arrived at the router.
|
|
126
|
-
*
|
|
127
|
-
* Units for queue time are
|
|
128
|
-
*/
|
|
129
|
-
var qtime = request.headers[REQUEST_HEADER] || request.headers[QUEUE_HEADER]
|
|
130
|
-
if (qtime) {
|
|
131
|
-
var split = qtime.split('=')
|
|
132
|
-
if (split.length > 1) {
|
|
133
|
-
qtime = split[1]
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
var start = parseFloat(qtime)
|
|
137
|
-
|
|
138
|
-
if (isNaN(start)) {
|
|
139
|
-
logger.warn('Queue time header parsed as NaN (%s)', qtime)
|
|
140
|
-
} else {
|
|
141
|
-
// nano seconds
|
|
142
|
-
if (start > 1e18) start = start / 1e6
|
|
143
|
-
// micro seconds
|
|
144
|
-
else if (start > 1e15) start = start / 1e3
|
|
145
|
-
// seconds
|
|
146
|
-
else if (start < 1e12) start = start * 1e3
|
|
147
|
-
|
|
148
|
-
transaction.queueTime = Date.now() - start
|
|
149
|
-
}
|
|
115
|
+
const queueTimeStamp = headerProcessing.getQueueTime(logger, request.headers)
|
|
116
|
+
if (queueTimeStamp) {
|
|
117
|
+
transaction.queueTime = Date.now() - queueTimeStamp
|
|
150
118
|
}
|
|
119
|
+
|
|
151
120
|
if (agent.config.distributed_tracing.enabled) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const tracestate = request.headers[TRACE_CONTEXT_STATE_HEADER]
|
|
155
|
-
if (traceparent && tracestate) {
|
|
156
|
-
logger.trace(
|
|
157
|
-
'Accepting trace context payload for transaction %s',
|
|
158
|
-
transaction.id
|
|
159
|
-
)
|
|
160
|
-
transaction.traceContext.acceptTraceContextPayload(traceparent, tracestate)
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
const payload = request.headers[NEWRELIC_TRACE_HEADER]
|
|
164
|
-
if (payload) {
|
|
165
|
-
logger.trace(
|
|
166
|
-
'Accepting distributed trace payload for transaction %s',
|
|
167
|
-
transaction.id
|
|
168
|
-
)
|
|
169
|
-
transaction.acceptDistributedTracePayload(payload, transport)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
121
|
+
// Node http headers are automatically lowercase
|
|
122
|
+
transaction.acceptDistributedTraceHeaders(transport, request.headers)
|
|
172
123
|
} else if (agent.config.cross_application_tracer.enabled) {
|
|
173
124
|
var encKey = agent.config.encoding_key
|
|
174
125
|
var incomingCatId = request.headers[NEWRELIC_ID_HEADER]
|
|
@@ -202,6 +153,10 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
|
|
|
202
153
|
|
|
203
154
|
if (response) {
|
|
204
155
|
if (response.statusCode != null) {
|
|
156
|
+
/*
|
|
157
|
+
TODO: remove with next major release
|
|
158
|
+
httpResponseCode attribute is deprecated
|
|
159
|
+
*/
|
|
205
160
|
var responseCode = String(response.statusCode)
|
|
206
161
|
transaction.trace.attributes.addAttribute(
|
|
207
162
|
DESTS.TRANS_COMMON,
|
|
@@ -210,6 +165,16 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
|
|
|
210
165
|
)
|
|
211
166
|
|
|
212
167
|
if (/^\d+$/.test(responseCode)) {
|
|
168
|
+
transaction.trace.attributes.addAttribute(
|
|
169
|
+
DESTS.TRANS_COMMON,
|
|
170
|
+
'http.statusCode',
|
|
171
|
+
responseCode
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
/*
|
|
175
|
+
TODO: remove with next major release
|
|
176
|
+
response.status attribute is deprecated
|
|
177
|
+
*/
|
|
213
178
|
transaction.trace.attributes.addAttribute(
|
|
214
179
|
DESTS.TRANS_COMMON,
|
|
215
180
|
'response.status',
|
|
@@ -217,7 +182,18 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
|
|
|
217
182
|
)
|
|
218
183
|
}
|
|
219
184
|
}
|
|
185
|
+
|
|
220
186
|
if (response.statusMessage !== undefined) {
|
|
187
|
+
transaction.trace.attributes.addAttribute(
|
|
188
|
+
DESTS.TRANS_COMMON,
|
|
189
|
+
'http.statusText',
|
|
190
|
+
response.statusMessage
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
/*
|
|
194
|
+
TODO: remove with next major release
|
|
195
|
+
httpResponseMessage attribute is deprecated
|
|
196
|
+
*/
|
|
221
197
|
transaction.trace.attributes.addAttribute(
|
|
222
198
|
DESTS.TRANS_COMMON,
|
|
223
199
|
'httpResponseMessage',
|
|
@@ -225,8 +201,7 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
|
|
|
225
201
|
)
|
|
226
202
|
}
|
|
227
203
|
|
|
228
|
-
|
|
229
|
-
var headers = (response.getHeaders && response.getHeaders()) || response._headers
|
|
204
|
+
var headers = response.getHeaders()
|
|
230
205
|
if (headers) {
|
|
231
206
|
headerAttributes.collectResponseHeaders(headers, transaction)
|
|
232
207
|
}
|
|
@@ -379,25 +354,12 @@ function wrapWriteHead(agent, writeHead) {
|
|
|
379
354
|
var new_headers = arguments[arguments.length - 1]
|
|
380
355
|
|
|
381
356
|
if (typeof new_headers === 'object') {
|
|
382
|
-
|
|
383
|
-
if (CONTENT_LENGTH_REGEX.test(header)) {
|
|
384
|
-
contentLength = new_headers[header]
|
|
385
|
-
break
|
|
386
|
-
}
|
|
387
|
-
}
|
|
357
|
+
contentLength = headerProcessing.getContentLengthFromHeaders(new_headers)
|
|
388
358
|
}
|
|
389
359
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
// Outbound headers can be capitalized in any way, use regex instead
|
|
394
|
-
// of direct lookup.
|
|
395
|
-
for (var userHeader in this._headers) { // jshint ignore: line
|
|
396
|
-
if (CONTENT_LENGTH_REGEX.test(userHeader)) {
|
|
397
|
-
contentLength = this._headers[userHeader]
|
|
398
|
-
break
|
|
399
|
-
}
|
|
400
|
-
}
|
|
360
|
+
const currentHeaders = this.getHeaders()
|
|
361
|
+
if (contentLength === -1 && currentHeaders) {
|
|
362
|
+
contentLength = headerProcessing.getContentLengthFromHeaders(currentHeaders)
|
|
401
363
|
}
|
|
402
364
|
// Stored on the tx so we can push a metric with this time instead of
|
|
403
365
|
// actual duration.
|
|
@@ -434,8 +396,60 @@ function wrapWriteHead(agent, writeHead) {
|
|
|
434
396
|
}
|
|
435
397
|
}
|
|
436
398
|
|
|
399
|
+
// Taken from the Node code base, internal/url.js
|
|
400
|
+
function urlToOptions(_url) {
|
|
401
|
+
const options = {
|
|
402
|
+
protocol: _url.protocol,
|
|
403
|
+
hostname: typeof _url.hostname === 'string' && _url.hostname.startsWith('[') ?
|
|
404
|
+
_url.hostname.slice(1, -1) :
|
|
405
|
+
_url.hostname,
|
|
406
|
+
hash: _url.hash,
|
|
407
|
+
search: _url.search,
|
|
408
|
+
pathname: _url.pathname,
|
|
409
|
+
path: `${_url.pathname || ''}${_url.search || ''}`,
|
|
410
|
+
href: _url.href
|
|
411
|
+
}
|
|
412
|
+
if (_url.port !== '') {
|
|
413
|
+
options.port = Number(_url.port)
|
|
414
|
+
}
|
|
415
|
+
if (_url.username || _url.password) {
|
|
416
|
+
options.auth = `${_url.username}:${_url.password}`
|
|
417
|
+
}
|
|
418
|
+
return options
|
|
419
|
+
}
|
|
420
|
+
|
|
437
421
|
function wrapRequest(agent, request) {
|
|
438
|
-
|
|
422
|
+
// URL as a global was not added until Node v10
|
|
423
|
+
const URLClass = global.URL ? global.URL : url.URL
|
|
424
|
+
return function wrappedRequest(input, options, cb) {
|
|
425
|
+
let reqArgs = arguments
|
|
426
|
+
if (SHOULD_FORMAT_ARGS) {
|
|
427
|
+
// Support new function signatures in Node v10+. If the first argument is a URL,
|
|
428
|
+
// merge it into the options object. This code is copied from Node internals.
|
|
429
|
+
if (typeof input === 'string') {
|
|
430
|
+
const urlStr = input
|
|
431
|
+
input = urlToOptions(new URLClass(urlStr))
|
|
432
|
+
} else if (input.constructor && input.constructor.name === 'URL') {
|
|
433
|
+
input = urlToOptions(input)
|
|
434
|
+
} else {
|
|
435
|
+
cb = options
|
|
436
|
+
options = input
|
|
437
|
+
input = null
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (typeof options === 'function') {
|
|
441
|
+
cb = options
|
|
442
|
+
options = input || {}
|
|
443
|
+
} else {
|
|
444
|
+
options = Object.assign(input || {}, options)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
reqArgs = [options, cb]
|
|
448
|
+
} else {
|
|
449
|
+
// Node v8 function signature had options as the first argument
|
|
450
|
+
options = input
|
|
451
|
+
}
|
|
452
|
+
|
|
439
453
|
// Don't pollute metrics and calls with NR connections
|
|
440
454
|
const internalOnly = options && options[NR_CONNECTION_PROP]
|
|
441
455
|
if (internalOnly) {
|
|
@@ -453,13 +467,12 @@ function wrapRequest(agent, request) {
|
|
|
453
467
|
logOpts.port
|
|
454
468
|
)
|
|
455
469
|
}
|
|
456
|
-
return request.apply(this,
|
|
470
|
+
return request.apply(this, reqArgs)
|
|
457
471
|
}
|
|
458
472
|
|
|
459
|
-
const args = agent.tracer.slice(
|
|
473
|
+
const args = agent.tracer.slice(reqArgs)
|
|
460
474
|
const context = this
|
|
461
475
|
|
|
462
|
-
// hostname & port logic pulled directly from node's 0.10 lib/http.js
|
|
463
476
|
return instrumentOutbound(agent, options, function makeRequest(opts) {
|
|
464
477
|
args[0] = opts
|
|
465
478
|
return request.apply(context, args)
|
|
@@ -467,28 +480,6 @@ function wrapRequest(agent, request) {
|
|
|
467
480
|
}
|
|
468
481
|
}
|
|
469
482
|
|
|
470
|
-
function wrapLegacyRequest(agent, request) {
|
|
471
|
-
return function wrappedLegacyRequest(method, path, headers) {
|
|
472
|
-
var makeRequest = request.bind(this, method, path, headers)
|
|
473
|
-
|
|
474
|
-
if (agent.tracer.getTransaction()) {
|
|
475
|
-
return instrumentOutbound(agent, this, makeRequest)
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
logger.trace('No transaction, not recording external to %s:%s', this.host, this.port)
|
|
479
|
-
return makeRequest()
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function wrapLegacyClient(agent, proto) {
|
|
484
|
-
shimmer.wrapMethod(
|
|
485
|
-
proto,
|
|
486
|
-
'http.Client.prototype',
|
|
487
|
-
'request',
|
|
488
|
-
wrapLegacyRequest.bind(null, agent)
|
|
489
|
-
)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
483
|
module.exports = function initialize(agent, http, moduleName) {
|
|
493
484
|
if (!http) {
|
|
494
485
|
logger.debug('Did not get http module, not instrumenting!')
|
|
@@ -545,7 +536,7 @@ module.exports = function initialize(agent, http, moduleName) {
|
|
|
545
536
|
// change originally also appeared in 8.9.0 but was reverted in 8.9.1.
|
|
546
537
|
//
|
|
547
538
|
// TODO: Remove `SHOULD_WRAP_HTTPS` after deprecating Node <9.
|
|
548
|
-
if (SHOULD_WRAP_HTTPS ||
|
|
539
|
+
if (SHOULD_WRAP_HTTPS || !IS_HTTPS) {
|
|
549
540
|
shimmer.wrapMethod(
|
|
550
541
|
http,
|
|
551
542
|
'http',
|
|
@@ -553,21 +544,12 @@ module.exports = function initialize(agent, http, moduleName) {
|
|
|
553
544
|
wrapRequest.bind(null, agent)
|
|
554
545
|
)
|
|
555
546
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
// instrumenting external requests.
|
|
563
|
-
if (!IS_HTTPS && psemver.satisfies('>=8')) {
|
|
564
|
-
shimmer.wrapMethod(
|
|
565
|
-
http,
|
|
566
|
-
'http',
|
|
567
|
-
'get',
|
|
568
|
-
wrapRequest.bind(null, agent)
|
|
569
|
-
)
|
|
570
|
-
}
|
|
547
|
+
shimmer.wrapMethod(
|
|
548
|
+
http,
|
|
549
|
+
'http',
|
|
550
|
+
'get',
|
|
551
|
+
wrapRequest.bind(null, agent)
|
|
552
|
+
)
|
|
571
553
|
}
|
|
572
554
|
|
|
573
555
|
shimmer.wrapMethod(
|
|
@@ -592,58 +574,6 @@ module.exports = function initialize(agent, http, moduleName) {
|
|
|
592
574
|
}
|
|
593
575
|
}
|
|
594
576
|
)
|
|
595
|
-
|
|
596
|
-
// http.Client is deprecated, but still in use
|
|
597
|
-
// TODO: Remove this once Node <7 is deprecated.
|
|
598
|
-
var DeprecatedClient, deprecatedCreateClient
|
|
599
|
-
function clearGetters() {
|
|
600
|
-
if (DeprecatedClient) {
|
|
601
|
-
delete http.Client
|
|
602
|
-
http.Client = DeprecatedClient
|
|
603
|
-
}
|
|
604
|
-
if (deprecatedCreateClient) {
|
|
605
|
-
delete http.createClient
|
|
606
|
-
http.createClient = deprecatedCreateClient
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// TODO: Remove this once Node <7 is deprecated.
|
|
611
|
-
DeprecatedClient = shimmer.wrapDeprecated(
|
|
612
|
-
http,
|
|
613
|
-
'http',
|
|
614
|
-
'Client',
|
|
615
|
-
{
|
|
616
|
-
get: function get() {
|
|
617
|
-
var example = new DeprecatedClient(80, 'localhost')
|
|
618
|
-
wrapLegacyClient(agent, example.constructor.prototype)
|
|
619
|
-
clearGetters()
|
|
620
|
-
|
|
621
|
-
return DeprecatedClient
|
|
622
|
-
},
|
|
623
|
-
set: function set(NewClient) {
|
|
624
|
-
DeprecatedClient = NewClient
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
)
|
|
628
|
-
|
|
629
|
-
// TODO: Remove this once Node <7 is deprecated.
|
|
630
|
-
deprecatedCreateClient = shimmer.wrapDeprecated(
|
|
631
|
-
http,
|
|
632
|
-
'http',
|
|
633
|
-
'createClient',
|
|
634
|
-
{
|
|
635
|
-
get: function get() {
|
|
636
|
-
var example = deprecatedCreateClient(80, 'localhost')
|
|
637
|
-
wrapLegacyClient(agent, example.constructor.prototype)
|
|
638
|
-
clearGetters()
|
|
639
|
-
|
|
640
|
-
return deprecatedCreateClient
|
|
641
|
-
},
|
|
642
|
-
set: function set(newCreateClient) {
|
|
643
|
-
deprecatedCreateClient = newCreateClient
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
)
|
|
647
577
|
}
|
|
648
578
|
|
|
649
579
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const shared = require('./shared')
|
|
4
|
+
const record = require('../../metrics/recorders/generic')
|
|
4
5
|
|
|
5
6
|
// TODO: abstract and consolidate mostly-shared hapi functionality
|
|
6
7
|
module.exports = function initialize(agent, hapi, moduleName, shim) {
|
|
@@ -154,14 +155,20 @@ function wrapPreHandlers(shim, container, path) {
|
|
|
154
155
|
}
|
|
155
156
|
return container
|
|
156
157
|
} else if (shim.isFunction(container)) {
|
|
157
|
-
return
|
|
158
|
+
return wrapPreHandler(shim, container, path)
|
|
158
159
|
} else if (container.method && shim.isFunction(container.method)) {
|
|
159
160
|
return shim.wrap(container, 'method', function wrapHandler(shim, handler) {
|
|
160
|
-
return
|
|
161
|
+
return wrapPreHandler(shim, handler, path)
|
|
161
162
|
})
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
function wrapPreHandler(shim, container, path) {
|
|
167
|
+
return shim.record(container, (shim) => {
|
|
168
|
+
return {name: [shim.HAPI, ' pre handler: ','(',path,')'].join(''), recorder: record}
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
165
172
|
function wrapRouteHandler(shim, handler, path) {
|
|
166
173
|
return shim.recordMiddleware(handler, {
|
|
167
174
|
route: path,
|
package/lib/metrics/names.js
CHANGED
|
@@ -11,6 +11,7 @@ const SUPPORTABILITY = {
|
|
|
11
11
|
UNINSTRUMENTED: 'Supportability/Uninstrumented',
|
|
12
12
|
EVENTS: 'Supportability/Events',
|
|
13
13
|
API: 'Supportability/API',
|
|
14
|
+
TRANSACTION_API: 'Supportability/API/Transaction',
|
|
14
15
|
UTILIZATION: 'Supportability/utilization',
|
|
15
16
|
DEPENDENCIES: 'Supportability/InstalledDependencies',
|
|
16
17
|
NODEJS: 'Supportability/Nodejs',
|
|
@@ -2,22 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
const apiGateway = require('./api-gateway')
|
|
4
4
|
const headerAttributes = require('../header-attributes')
|
|
5
|
+
const get = require('../util/get')
|
|
5
6
|
const logger = require('../logger').child({component: 'aws-lambda'})
|
|
6
7
|
const recordBackground = require('../metrics/recorders/other')
|
|
7
8
|
const recordWeb = require('../metrics/recorders/http')
|
|
8
9
|
const TransactionShim = require('../shim/transaction-shim')
|
|
9
10
|
const urltils = require('../util/urltils')
|
|
10
|
-
const TRACE_CONTEXT_PARENT_HEADER
|
|
11
|
-
= require('../transaction/tracecontext').TRACE_CONTEXT_PARENT_HEADER
|
|
12
|
-
const TRACE_CONTEXT_STATE_HEADER
|
|
13
|
-
= require('../transaction/tracecontext').TRACE_CONTEXT_STATE_HEADER
|
|
14
11
|
|
|
15
12
|
// CONSTANTS
|
|
16
13
|
const ATTR_DEST = require('../config/attribute-filter').DESTINATIONS
|
|
17
14
|
const COLD_START_KEY = 'aws.lambda.coldStart'
|
|
18
|
-
const
|
|
15
|
+
const EVENT_SOURCE_PREFIX = 'aws.lambda.eventSource'
|
|
16
|
+
const EVENT_SOURCE_ARN_KEY = `${EVENT_SOURCE_PREFIX}.arn`
|
|
17
|
+
const EVENT_SOURCE_TYPE_KEY = `${EVENT_SOURCE_PREFIX}.eventType`
|
|
19
18
|
const NAMES = require('../metrics/names')
|
|
20
19
|
|
|
20
|
+
const EVENT_SOURCE_INFO = require('./event-sources')
|
|
21
|
+
|
|
21
22
|
// A function with no references used to stub out closures
|
|
22
23
|
function cleanClosure() {}
|
|
23
24
|
|
|
@@ -38,13 +39,27 @@ class AwsLambda {
|
|
|
38
39
|
this.shim = new TransactionShim(agent, 'aws-lambda')
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
// FOR TESTING
|
|
42
|
+
// FOR TESTING PURPOSES ONLY
|
|
42
43
|
_resetModuleState() {
|
|
43
44
|
patchCalled = false
|
|
44
45
|
coldStartRecorded = false
|
|
45
46
|
transactionEnders = []
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
_detectEventType(event) {
|
|
50
|
+
const pathMatch = (obj, path) => {
|
|
51
|
+
return get(obj, path, null) !== null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const typeInfo of Object.values(EVENT_SOURCE_INFO)) {
|
|
55
|
+
if (typeInfo.required_keys.every((path) => pathMatch(event, path))) {
|
|
56
|
+
return typeInfo
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
patchLambdaHandler(handler) {
|
|
49
64
|
const awsLambda = this
|
|
50
65
|
const shim = this.shim
|
|
@@ -161,13 +176,34 @@ class AwsLambda {
|
|
|
161
176
|
|
|
162
177
|
segment.start()
|
|
163
178
|
|
|
164
|
-
let res
|
|
179
|
+
let res
|
|
180
|
+
try {
|
|
181
|
+
res = shim.applySegment(handler, segment, false, this, args)
|
|
182
|
+
} catch (err) {
|
|
183
|
+
uncaughtException = err
|
|
184
|
+
txnEnder()
|
|
185
|
+
throw err
|
|
186
|
+
}
|
|
165
187
|
if (shim.isPromise(res)) {
|
|
166
|
-
res =
|
|
188
|
+
res = lambdaInterceptPromise(res, txnEnder)
|
|
167
189
|
}
|
|
168
190
|
return res
|
|
169
191
|
}
|
|
170
192
|
|
|
193
|
+
// In order to capture error events
|
|
194
|
+
// we need to store the error in uncaughtException
|
|
195
|
+
// otherwise the transaction will end before they are captured
|
|
196
|
+
function lambdaInterceptPromise(prom, cb) {
|
|
197
|
+
return prom.then(function onThen(arg) {
|
|
198
|
+
cb()
|
|
199
|
+
return arg
|
|
200
|
+
}, function onCatch(err) {
|
|
201
|
+
uncaughtException = err
|
|
202
|
+
cb()
|
|
203
|
+
throw err // This is not our error, just rethrowing the promise rejection.
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
171
207
|
function wrapCallbackAndCaptureError(transaction, txnEnder, cb, processResult) {
|
|
172
208
|
return function wrappedCallback() {
|
|
173
209
|
let err = arguments[0]
|
|
@@ -195,6 +231,22 @@ class AwsLambda {
|
|
|
195
231
|
'aws.requestId': context.awsRequestId
|
|
196
232
|
}
|
|
197
233
|
|
|
234
|
+
const eventSourceInfo = this._detectEventType(event)
|
|
235
|
+
|
|
236
|
+
if (eventSourceInfo) {
|
|
237
|
+
attributes[EVENT_SOURCE_TYPE_KEY] = eventSourceInfo.name
|
|
238
|
+
|
|
239
|
+
for (const key of Object.keys(eventSourceInfo.attributes)) {
|
|
240
|
+
const value = get(event, eventSourceInfo.attributes[key], null)
|
|
241
|
+
|
|
242
|
+
if (value === null) {
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
attributes[key] = value
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
198
250
|
setEventSourceAttributes(event, attributes)
|
|
199
251
|
|
|
200
252
|
if (!coldStartRecorded) {
|
|
@@ -223,6 +275,11 @@ function setEventSourceAttributes(event, attributes) {
|
|
|
223
275
|
} else if (event.records && event.deliveryStreamArn) {
|
|
224
276
|
// Kinesis Firehose
|
|
225
277
|
attributes[EVENT_SOURCE_ARN_KEY] = event.deliveryStreamArn
|
|
278
|
+
} else if (event.requestContext && event.requestContext.elb &&
|
|
279
|
+
event.requestContext.elb.targetGroupArn) {
|
|
280
|
+
attributes[EVENT_SOURCE_ARN_KEY] = event.requestContext.elb.targetGroupArn
|
|
281
|
+
} else if (event.resources && event.resources[0]) {
|
|
282
|
+
attributes[EVENT_SOURCE_ARN_KEY] = event.resources[0]
|
|
226
283
|
} else {
|
|
227
284
|
logger.trace('Unable to determine ARN for event type.', event)
|
|
228
285
|
}
|
|
@@ -263,34 +320,23 @@ function setWebRequest(shim, transaction, request) {
|
|
|
263
320
|
headerAttributes.collectRequestHeaders(request.headers, transaction)
|
|
264
321
|
|
|
265
322
|
if (shim.agent.config.distributed_tracing.enabled) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (traceparent && tracestate) {
|
|
271
|
-
logger.trace(
|
|
272
|
-
'Accepting trace context payload for transaction %s',
|
|
273
|
-
transaction.id
|
|
274
|
-
)
|
|
275
|
-
transaction.acceptTraceContextPayload(traceparent, tracestate)
|
|
276
|
-
}
|
|
277
|
-
} else {
|
|
278
|
-
// These are the three cases specified in the DT spec
|
|
279
|
-
// https://source.datanerd.us/agents/agent-specs/blob/master/Distributed-Tracing.md#supported-transports
|
|
280
|
-
const payload = request.headers.newrelic || request.headers.NEWRELIC ||
|
|
281
|
-
request.headers.Newrelic
|
|
282
|
-
|
|
283
|
-
if (payload) {
|
|
284
|
-
logger.trace(
|
|
285
|
-
'Accepting distributed trace payload for transaction %s',
|
|
286
|
-
transaction.id
|
|
287
|
-
)
|
|
288
|
-
transaction.acceptDistributedTracePayload(payload, request.transportType)
|
|
289
|
-
}
|
|
290
|
-
}
|
|
323
|
+
const lowercaseHeaders = lowercaseObjectKeys(request.headers)
|
|
324
|
+
|
|
325
|
+
const transportType = request.transportType && request.transportType.toUpperCase()
|
|
326
|
+
transaction.acceptDistributedTraceHeaders(transportType, lowercaseHeaders)
|
|
291
327
|
}
|
|
292
328
|
}
|
|
293
329
|
|
|
330
|
+
function lowercaseObjectKeys(original) {
|
|
331
|
+
const lowercaseObject = Object.keys(original)
|
|
332
|
+
.reduce((destination, key) => {
|
|
333
|
+
destination[key.toLowerCase()] = original[key]
|
|
334
|
+
return destination
|
|
335
|
+
}, {})
|
|
336
|
+
|
|
337
|
+
return lowercaseObject
|
|
338
|
+
}
|
|
339
|
+
|
|
294
340
|
function endTransaction(transaction, enderIndex) {
|
|
295
341
|
if (transactionEnders[enderIndex] === cleanClosure) {
|
|
296
342
|
// In the case where we have already been called, we return early. There may be a
|