newrelic 5.13.0 → 6.2.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/.travis.yml +20 -21
- package/NEWS.md +166 -0
- package/README.md +10 -10
- package/api.js +84 -2
- package/bin/run-versioned-tests.sh +3 -1
- package/bin/ssl.sh +43 -1
- package/bin/travis-node.sh +51 -0
- package/bin/travis-setup.sh +0 -4
- package/lib/agent.js +148 -315
- package/lib/aggregators/base-aggregator.js +3 -12
- package/lib/collector/api.js +2 -1
- package/lib/collector/facts.js +1 -1
- package/lib/config/default.js +8 -11
- package/lib/config/env.js +19 -1
- package/lib/config/index.js +52 -8
- package/lib/injector.js +161 -0
- package/lib/instrumentation/bluebird.js +4 -3
- package/lib/instrumentation/core/http-outbound.js +1 -0
- package/lib/instrumentation/mysql.js +10 -2
- package/lib/instrumentations.js +1 -1
- package/lib/shimmer.js +153 -5
- package/lib/spans/span-event-aggregator.js +1 -1
- package/lib/spans/span-event.js +1 -1
- package/lib/transaction/handle.js +11 -0
- package/lib/transaction/index.js +14 -3
- package/lib/transaction/trace/segment.js +10 -0
- package/lib/utilization/common.js +11 -4
- package/package.json +14 -11
- package/stub_api.js +8 -0
- package/lib/event-aggregator.js +0 -94
- package/lib/harvest.js +0 -718
- package/lib/instrumentation/aws-sdk.js +0 -25
package/lib/config/env.js
CHANGED
|
@@ -51,6 +51,11 @@ const ENV_MAPPING = {
|
|
|
51
51
|
include: 'NEW_RELIC_TRANSACTION_EVENTS_ATTRIBUTES_INCLUDE'
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
|
+
diagnostics: {
|
|
55
|
+
code_injector: {
|
|
56
|
+
enabled: 'NEW_RELIC_DIAGNOSTICS_CODE_INJECTOR_ENABLED'
|
|
57
|
+
}
|
|
58
|
+
},
|
|
54
59
|
transaction_tracer: {
|
|
55
60
|
attributes: {
|
|
56
61
|
enabled: 'NEW_RELIC_TRANSACTION_TRACER_ATTRIBUTES_ENABLED',
|
|
@@ -139,6 +144,11 @@ const ENV_MAPPING = {
|
|
|
139
144
|
serverless_mode: {
|
|
140
145
|
enabled: 'NEW_RELIC_SERVERLESS_MODE_ENABLED'
|
|
141
146
|
},
|
|
147
|
+
plugins: {
|
|
148
|
+
native_metrics: {
|
|
149
|
+
enabled: 'NEW_RELIC_NATIVE_METRICS_ENABLED'
|
|
150
|
+
}
|
|
151
|
+
},
|
|
142
152
|
trusted_account_key: 'NEW_RELIC_TRUSTED_ACCOUNT_KEY',
|
|
143
153
|
primary_application_id: 'NEW_RELIC_PRIMARY_APPLICATION_ID',
|
|
144
154
|
account_id: 'NEW_RELIC_ACCOUNT_ID'
|
|
@@ -169,6 +179,11 @@ const LIST_VARS = new Set([
|
|
|
169
179
|
'NEW_RELIC_AUDIT_LOG_ENDPOINTS'
|
|
170
180
|
])
|
|
171
181
|
|
|
182
|
+
// Mapping to customize the split delimiter for the LIST_VARS
|
|
183
|
+
const LIST_VARS_CUSTOM_DELIMITERS = {
|
|
184
|
+
'NEW_RELIC_APP_NAME': /;|,/
|
|
185
|
+
}
|
|
186
|
+
|
|
172
187
|
// Values in object lists are comma-delimited object literals
|
|
173
188
|
const OBJECT_LIST_VARS = new Set([
|
|
174
189
|
'NEW_RELIC_NAMING_RULES'
|
|
@@ -186,6 +201,7 @@ const BOOLEAN_VARS = new Set([
|
|
|
186
201
|
'NEW_RELIC_TRACER_ENABLED',
|
|
187
202
|
'NEW_RELIC_TRANSACTION_TRACER_ATTRIBUTES_ENABLED',
|
|
188
203
|
'NEW_RELIC_TRANSACTION_EVENTS_ATTRIBUTES_ENABLED',
|
|
204
|
+
'NEW_RELIC_DIAGNOSTICS_CODE_INJECTOR_ENABLED',
|
|
189
205
|
'NEW_RELIC_TRANSACTION_SEGMENTS_ATTRIBUTES_ENABLED',
|
|
190
206
|
'NEW_RELIC_DEBUG_METRICS',
|
|
191
207
|
'NEW_RELIC_DEBUG_TRACER',
|
|
@@ -210,7 +226,8 @@ const BOOLEAN_VARS = new Set([
|
|
|
210
226
|
'NEW_RELIC_UTILIZATION_DETECT_DOCKER',
|
|
211
227
|
'NEW_RELIC_UTILIZATION_DETECT_GCP',
|
|
212
228
|
'NEW_RELIC_UTILIZATION_DETECT_KUBERNETES',
|
|
213
|
-
'NEW_RELIC_SERVERLESS_MODE_ENABLED'
|
|
229
|
+
'NEW_RELIC_SERVERLESS_MODE_ENABLED',
|
|
230
|
+
'NEW_RELIC_NATIVE_METRICS_ENABLED'
|
|
214
231
|
])
|
|
215
232
|
|
|
216
233
|
const FLOAT_VARS = new Set([
|
|
@@ -225,6 +242,7 @@ const INT_VARS = new Set([
|
|
|
225
242
|
|
|
226
243
|
exports.ENV_MAPPING = ENV_MAPPING
|
|
227
244
|
exports.LIST_VARS = LIST_VARS
|
|
245
|
+
exports.LIST_VARS_CUSTOM_DELIMITERS = LIST_VARS_CUSTOM_DELIMITERS
|
|
228
246
|
exports.OBJECT_LIST_VARS = OBJECT_LIST_VARS
|
|
229
247
|
exports.BOOLEAN_VARS = BOOLEAN_VARS
|
|
230
248
|
exports.FLOAT_VARS = FLOAT_VARS
|
package/lib/config/index.js
CHANGED
|
@@ -135,12 +135,11 @@ function Config(config) {
|
|
|
135
135
|
this.sampling_target_period_in_seconds = 60
|
|
136
136
|
this.max_payload_size_in_bytes = DEFAULT_MAX_PAYLOAD_SIZE_IN_BYTES
|
|
137
137
|
|
|
138
|
-
// how frequently harvester runs
|
|
139
|
-
this.data_report_period = 60
|
|
140
|
-
|
|
141
138
|
// this value is arbitrary
|
|
142
139
|
this.max_trace_segments = 900
|
|
143
140
|
|
|
141
|
+
this.entity_guid = null
|
|
142
|
+
|
|
144
143
|
// feature level of this account
|
|
145
144
|
this.product_level = 0
|
|
146
145
|
// product-level related
|
|
@@ -182,7 +181,7 @@ function Config(config) {
|
|
|
182
181
|
this.event_harvest_config = {
|
|
183
182
|
report_period_ms: 60000,
|
|
184
183
|
harvest_limits: {
|
|
185
|
-
analytic_event_data: this.transaction_events.
|
|
184
|
+
analytic_event_data: this.transaction_events.max_samples_stored,
|
|
186
185
|
custom_event_data: this.custom_insights_events.max_samples_stored,
|
|
187
186
|
error_event_data: this.error_collector.max_event_samples_stored,
|
|
188
187
|
span_event_data: SPAN_EVENT_LIMIT
|
|
@@ -338,7 +337,6 @@ Config.prototype._fromServer = function _fromServer(params, key) {
|
|
|
338
337
|
|
|
339
338
|
case 'apdex_t':
|
|
340
339
|
case 'web_transactions_apdex':
|
|
341
|
-
case 'data_report_period':
|
|
342
340
|
this._updateIfChanged(params, key)
|
|
343
341
|
break
|
|
344
342
|
case 'event_harvest_config':
|
|
@@ -551,6 +549,11 @@ Config.prototype._fromServer = function _fromServer(params, key) {
|
|
|
551
549
|
)
|
|
552
550
|
break
|
|
553
551
|
|
|
552
|
+
// Entity GUID
|
|
553
|
+
case 'entity_guid':
|
|
554
|
+
this.entity_guid = params[key]
|
|
555
|
+
break
|
|
556
|
+
|
|
554
557
|
// These settings aren't supported by the agent (yet).
|
|
555
558
|
case 'sampling_rate':
|
|
556
559
|
case 'episodes_file':
|
|
@@ -578,7 +581,6 @@ Config.prototype._fromServer = function _fromServer(params, key) {
|
|
|
578
581
|
case 'transaction_events.attributes.exclude':
|
|
579
582
|
case 'transaction_events.attributes.include':
|
|
580
583
|
case 'transaction_events.max_samples_stored':
|
|
581
|
-
case 'transaction_events.max_samples_per_minute':
|
|
582
584
|
case 'transaction_tracer.attributes.enabled':
|
|
583
585
|
case 'transaction_tracer.attributes.exclude':
|
|
584
586
|
case 'transaction_tracer.attributes.include':
|
|
@@ -1029,7 +1031,7 @@ Config.prototype._fromPassed = function _fromPassed(external, internal, arbitrar
|
|
|
1029
1031
|
return
|
|
1030
1032
|
}
|
|
1031
1033
|
|
|
1032
|
-
if (typeof node === 'object' && !Array.isArray(node)) {
|
|
1034
|
+
if (typeof node === 'object' && !(node instanceof RegExp) && !Array.isArray(node)) {
|
|
1033
1035
|
// is top level and can have arbitrary keys
|
|
1034
1036
|
var allowArbitrary = internal === this || HAS_ARBITRARY_KEYS.has(key)
|
|
1035
1037
|
this._fromPassed(node, internal[key], allowArbitrary)
|
|
@@ -1104,7 +1106,8 @@ Config.prototype._fromEnvironment = function _fromEnvironment(metadata, data) {
|
|
|
1104
1106
|
var setting = process.env[node]
|
|
1105
1107
|
if (setting) {
|
|
1106
1108
|
if (ENV.LIST_VARS.has(node)) {
|
|
1107
|
-
|
|
1109
|
+
let split = ENV.LIST_VARS_CUSTOM_DELIMITERS[node] || /,/
|
|
1110
|
+
data[value] = setting.split(split).map(function trimVal(k) {
|
|
1108
1111
|
return k.trim()
|
|
1109
1112
|
})
|
|
1110
1113
|
} else if (ENV.OBJECT_LIST_VARS.has(node)) {
|
|
@@ -1146,6 +1149,25 @@ Config.prototype._loggingManuallySet = function _loggingManuallySet(inputConfig)
|
|
|
1146
1149
|
return (inputEnabled !== undefined) || (envEnabled !== undefined)
|
|
1147
1150
|
}
|
|
1148
1151
|
|
|
1152
|
+
/**
|
|
1153
|
+
* Returns true if native-metrics has been manually enabled via configuration
|
|
1154
|
+
* file or enveironment variable
|
|
1155
|
+
*
|
|
1156
|
+
* @param {*} inputConfig configuration pass to the Config constructor
|
|
1157
|
+
*
|
|
1158
|
+
* @returns {boolean}
|
|
1159
|
+
*/
|
|
1160
|
+
Config.prototype._nativeMetricsManuallySet =
|
|
1161
|
+
function _nativeMetricsManuallySet(inputConfig) {
|
|
1162
|
+
const inputEnabled = inputConfig
|
|
1163
|
+
&& inputConfig.plugins
|
|
1164
|
+
&& inputConfig.plugins.native_metrics
|
|
1165
|
+
&& inputConfig.plugins.native_metrics.enabled
|
|
1166
|
+
const envEnabled = process.env.NEW_RELIC_NATIVE_METRICS_ENABLED
|
|
1167
|
+
|
|
1168
|
+
return (inputEnabled !== undefined) || (envEnabled !== undefined)
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1149
1171
|
/**
|
|
1150
1172
|
* Returns true if distributed tracing has been manually enabled via configuration file or
|
|
1151
1173
|
* environment variable.
|
|
@@ -1189,6 +1211,24 @@ Config.prototype._enforceServerless = function _enforceServerless(inputConfig) {
|
|
|
1189
1211
|
)
|
|
1190
1212
|
}
|
|
1191
1213
|
|
|
1214
|
+
if (this._nativeMetricsManuallySet(inputConfig) &&
|
|
1215
|
+
this.plugins.native_metrics.enabled) {
|
|
1216
|
+
logger.info(
|
|
1217
|
+
'Enabling the native-metrics module when in serverless mode may greatly ' +
|
|
1218
|
+
'increase cold-start times. Given the limited benefit of the VM metrics' +
|
|
1219
|
+
'and general lack of control in a serverless environment, we do not ' +
|
|
1220
|
+
'recommend this trade-off.'
|
|
1221
|
+
)
|
|
1222
|
+
} else {
|
|
1223
|
+
this.plugins.native_metrics.enabled = false
|
|
1224
|
+
|
|
1225
|
+
logger.info(
|
|
1226
|
+
'The native-metrics module is disabled by default when serverless_mode ' +
|
|
1227
|
+
'is enabled. If desired, enable the native-metrics module via config file ' +
|
|
1228
|
+
'or environment variable.'
|
|
1229
|
+
)
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1192
1232
|
if (!this._DTManuallySet(inputConfig)) {
|
|
1193
1233
|
this.distributed_tracing.enabled = true
|
|
1194
1234
|
}
|
|
@@ -1207,6 +1247,10 @@ Config.prototype._enforceServerless = function _enforceServerless(inputConfig) {
|
|
|
1207
1247
|
}
|
|
1208
1248
|
// default trusted_account_key to account_id
|
|
1209
1249
|
this.trusted_account_key = this.trusted_account_key || this.account_id
|
|
1250
|
+
|
|
1251
|
+
// Not required in serverless mode but must default to Unknown to function.
|
|
1252
|
+
this.primary_application_id = this.primary_application_id || 'Unknown'
|
|
1253
|
+
|
|
1210
1254
|
return
|
|
1211
1255
|
}
|
|
1212
1256
|
const DT_KEYS = ['account_id', 'primary_application_id', 'trusted_account_key']
|
package/lib/injector.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// to avoid parsing the esprima code base we require it here
|
|
4
|
+
// TODO: excise this with better internal file filtering
|
|
5
|
+
const esprima = require('esprima')
|
|
6
|
+
const ecg = require('escodegen')
|
|
7
|
+
const fileNameToken = {
|
|
8
|
+
type: 'Literal',
|
|
9
|
+
value: null
|
|
10
|
+
}
|
|
11
|
+
const lineNumberTokenTemplate = {
|
|
12
|
+
type: 'Literal',
|
|
13
|
+
value: null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class Mutator {
|
|
17
|
+
constructor(appliesTo, mutate) {
|
|
18
|
+
this.appliesTo = appliesTo
|
|
19
|
+
this.mutate = mutate
|
|
20
|
+
this.tokens = []
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
add(token) {
|
|
24
|
+
this.tokens.push(token)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
apply() {
|
|
28
|
+
this.tokens.forEach(this.mutate)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const functionTypes = new Set([
|
|
33
|
+
'ArrowFunctionExpression',
|
|
34
|
+
'FunctionExpression',
|
|
35
|
+
'AsyncFunction'
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
const equalityCheckOps = new Set([
|
|
39
|
+
'==',
|
|
40
|
+
'===',
|
|
41
|
+
'!=',
|
|
42
|
+
'!==',
|
|
43
|
+
'instanceof'
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
const variableTypes = new Set([
|
|
47
|
+
'MemberExpression',
|
|
48
|
+
'Identifier'
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
const callTypes = new Set([
|
|
52
|
+
'CallExpression',
|
|
53
|
+
'NewExpression'
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
const wrapTemplate = esprima.parse('__NR_wrap()').body[0].expression
|
|
57
|
+
const unwrapTemplate = esprima.parse('__NR_unwrap()').body[0].expression
|
|
58
|
+
|
|
59
|
+
const mutators = [
|
|
60
|
+
new Mutator(function wrapAssignmentPredicate(token) {
|
|
61
|
+
return token.type === 'AssignmentExpression' &&
|
|
62
|
+
token.operator === '=' &&
|
|
63
|
+
functionTypes.has(token.right.type)
|
|
64
|
+
}, function injectWrapAssignment(token) {
|
|
65
|
+
token.right = wrapToken(token.right)
|
|
66
|
+
}),
|
|
67
|
+
new Mutator(function wrapArgPredicate(token) {
|
|
68
|
+
return callTypes.has(token.type)
|
|
69
|
+
}, function injectWrapArg(token) {
|
|
70
|
+
token.arguments = token.arguments.map(wrapToken)
|
|
71
|
+
}),
|
|
72
|
+
new Mutator(function unwrapPredicate(token) {
|
|
73
|
+
return token.type === 'BinaryExpression' && equalityCheckOps.has(token.operator)
|
|
74
|
+
}, function injectUnwrap(token) {
|
|
75
|
+
token.left = unwrapToken(token.left)
|
|
76
|
+
token.right = unwrapToken(token.right)
|
|
77
|
+
})
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
function wrapToken(argToken) {
|
|
81
|
+
const type = argToken.type
|
|
82
|
+
if (
|
|
83
|
+
!functionTypes.has(type) &&
|
|
84
|
+
!callTypes.has(type) &&
|
|
85
|
+
!variableTypes.has(type) ||
|
|
86
|
+
!argToken.loc
|
|
87
|
+
) {
|
|
88
|
+
return argToken
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const wrapped = Object.assign({}, wrapTemplate)
|
|
92
|
+
const lineNumberToken = Object.assign({}, lineNumberTokenTemplate)
|
|
93
|
+
lineNumberToken.value = argToken.loc.start.line
|
|
94
|
+
wrapped.arguments = [argToken, lineNumberToken, Object.assign({}, fileNameToken)]
|
|
95
|
+
return wrapped
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function unwrapToken(argToken) {
|
|
99
|
+
if (!variableTypes.has(argToken.type)) {
|
|
100
|
+
return argToken
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const wrapped = Object.assign({}, unwrapTemplate)
|
|
104
|
+
wrapped.arguments = [argToken]
|
|
105
|
+
return wrapped
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function inject(sourceCode, file) {
|
|
109
|
+
// wrap the incoming file code to make it more palatable for esprima.
|
|
110
|
+
// node likewise wraps the contents of the file in a function, so this
|
|
111
|
+
// replicates the behavior (e.g. allows for global returns)
|
|
112
|
+
sourceCode = 'function main() {' + sourceCode + '}'
|
|
113
|
+
const sourceRootBody = esprima.parse(sourceCode, {loc: true}).body[0].body.body
|
|
114
|
+
|
|
115
|
+
const toRelax = [].concat(sourceRootBody)
|
|
116
|
+
|
|
117
|
+
while (toRelax.length) {
|
|
118
|
+
const currentToken = toRelax.pop()
|
|
119
|
+
|
|
120
|
+
mutators.forEach(m => {
|
|
121
|
+
if (m.appliesTo(currentToken)) {
|
|
122
|
+
m.add(currentToken)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
for (let key of Object.keys(currentToken)) {
|
|
127
|
+
if (key === 'loc') continue
|
|
128
|
+
const value = currentToken[key]
|
|
129
|
+
if (value && value instanceof Object) {
|
|
130
|
+
if (Array.isArray(value)) {
|
|
131
|
+
for (let t of value) {
|
|
132
|
+
if (t) {
|
|
133
|
+
toRelax.push(t)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
toRelax.push(value)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// TODO: make this less janky
|
|
144
|
+
fileNameToken.value = file
|
|
145
|
+
mutators.forEach(m => m.apply())
|
|
146
|
+
fileNameToken.value = null
|
|
147
|
+
// create a new base level token that contains all the statements we
|
|
148
|
+
// want to pass back to node
|
|
149
|
+
const printed = ecg.generate({
|
|
150
|
+
type: 'Program',
|
|
151
|
+
body: sourceRootBody,
|
|
152
|
+
sourceType: 'script'
|
|
153
|
+
}, {
|
|
154
|
+
format: {
|
|
155
|
+
semicolons: false
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
return printed
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { inject }
|
|
@@ -32,9 +32,10 @@ module.exports = function initialize(agent, bluebird, moduleName, shim) {
|
|
|
32
32
|
])
|
|
33
33
|
|
|
34
34
|
shim.wrapCast(Promise, [
|
|
35
|
-
'all', 'any', 'attempt', 'bind', 'cast', 'delay', 'each',
|
|
36
|
-
'fromCallback', 'fromNode', 'fulfilled', 'join', 'map',
|
|
37
|
-
'props', 'race', 'reduce', 'reject', 'rejected', 'resolve',
|
|
35
|
+
'all', 'allSettled', 'any', 'attempt', 'bind', 'cast', 'delay', 'each',
|
|
36
|
+
'filter', 'fromCallback', 'fromNode', 'fulfilled', 'join', 'map',
|
|
37
|
+
'mapSeries', 'props', 'race', 'reduce', 'reject', 'rejected', 'resolve',
|
|
38
|
+
'some', 'try'
|
|
38
39
|
])
|
|
39
40
|
shim.wrapPromisify(Promise, ['coroutine', 'method', 'promisify'])
|
|
40
41
|
|
|
@@ -138,6 +138,7 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
segment.addAttribute('url', `${proto}//${hostname}${parsed.path}`)
|
|
141
|
+
segment.addAttribute('procedure', opts.method || 'GET')
|
|
141
142
|
|
|
142
143
|
// Wrap the emit method. We're doing a special wrapper instead of using
|
|
143
144
|
// `tracer.bindEmitter` because we want to do some logic based on certain
|
|
@@ -111,13 +111,21 @@ function wrapQueriable(shim, queriable, isPoolQuery) {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
var proto = Object.getPrototypeOf(queriable)
|
|
114
|
+
|
|
115
|
+
var describe
|
|
114
116
|
if (isPoolQuery) {
|
|
115
|
-
|
|
117
|
+
describe = describePoolQuery
|
|
116
118
|
} else {
|
|
117
|
-
|
|
119
|
+
describe = describeQuery
|
|
118
120
|
shim.setInternalProperty(proto, '__NR_databaseName', null)
|
|
119
121
|
}
|
|
120
122
|
|
|
123
|
+
shim.recordQuery(proto, 'query', describe)
|
|
124
|
+
|
|
125
|
+
if (queriable.execute) {
|
|
126
|
+
shim.recordQuery(proto, 'execute', describe)
|
|
127
|
+
}
|
|
128
|
+
|
|
121
129
|
return true
|
|
122
130
|
}
|
|
123
131
|
|
package/lib/instrumentations.js
CHANGED
|
@@ -5,7 +5,7 @@ var MODULE_TYPE = require('./shim/constants').MODULE_TYPE
|
|
|
5
5
|
// Return a new copy of this array every time we're called
|
|
6
6
|
module.exports = function instrumentations() {
|
|
7
7
|
return {
|
|
8
|
-
'aws-sdk': {
|
|
8
|
+
'aws-sdk': {module: '@newrelic/aws-sdk'},
|
|
9
9
|
'amqplib': {type: MODULE_TYPE.MESSAGE},
|
|
10
10
|
'cassandra-driver': {type: MODULE_TYPE.DATASTORE},
|
|
11
11
|
'connect': {type: MODULE_TYPE.WEB_FRAMEWORK},
|
package/lib/shimmer.js
CHANGED
|
@@ -6,6 +6,7 @@ var logger = require('./logger').child({component: 'shimmer'})
|
|
|
6
6
|
var INSTRUMENTATIONS = require('./instrumentations')()
|
|
7
7
|
var properties = require('./util/properties')
|
|
8
8
|
var shims = require('./shim')
|
|
9
|
+
const { inject } = require('./injector')
|
|
9
10
|
|
|
10
11
|
var MODULE_TYPE = shims.constants.MODULE_TYPE
|
|
11
12
|
|
|
@@ -56,6 +57,13 @@ var CORE_INSTRUMENTATION = {
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
const FORCE_MODULE_RESOLUTION_WARNING =
|
|
61
|
+
'Unable to retrieve cached path for one or more modules ' +
|
|
62
|
+
'with an already loaded parent. Forcing resolution. ' +
|
|
63
|
+
'This should not occur during normal agent execution. ' +
|
|
64
|
+
'Module resolution performance my be impacted. ' +
|
|
65
|
+
'See trace-level logs for specific modules.'
|
|
66
|
+
|
|
59
67
|
/**
|
|
60
68
|
* Unwrapping is only likely to be used by test code, and is a fairly drastic
|
|
61
69
|
* maneuver, but it should be pretty safe if there's a desire to reboot the
|
|
@@ -342,6 +350,121 @@ var shimmer = module.exports = {
|
|
|
342
350
|
var Module = require('module')
|
|
343
351
|
var filepathMap = {}
|
|
344
352
|
|
|
353
|
+
// TODO: how do we unpatch this?
|
|
354
|
+
const diagConf = agent.config.diagnostics.code_injector
|
|
355
|
+
if (diagConf.enabled) {
|
|
356
|
+
// XXX: this is required to handle unwrapping event listener removal
|
|
357
|
+
const EEProto = require('events').EventEmitter.prototype
|
|
358
|
+
shimmer.wrapMethod(
|
|
359
|
+
EEProto,
|
|
360
|
+
'EventEmitter.prototype',
|
|
361
|
+
['addListener', 'on', 'once', 'off', 'prependListener', 'prependOnceListener'],
|
|
362
|
+
function wrapAddListener(addListener) {
|
|
363
|
+
return function wrappedAddListener(ev, listener) {
|
|
364
|
+
return addListener.call(this, ev, __NR_unwrap(listener))
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
)
|
|
368
|
+
shimmer.wrapMethod(
|
|
369
|
+
EEProto,
|
|
370
|
+
'EventEmitter.prototype',
|
|
371
|
+
'removeListener',
|
|
372
|
+
function wrapRemoveListener(removeListener) {
|
|
373
|
+
return function wrappedRemoveListener(ev, listener) {
|
|
374
|
+
return removeListener.call(this, ev, __NR_unwrap(listener))
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
)
|
|
378
|
+
const internalCodePattern = diagConf.internal_file_pattern
|
|
379
|
+
const proto = Module.prototype
|
|
380
|
+
shimmer.wrapMethod(
|
|
381
|
+
proto,
|
|
382
|
+
'Module.prototype',
|
|
383
|
+
'_compile',
|
|
384
|
+
function wrapCompile(_compile) {
|
|
385
|
+
return function wrappedCompile(code, file) {
|
|
386
|
+
let injected
|
|
387
|
+
try {
|
|
388
|
+
injected = internalCodePattern.test(file) ? code : inject(code, file)
|
|
389
|
+
return _compile.call(this, injected, file)
|
|
390
|
+
} catch (e) {
|
|
391
|
+
logger.debug('Unable to parse file:', file, e)
|
|
392
|
+
return _compile.call(this, code, file)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
// TODO: use shimmer methods
|
|
399
|
+
global.tracer = agent.tracer
|
|
400
|
+
global.__NR_wrap = __NR_wrap
|
|
401
|
+
function __NR_wrap(f, lineNum, fileName) {
|
|
402
|
+
const scheduledSegment = agent.tracer.getSegment()
|
|
403
|
+
if (
|
|
404
|
+
typeof f !== 'function' ||
|
|
405
|
+
!scheduledSegment ||
|
|
406
|
+
!scheduledSegment.transaction.isActive()
|
|
407
|
+
) {
|
|
408
|
+
return f
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
const scheduledTransaction = scheduledSegment.transaction
|
|
413
|
+
return new Proxy(f, {
|
|
414
|
+
get: function getTrap(target, prop) {
|
|
415
|
+
// Allow for look up of the target
|
|
416
|
+
if (prop === '__NR_new_original') {
|
|
417
|
+
return target
|
|
418
|
+
}
|
|
419
|
+
return target[prop]
|
|
420
|
+
},
|
|
421
|
+
construct: function constructTrap(Target, proxyArgs) {
|
|
422
|
+
const currentSegment = agent.tracer.getSegment()
|
|
423
|
+
if (
|
|
424
|
+
scheduledTransaction.isActive() &&
|
|
425
|
+
(
|
|
426
|
+
!currentSegment ||
|
|
427
|
+
currentSegment.transaction.id !== scheduledTransaction.id
|
|
428
|
+
)
|
|
429
|
+
) {
|
|
430
|
+
logger.info(
|
|
431
|
+
`lost state in ${fileName} (line ${lineNum});`,
|
|
432
|
+
`expected to be in transaction ${scheduledTransaction.id},`,
|
|
433
|
+
`instead landed in ${currentSegment && currentSegment.transaction.id}`
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
return new Target(...proxyArgs)
|
|
437
|
+
},
|
|
438
|
+
apply: function wrappedApply(target, thisArg, args) {
|
|
439
|
+
const currentSegment = agent.tracer.getSegment()
|
|
440
|
+
if (
|
|
441
|
+
scheduledTransaction.isActive() &&
|
|
442
|
+
(
|
|
443
|
+
!currentSegment ||
|
|
444
|
+
currentSegment.transaction.id !== scheduledTransaction.id
|
|
445
|
+
)
|
|
446
|
+
) {
|
|
447
|
+
logger.info(
|
|
448
|
+
`lost state in ${fileName} (line ${lineNum});`,
|
|
449
|
+
`expected to be in transaction ${scheduledTransaction.id},`,
|
|
450
|
+
`instead landed in ${currentSegment && currentSegment.transaction.id}`
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
return target.apply(thisArg, args)
|
|
454
|
+
}
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
global.__NR_unwrap = __NR_unwrap
|
|
459
|
+
function __NR_unwrap(f) {
|
|
460
|
+
if (typeof f !== 'function' || !f || !f.__NR_new_original) {
|
|
461
|
+
return f
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return f.__NR_new_original
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
345
468
|
shimmer.wrapMethod(Module, 'Module', '_resolveFilename', function wrapRes(resolve) {
|
|
346
469
|
return function wrappedResolveFilename(file) {
|
|
347
470
|
// This is triggered by the load call, so record the path that has been seen so
|
|
@@ -353,14 +476,39 @@ var shimmer = module.exports = {
|
|
|
353
476
|
})
|
|
354
477
|
|
|
355
478
|
shimmer.wrapMethod(Module, 'Module', '_load', function wrapLoad(load) {
|
|
356
|
-
return function wrappedLoad(
|
|
357
|
-
//
|
|
358
|
-
// load function calls resolve, we must mirror the recursive load calls with a
|
|
359
|
-
// stack.
|
|
479
|
+
return function wrappedLoad(request, parent, isMain) {
|
|
480
|
+
// _load() will invoke _resolveFilename() first time resolving a module.
|
|
360
481
|
const m = load.apply(this, arguments)
|
|
361
|
-
|
|
482
|
+
|
|
483
|
+
const fileName = resolveFileName(request, parent, isMain)
|
|
484
|
+
return _postLoad(agent, m, request, fileName)
|
|
362
485
|
}
|
|
363
486
|
})
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Forces file name resolve for modules not in our cache when
|
|
490
|
+
* their parent has already been loaded/cached by Node.
|
|
491
|
+
* Provides a fall-back for unexpected cases that may occur.
|
|
492
|
+
* Also provides flexibilty for testing now that node 11+ caches these.
|
|
493
|
+
* @param {*} request
|
|
494
|
+
* @param {*} parent
|
|
495
|
+
* @param {*} isMain
|
|
496
|
+
*/
|
|
497
|
+
function resolveFileName(request, parent, isMain) {
|
|
498
|
+
const cachedPath = filepathMap[request]
|
|
499
|
+
if (!cachedPath && parent && parent.loaded) {
|
|
500
|
+
logger.warnOnce('Force Resolution', FORCE_MODULE_RESOLUTION_WARNING)
|
|
501
|
+
|
|
502
|
+
if (logger.traceEnabled()) {
|
|
503
|
+
logger.trace(`No cached path found for ${request}. Forcing resolution.`)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Our patched _resolveFilename will cache. No need to here.
|
|
507
|
+
return Module._resolveFilename(request, parent, isMain)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return cachedPath
|
|
511
|
+
}
|
|
364
512
|
},
|
|
365
513
|
|
|
366
514
|
unpatchModule: function unpatchModule() {
|
package/lib/spans/span-event.js
CHANGED
|
@@ -88,7 +88,7 @@ class SpanEvent {
|
|
|
88
88
|
|
|
89
89
|
const tx = segment.transaction
|
|
90
90
|
|
|
91
|
-
span.intrinsics.traceId = tx.
|
|
91
|
+
span.intrinsics.traceId = tx.getTraceId()
|
|
92
92
|
span.intrinsics.guid = segment.id
|
|
93
93
|
span.intrinsics.parentId = parentId
|
|
94
94
|
span.intrinsics.transactionId = tx.id
|
|
@@ -35,6 +35,13 @@ module.exports = class TransactionHandle {
|
|
|
35
35
|
this._transaction.setForceIgnore(true)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Return whether this Transaction is being sampled
|
|
40
|
+
*/
|
|
41
|
+
isSampled() {
|
|
42
|
+
return this._transaction.isSampled()
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
/**
|
|
39
46
|
*
|
|
40
47
|
* Proxy method for Transaction#createDistrubtedTracePayload.
|
|
@@ -70,6 +77,10 @@ module.exports.Stub = class TransactionHandleStub {
|
|
|
70
77
|
logger.debug("No transaction found when calling Transaction.ignore")
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
isSampled() {
|
|
81
|
+
logger.debug("No transaction found when calling Transaction.isSampled")
|
|
82
|
+
}
|
|
83
|
+
|
|
73
84
|
createDistributedTracePayload() {
|
|
74
85
|
logger.debug(
|
|
75
86
|
"No transaction found when calling Transaction.createDistributedTracePayload"
|
package/lib/transaction/index.js
CHANGED
|
@@ -72,7 +72,6 @@ function Transaction(agent) {
|
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
++agent.activeTransactions
|
|
75
|
-
++agent.transactionCreatedInHarvest
|
|
76
75
|
|
|
77
76
|
this.numSegments = 0
|
|
78
77
|
this.id = hashes.makeId()
|
|
@@ -972,7 +971,7 @@ function createDistributedTracePayload() {
|
|
|
972
971
|
ac: accountId,
|
|
973
972
|
ap: appId,
|
|
974
973
|
tx: this.id,
|
|
975
|
-
tr: this.
|
|
974
|
+
tr: this.getTraceId(),
|
|
976
975
|
pr: this.priority,
|
|
977
976
|
sa: this.sampled,
|
|
978
977
|
ti: Date.now()
|
|
@@ -1000,7 +999,7 @@ function addDistributedTraceIntrinsics(attrs) {
|
|
|
1000
999
|
this._calculatePriority()
|
|
1001
1000
|
|
|
1002
1001
|
// *always* add these if DT flag is enabled.
|
|
1003
|
-
attrs.traceId = this.
|
|
1002
|
+
attrs.traceId = this.getTraceId()
|
|
1004
1003
|
attrs.guid = this.id
|
|
1005
1004
|
attrs.priority = this.priority
|
|
1006
1005
|
|
|
@@ -1028,6 +1027,11 @@ function addDistributedTraceIntrinsics(attrs) {
|
|
|
1028
1027
|
}
|
|
1029
1028
|
}
|
|
1030
1029
|
|
|
1030
|
+
Transaction.prototype.isSampled = function isSampled() {
|
|
1031
|
+
this._calculatePriority()
|
|
1032
|
+
return this.sampled
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1031
1035
|
/**
|
|
1032
1036
|
* Generates a priority for the transaction if it does not have one already.
|
|
1033
1037
|
*/
|
|
@@ -1074,4 +1078,11 @@ function addRequestParameters(requestParameters) {
|
|
|
1074
1078
|
}
|
|
1075
1079
|
}
|
|
1076
1080
|
|
|
1081
|
+
/**
|
|
1082
|
+
* Gets the current distributed trace traceId
|
|
1083
|
+
*/
|
|
1084
|
+
Transaction.prototype.getTraceId = function getTraceId() {
|
|
1085
|
+
return this.traceId || this.id
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1077
1088
|
module.exports = Transaction
|