newrelic 9.13.0 → 9.14.1
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 +8 -0
- package/api.js +47 -0
- package/index.js +5 -6
- package/lib/errors/error-collector.js +6 -0
- package/lib/errors/index.js +37 -0
- package/lib/metrics/normalizer/tx_segment.js +21 -22
- package/lib/shimmer.js +44 -28
- package/lib/symbols.js +0 -1
- package/package.json +1 -1
package/NEWS.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
### v9.14.1 (2023-03-23)
|
|
2
|
+
|
|
3
|
+
* Restored assigning loaded version of agent to require.cache as `__NR_cache` instead of a symbol to properly detect attempts at loading agent twice.
|
|
4
|
+
|
|
5
|
+
### v9.14.0 (2023-03-23)
|
|
6
|
+
|
|
7
|
+
* Added new API function called `setErrorGroupCallback`, which provides a way for you to customize the `error.group.name` attribute of errors that are captured by the agent. This attribute controls how the Errors Inbox functionality groups similar errors together. To learn more about this function, please refer to our [example app](https://github.com/newrelic/newrelic-node-examples).
|
|
8
|
+
|
|
1
9
|
### v9.13.0 (2023-03-20)
|
|
2
10
|
|
|
3
11
|
* Updated http instrumentation to no longer remove the `x-new-relic-disable-dt` header when using AWS SDK v3. This was done to prevent the "The request signature we calculated does not match the signature you provided. Check your key and signing method." error from AWS SDK.
|
package/api.js
CHANGED
|
@@ -1739,4 +1739,51 @@ function _filterAttributes(attributes, name) {
|
|
|
1739
1739
|
return filteredAttributes
|
|
1740
1740
|
}
|
|
1741
1741
|
|
|
1742
|
+
/**
|
|
1743
|
+
* Function for adding a custom callback to generate Error Group names, which
|
|
1744
|
+
* will be used by the Errors Inbox to group similar errors together via the `error.group.name`
|
|
1745
|
+
* agent attribute.
|
|
1746
|
+
*
|
|
1747
|
+
* Provided functions must return a string, and receive an object as an argument,
|
|
1748
|
+
* which contains information related to the Error that occurred, and has the
|
|
1749
|
+
* following format:
|
|
1750
|
+
*
|
|
1751
|
+
* ```
|
|
1752
|
+
* {
|
|
1753
|
+
* customAttributes: object,
|
|
1754
|
+
* 'request.uri': string,
|
|
1755
|
+
* 'http.statusCode': string,
|
|
1756
|
+
* 'http.method': string,
|
|
1757
|
+
* error: Error,
|
|
1758
|
+
* 'error.expected': boolean
|
|
1759
|
+
* }
|
|
1760
|
+
* ```
|
|
1761
|
+
*
|
|
1762
|
+
* Calling this function multiple times will replace previously defined functions
|
|
1763
|
+
*
|
|
1764
|
+
* @param {Function} callback - callback function to generate `error.group.name` attribute
|
|
1765
|
+
* @example
|
|
1766
|
+
* function myCallback(metadata) {
|
|
1767
|
+
* if (metadata['http.statusCode'] === '400') {
|
|
1768
|
+
* return 'Bad User Input'
|
|
1769
|
+
* }
|
|
1770
|
+
* }
|
|
1771
|
+
* newrelic.setErrorGroupCallback(myCallback)
|
|
1772
|
+
*/
|
|
1773
|
+
API.prototype.setErrorGroupCallback = function setErrorGroupCallback(callback) {
|
|
1774
|
+
const metric = this.agent.metrics.getOrCreateMetric(
|
|
1775
|
+
NAMES.SUPPORTABILITY.API + '/setErrorGroupCallback'
|
|
1776
|
+
)
|
|
1777
|
+
metric.incrementCallCount()
|
|
1778
|
+
|
|
1779
|
+
if (!this.shim.isFunction(callback) || this.shim.isPromise(callback)) {
|
|
1780
|
+
logger.warn(
|
|
1781
|
+
'Error Group callback must be a synchronous function, Error Group attribute will not be added'
|
|
1782
|
+
)
|
|
1783
|
+
return
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
this.agent.errors.errorGroupCallback = callback
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1742
1789
|
module.exports = API
|
package/index.js
CHANGED
|
@@ -14,7 +14,6 @@ require('./lib/util/unwrapped-core')
|
|
|
14
14
|
|
|
15
15
|
const featureFlags = require('./lib/feature_flags').prerelease
|
|
16
16
|
const psemver = require('./lib/util/process-version')
|
|
17
|
-
const symbols = require('./lib/symbols')
|
|
18
17
|
let logger = require('./lib/logger') // Gets re-loaded after initialization.
|
|
19
18
|
|
|
20
19
|
const pkgJSON = require('./package.json')
|
|
@@ -24,15 +23,15 @@ logger.info(
|
|
|
24
23
|
process.version
|
|
25
24
|
)
|
|
26
25
|
|
|
27
|
-
if (require.cache
|
|
26
|
+
if (require.cache.__NR_cache) {
|
|
28
27
|
logger.warn(
|
|
29
28
|
'Attempting to load a second copy of newrelic from %s, using cache instead',
|
|
30
29
|
__dirname
|
|
31
30
|
)
|
|
32
|
-
if (require.cache
|
|
33
|
-
require.cache
|
|
31
|
+
if (require.cache.__NR_cache.agent) {
|
|
32
|
+
require.cache.__NR_cache.agent.recordSupportability('Agent/DoubleLoad')
|
|
34
33
|
}
|
|
35
|
-
module.exports = require.cache
|
|
34
|
+
module.exports = require.cache.__NR_cache
|
|
36
35
|
} else {
|
|
37
36
|
initialize()
|
|
38
37
|
}
|
|
@@ -99,7 +98,7 @@ function initialize() {
|
|
|
99
98
|
API = require('./stub_api')
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
require.cache
|
|
101
|
+
require.cache.__NR_cache = module.exports = new API(agent)
|
|
103
102
|
|
|
104
103
|
// If we loaded an agent, record a startup time for the agent.
|
|
105
104
|
// NOTE: Metrics are recorded in seconds, so divide the value by 1000.
|
|
@@ -36,6 +36,8 @@ class ErrorCollector {
|
|
|
36
36
|
this.seenStringsByTransaction = Object.create(null)
|
|
37
37
|
|
|
38
38
|
this.traceAggregator.on('starting error_data data send.', this._onSendErrorTrace.bind(this))
|
|
39
|
+
|
|
40
|
+
this.errorGroupCallback = null
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
_onSendErrorTrace() {
|
|
@@ -330,6 +332,10 @@ class ErrorCollector {
|
|
|
330
332
|
return false
|
|
331
333
|
}
|
|
332
334
|
|
|
335
|
+
if (this.errorGroupCallback) {
|
|
336
|
+
exception.errorGroupCallback = this.errorGroupCallback
|
|
337
|
+
}
|
|
338
|
+
|
|
333
339
|
const errorTrace = createError(transaction, exception, this.config)
|
|
334
340
|
this._maybeRecordErrorMetrics(errorTrace, transaction)
|
|
335
341
|
|
package/lib/errors/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
+
const logger = require('../logger').child({ component: 'errors_lib' })
|
|
8
9
|
const DESTINATIONS = require('../config/attribute-filter').DESTINATIONS
|
|
9
10
|
const props = require('../util/properties')
|
|
10
11
|
const urltils = require('../util/urltils')
|
|
@@ -25,6 +26,7 @@ class Exception {
|
|
|
25
26
|
this.customAttributes = customAttributes || {}
|
|
26
27
|
this.agentAttributes = agentAttributes || {}
|
|
27
28
|
this._expected = expected
|
|
29
|
+
this.errorGroupCallback = null
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
getErrorDetails(config) {
|
|
@@ -107,9 +109,44 @@ function createError(transaction, exception, config) {
|
|
|
107
109
|
params.intrinsics[ERROR_EXPECTED_PATH] =
|
|
108
110
|
exception._expected || errorHelper.isExpected(type, message, transaction, config, urltils)
|
|
109
111
|
|
|
112
|
+
maybeAddAgentAttributes(params, exception)
|
|
113
|
+
|
|
110
114
|
return [0, name, message, type, params]
|
|
111
115
|
}
|
|
112
116
|
|
|
117
|
+
function isValidErrorGroupOutput(output) {
|
|
118
|
+
return (typeof output === 'string' || output instanceof String) && output !== ''
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function maybeAddAgentAttributes(attributes, exception) {
|
|
122
|
+
if (exception.errorGroupCallback) {
|
|
123
|
+
const callbackInput = {
|
|
124
|
+
'error': exception.error,
|
|
125
|
+
'customAttributes': Object.assign({}, attributes.userAttributes),
|
|
126
|
+
'request.uri': attributes.agentAttributes['request.uri'],
|
|
127
|
+
'http.statusCode': attributes.agentAttributes['http.statusCode'],
|
|
128
|
+
'http.method': attributes.agentAttributes['request.method'],
|
|
129
|
+
'error.expected': attributes.intrinsics[ERROR_EXPECTED_PATH]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const callbackOutput = exception.errorGroupCallback(callbackInput)
|
|
134
|
+
|
|
135
|
+
if (!isValidErrorGroupOutput(callbackOutput)) {
|
|
136
|
+
logger.warn('Function provided via setErrorGroupCallback return value malformed')
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
attributes.agentAttributes['error.group.name'] = callbackOutput
|
|
141
|
+
} catch (err) {
|
|
142
|
+
logger.warn(
|
|
143
|
+
err,
|
|
144
|
+
'Function provided via setErrorGroupCallback failed, not generating `error.group.name`'
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
113
150
|
function maybeAddUserAttributes(userAttributes, exception, config) {
|
|
114
151
|
const customAttributes = exception.customAttributes
|
|
115
152
|
if (!config.high_security && config.api.custom_attributes_enabled && customAttributes) {
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
/* eslint sonarjs/cognitive-complexity: ["error", 17] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */
|
|
9
|
-
|
|
10
8
|
const logger = require('../../logger').child({ component: 'tx_segment_normalizer' })
|
|
11
9
|
|
|
12
10
|
module.exports = TxSegmentNormalizer
|
|
@@ -17,7 +15,7 @@ function TxSegmentNormalizer() {
|
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
17
|
* This normalize method is wicked. The best bet is to read the spec:
|
|
20
|
-
* https://
|
|
18
|
+
* https://source.datanerd.us/agents/agent-specs/blob/main/Metric-Name-Rules-PORTED.md
|
|
21
19
|
*
|
|
22
20
|
* A copy paste of the rules that were followed:
|
|
23
21
|
* 1. Find the first rule where the prefix key matches the prefix of the
|
|
@@ -31,11 +29,28 @@ function TxSegmentNormalizer() {
|
|
|
31
29
|
* 6. Join together the modified segments with slashes, and re-prepend the prefix.
|
|
32
30
|
*
|
|
33
31
|
* @param {string} path - The transaction metric path to normalize.
|
|
34
|
-
* @returns {
|
|
32
|
+
* @returns {{ignore: boolean, matched: boolean, value: string}} - The results of normalizing the given path.
|
|
35
33
|
*/
|
|
36
34
|
TxSegmentNormalizer.prototype.normalize = function normalize(path) {
|
|
37
35
|
let currentTerm
|
|
38
36
|
let prefix
|
|
37
|
+
let result
|
|
38
|
+
let prev
|
|
39
|
+
|
|
40
|
+
const normalizeParts = (part, idx, parts) => {
|
|
41
|
+
if (part === '' && idx + 1 === parts.length) {
|
|
42
|
+
return // if this is the last one, don't iterate
|
|
43
|
+
}
|
|
44
|
+
if (currentTerm.terms.indexOf(part) === -1) {
|
|
45
|
+
if (prev === '*') {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
result.push((prev = '*'))
|
|
49
|
+
} else {
|
|
50
|
+
result.push((prev = part))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
for (let i = 0; i < this.terms.length; i++) {
|
|
40
55
|
currentTerm = this.terms[i]
|
|
41
56
|
prefix = currentTerm.prefix
|
|
@@ -44,26 +59,10 @@ TxSegmentNormalizer.prototype.normalize = function normalize(path) {
|
|
|
44
59
|
}
|
|
45
60
|
const fragment = path.slice(prefix.length)
|
|
46
61
|
const parts = fragment.split('/')
|
|
47
|
-
|
|
48
|
-
let prev
|
|
49
|
-
let segment
|
|
50
|
-
|
|
51
|
-
for (let j = 0; j < parts.length; j++) {
|
|
52
|
-
segment = parts[j]
|
|
62
|
+
result = []
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
break
|
|
56
|
-
}
|
|
64
|
+
parts.forEach(normalizeParts)
|
|
57
65
|
|
|
58
|
-
if (currentTerm.terms.indexOf(segment) === -1) {
|
|
59
|
-
if (prev === '*') {
|
|
60
|
-
continue
|
|
61
|
-
}
|
|
62
|
-
result.push((prev = '*'))
|
|
63
|
-
} else {
|
|
64
|
-
result.push((prev = segment))
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
66
|
logger.trace('Normalizing %s because of rule: %s', path, currentTerm)
|
|
68
67
|
return {
|
|
69
68
|
matched: true, // To match MetricNormalizer
|
package/lib/shimmer.js
CHANGED
|
@@ -352,33 +352,15 @@ const shimmer = (module.exports = {
|
|
|
352
352
|
shimmer.unwrapMethod(Module, 'Module', '_load')
|
|
353
353
|
},
|
|
354
354
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
Object.
|
|
364
|
-
const core = CORE_INSTRUMENTATION[mojule]
|
|
365
|
-
const filePath = path.join(__dirname, 'instrumentation', 'core', core.file)
|
|
366
|
-
let uninstrumented = null
|
|
367
|
-
|
|
368
|
-
try {
|
|
369
|
-
uninstrumented = require(mojule)
|
|
370
|
-
} catch (err) {
|
|
371
|
-
logger.trace('Could not load core module %s got error %s', mojule, err)
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const shim = shims.createShimFromType(core.type, agent, mojule, mojule)
|
|
375
|
-
applyDebugState(shim, core)
|
|
376
|
-
_firstPartyInstrumentation(agent, filePath, shim, uninstrumented, mojule)
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
// Register all the first-party instrumentations.
|
|
380
|
-
Object.keys(INSTRUMENTATIONS).forEach(function forEachInstrumentation(moduleName) {
|
|
381
|
-
const instrInfo = INSTRUMENTATIONS[moduleName]
|
|
355
|
+
/**
|
|
356
|
+
* Registers all instrumentation for "first-party" libraries.
|
|
357
|
+
*
|
|
358
|
+
* This is all 3rd party libs with the exception of the domain library in Node.js core
|
|
359
|
+
*
|
|
360
|
+
* @param {object} agent NR agent
|
|
361
|
+
*/
|
|
362
|
+
registerFirstPartyInstrumentation(agent) {
|
|
363
|
+
for (const [moduleName, instrInfo] of Object.entries(INSTRUMENTATIONS)) {
|
|
382
364
|
if (instrInfo.module) {
|
|
383
365
|
// Because external instrumentations can change independent of
|
|
384
366
|
// the agent core, we don't want breakages in them to entirely
|
|
@@ -401,7 +383,7 @@ const shimmer = (module.exports = {
|
|
|
401
383
|
onRequire: _firstPartyInstrumentation.bind(null, agent, fileName)
|
|
402
384
|
})
|
|
403
385
|
}
|
|
404
|
-
}
|
|
386
|
+
}
|
|
405
387
|
|
|
406
388
|
// Even though domain is a core module we add it as a registered
|
|
407
389
|
// instrumentation to be lazy-loaded because we do not want to cause domain
|
|
@@ -414,6 +396,40 @@ const shimmer = (module.exports = {
|
|
|
414
396
|
})
|
|
415
397
|
},
|
|
416
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Registers all instrumentation for Node.js core libraries.
|
|
401
|
+
*
|
|
402
|
+
* @param {object} agent NR agent
|
|
403
|
+
*/
|
|
404
|
+
registerCoreInstrumentation(agent) {
|
|
405
|
+
// Instrument global.
|
|
406
|
+
const globalShim = new shims.Shim(agent, 'globals', 'globals')
|
|
407
|
+
applyDebugState(globalShim, global)
|
|
408
|
+
const globalsFilepath = path.join(__dirname, 'instrumentation', 'core', 'globals.js')
|
|
409
|
+
_firstPartyInstrumentation(agent, globalsFilepath, globalShim, global, 'globals')
|
|
410
|
+
|
|
411
|
+
// Instrument each of the core modules.
|
|
412
|
+
for (const [mojule, core] of Object.entries(CORE_INSTRUMENTATION)) {
|
|
413
|
+
const filePath = path.join(__dirname, 'instrumentation', 'core', core.file)
|
|
414
|
+
let uninstrumented = null
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
uninstrumented = require(mojule)
|
|
418
|
+
} catch (err) {
|
|
419
|
+
logger.trace('Could not load core module %s got error %s', mojule, err)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const shim = shims.createShimFromType(core.type, agent, mojule, mojule)
|
|
423
|
+
applyDebugState(shim, core)
|
|
424
|
+
_firstPartyInstrumentation(agent, filePath, shim, uninstrumented, mojule)
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
bootstrapInstrumentation: function bootstrapInstrumentation(agent) {
|
|
429
|
+
shimmer.registerCoreInstrumentation(agent)
|
|
430
|
+
shimmer.registerFirstPartyInstrumentation(agent)
|
|
431
|
+
},
|
|
432
|
+
|
|
417
433
|
registerInstrumentation: function registerInstrumentation(opts) {
|
|
418
434
|
if (!hasValidRegisterOptions(opts)) {
|
|
419
435
|
return
|
package/lib/symbols.js
CHANGED