newrelic 4.8.1 → 4.9.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 +53 -0
- package/api.js +16 -10
- package/bin/travis-setup.sh +18 -7
- package/lib/config/env.js +2 -1
- package/lib/config/index.js +2 -0
- package/lib/db/tracer.js +3 -1
- package/lib/errors/index.js +1 -1
- package/lib/instrumentation/bluebird.js +35 -45
- package/lib/instrumentation/core/child_process.js +41 -38
- package/lib/instrumentation/core/domain.js +6 -5
- package/lib/instrumentation/core/net.js +62 -66
- package/lib/instrumentation/core/timers.js +78 -49
- package/lib/instrumentation/core/zlib.js +22 -8
- package/lib/instrumentations.js +3 -2
- package/lib/shim/constants.js +4 -0
- package/lib/shim/datastore-shim.js +1 -1
- package/lib/shim/index.js +8 -6
- package/lib/shim/promise-shim.js +563 -0
- package/lib/shim/shim.js +38 -11
- package/lib/shimmer.js +1 -0
- package/lib/transaction/index.js +18 -8
- package/lib/transaction/trace/aggregator.js +3 -3
- package/lib/util/codec.js +4 -1
- package/package.json +3 -2
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const logger = require('../logger').child({component: 'PromiseShim'})
|
|
4
|
+
const Shim = require('./shim')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A helper class for wrapping promise modules.
|
|
8
|
+
*
|
|
9
|
+
* @extends Shim
|
|
10
|
+
*/
|
|
11
|
+
class PromiseShim extends Shim {
|
|
12
|
+
/**
|
|
13
|
+
* Constructs a shim associated with the given agent instance, specialized for
|
|
14
|
+
* instrumenting promise libraries.
|
|
15
|
+
*
|
|
16
|
+
* @param {Agent} agent
|
|
17
|
+
* The agent this shim will use.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} moduleName
|
|
20
|
+
* The name of the module being instrumented.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} resolvedName
|
|
23
|
+
* The full path to the loaded module.
|
|
24
|
+
*
|
|
25
|
+
* @see Shim
|
|
26
|
+
*/
|
|
27
|
+
constructor(agent, moduleName, resolvedName) {
|
|
28
|
+
super(agent, moduleName, resolvedName)
|
|
29
|
+
this._logger = logger.child({module: moduleName})
|
|
30
|
+
this._class = null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Grants access to the `Contextualizer` class used by the `PromiseShim` to
|
|
35
|
+
* propagate context down promise chains.
|
|
36
|
+
*
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
static get Contextualizer() {
|
|
40
|
+
return Contextualizer
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets the class used to indentify promises from the wrapped promise library.
|
|
45
|
+
*
|
|
46
|
+
* @param {function} clss - The promise library's class.
|
|
47
|
+
*/
|
|
48
|
+
setClass(clss) {
|
|
49
|
+
this._class = clss
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if the given object is an instance of a promise from the promise
|
|
54
|
+
* library being wrapped.
|
|
55
|
+
*
|
|
56
|
+
* @param {*} obj - The object to check the instance type of.
|
|
57
|
+
*
|
|
58
|
+
* @return {bool} True if the provided object is an instance of a promise from
|
|
59
|
+
* this promise library.
|
|
60
|
+
*
|
|
61
|
+
* @see PromiseShim#setClass
|
|
62
|
+
*/
|
|
63
|
+
isPromiseInstance(obj) {
|
|
64
|
+
return !!this._class && obj instanceof this._class
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Wraps the given properties as constructors for the promise library.
|
|
69
|
+
*
|
|
70
|
+
* - `wrapConstructor(nodule, properties)`
|
|
71
|
+
* - `wrapConstructor(func)`
|
|
72
|
+
*
|
|
73
|
+
* It is only necessary to wrap the constructor for the class if there is no
|
|
74
|
+
* other way to access the executor function. Some libraries expose a separate
|
|
75
|
+
* method which is called to execute the executor. If that is available, it is
|
|
76
|
+
* better to wrap that using {@link PromiseShim#wrapExecutorCaller} than to
|
|
77
|
+
* use this method.
|
|
78
|
+
*
|
|
79
|
+
* @param {object|function} nodule
|
|
80
|
+
* The source of the properties to wrap, or a single function to wrap.
|
|
81
|
+
*
|
|
82
|
+
* @param {string|array.<string>} [properties]
|
|
83
|
+
* One or more properties to wrap. If omitted, the `nodule` parameter is
|
|
84
|
+
* assumed to be the constructor to wrap.
|
|
85
|
+
*
|
|
86
|
+
* @return {object|function} The first parameter to this function, after
|
|
87
|
+
* wrapping it or its properties.
|
|
88
|
+
*
|
|
89
|
+
* @see PromiseShim#wrapExecutorCaller
|
|
90
|
+
*/
|
|
91
|
+
wrapConstructor(nodule, properties) {
|
|
92
|
+
return this.wrapClass(nodule, properties, {
|
|
93
|
+
pre: function prePromise(shim, Promise, name, args) {
|
|
94
|
+
// We are expecting one function argument for executor, anything else is
|
|
95
|
+
// non-standard, do not attempt to wrap. Also do not attempt to wrap if
|
|
96
|
+
// we are not in a transaction.
|
|
97
|
+
if (args.length !== 1 || !shim.isFunction(args[0]) || !shim.getActiveSegment()) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
_wrapExecutorContext(shim, args)
|
|
101
|
+
},
|
|
102
|
+
post: function postPromise(shim, Promise, name, args) {
|
|
103
|
+
// This extra property is added by `_wrapExecutorContext` in the pre step.
|
|
104
|
+
const executor = args[0]
|
|
105
|
+
const context = executor && executor.__NR_executorContext
|
|
106
|
+
if (!context || !shim.isFunction(context.executor)) {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
context.promise = this
|
|
111
|
+
Contextualizer.link(null, this, shim.getSegment())
|
|
112
|
+
try {
|
|
113
|
+
// Must run after promise is defined so that `__NR_wrapper` can be set.
|
|
114
|
+
context.executor.apply(context.self, context.args)
|
|
115
|
+
} catch (e) {
|
|
116
|
+
const reject = context.args[1]
|
|
117
|
+
reject(e)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Wraps the given properties as the caller of promise executors.
|
|
125
|
+
*
|
|
126
|
+
* - `wrapExecutorCaller(nodule, properties)`
|
|
127
|
+
* - `wrapExecutorCaller(func)`
|
|
128
|
+
*
|
|
129
|
+
* Wrapping the executor caller method directly is preferable to wrapping
|
|
130
|
+
* the constructor of the promise class.
|
|
131
|
+
*
|
|
132
|
+
* @param {object|function} nodule
|
|
133
|
+
* The source of the properties to wrap, or a single function to wrap.
|
|
134
|
+
*
|
|
135
|
+
* @param {string|array.<string>} [properties]
|
|
136
|
+
* One or more properties to wrap. If omitted, the `nodule` parameter is
|
|
137
|
+
* assumed to be the function to wrap.
|
|
138
|
+
*
|
|
139
|
+
* @return {object|function} The first parameter to this function, after
|
|
140
|
+
* wrapping it or its properties.
|
|
141
|
+
*
|
|
142
|
+
* @see PromiseShim#wrapConstructor
|
|
143
|
+
*/
|
|
144
|
+
wrapExecutorCaller(nodule, properties) {
|
|
145
|
+
return this.wrap(nodule, properties, function executorWrapper(shim, caller) {
|
|
146
|
+
if (!shim.isFunction(caller) || shim.isWrapped(caller)) {
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return function wrappedExecutorCaller(executor) {
|
|
151
|
+
var parent = shim.getActiveSegment()
|
|
152
|
+
if (!this || !parent) {
|
|
153
|
+
return caller.apply(this, arguments)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!this.__NR_context) {
|
|
157
|
+
Contextualizer.link(null, this, parent)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const args = shim.argsToArray.apply(shim, arguments)
|
|
161
|
+
_wrapExecutorContext(shim, args)
|
|
162
|
+
const ret = caller.apply(this, args)
|
|
163
|
+
const context = args[0].__NR_executorContext
|
|
164
|
+
context.promise = this
|
|
165
|
+
|
|
166
|
+
// Bluebird catches executor errors and auto-rejects when it catches them,
|
|
167
|
+
// thus we need to do so as well.
|
|
168
|
+
//
|
|
169
|
+
// When adding new libraries, make sure to check that they behave the same
|
|
170
|
+
// way. We may need to enhance the promise spec to handle this variance.
|
|
171
|
+
try {
|
|
172
|
+
executor.apply(context.self, context.args)
|
|
173
|
+
} catch (e) {
|
|
174
|
+
const reject = context.args[1]
|
|
175
|
+
reject(e)
|
|
176
|
+
}
|
|
177
|
+
return ret
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Wraps the given properties as methods which take is some value other than
|
|
184
|
+
* a function to call and return a promise.
|
|
185
|
+
*
|
|
186
|
+
* - `wrapCast(nodule, properties)`
|
|
187
|
+
* - `wrapCast(func)`
|
|
188
|
+
*
|
|
189
|
+
* Examples of promise cast methods include `Promise.resolve`, `Promise.all`,
|
|
190
|
+
* and Bluebird's `Promise.delay`. These are static methods which accept some
|
|
191
|
+
* arbitrary value and return a Promise instance.
|
|
192
|
+
*
|
|
193
|
+
* @param {object|function} nodule
|
|
194
|
+
* The source of the properties to wrap, or a single function to wrap.
|
|
195
|
+
*
|
|
196
|
+
* @param {string|array.<string>} [properties]
|
|
197
|
+
* One or more properties to wrap. If omitted, the `nodule` parameter is
|
|
198
|
+
* assumed to be the function to wrap.
|
|
199
|
+
*
|
|
200
|
+
* @return {object|function} The first parameter to this function, after
|
|
201
|
+
* wrapping it or its properties.
|
|
202
|
+
*/
|
|
203
|
+
wrapCast(nodule, properties) {
|
|
204
|
+
return this.wrap(nodule, properties, function castWrapper(shim, cast) {
|
|
205
|
+
if (!shim.isFunction(cast) || shim.isWrapped(cast)) {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return function __NR_wrappedCast() {
|
|
210
|
+
const segment = shim.getSegment()
|
|
211
|
+
const prom = cast.apply(this, arguments)
|
|
212
|
+
if (segment) {
|
|
213
|
+
Contextualizer.link(null, prom, segment)
|
|
214
|
+
}
|
|
215
|
+
return prom
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Wraps the given properties as promise chaining methods.
|
|
222
|
+
*
|
|
223
|
+
* - `wrapThen(nodule, properties)`
|
|
224
|
+
* - `wrapThen(func)`
|
|
225
|
+
*
|
|
226
|
+
* NOTE: You must set class used by the library before wrapping then-methods.
|
|
227
|
+
*
|
|
228
|
+
* Examples of promise then methods include `Promise#then`, `Promise#finally`,
|
|
229
|
+
* and Bluebird's `Promise#map`. These are methods which take a function to
|
|
230
|
+
* execute once the promise resolves and hands back a new promise.
|
|
231
|
+
*
|
|
232
|
+
* @param {object|function} nodule
|
|
233
|
+
* The source of the properties to wrap, or a single function to wrap.
|
|
234
|
+
*
|
|
235
|
+
* @param {string|array.<string>} [properties]
|
|
236
|
+
* One or more properties to wrap. If omitted, the `nodule` parameter is
|
|
237
|
+
* assumed to be the function to wrap.
|
|
238
|
+
*
|
|
239
|
+
* @return {object|function} The first parameter to this function, after
|
|
240
|
+
* wrapping it or its properties.
|
|
241
|
+
*
|
|
242
|
+
* @see PromiseShim#setClass
|
|
243
|
+
* @see PromiseShim#wrapCatch
|
|
244
|
+
*/
|
|
245
|
+
wrapThen(nodule, properties) {
|
|
246
|
+
return this.wrap(nodule, properties, _wrapThen, [true])
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Wraps the given properties as rejected promise chaining methods.
|
|
251
|
+
*
|
|
252
|
+
* - `wrapCatch(nodule, properties)`
|
|
253
|
+
* - `wrapCatch(func)`
|
|
254
|
+
*
|
|
255
|
+
* NOTE: You must set class used by the library before wrapping catch-methods.
|
|
256
|
+
*
|
|
257
|
+
* Promise catch methods differ from then methods in that only one function
|
|
258
|
+
* will be executed and only if the promise is rejected. Some libraries accept
|
|
259
|
+
* an additional argument to `Promise#catch` which is usually an error class
|
|
260
|
+
* to filter rejections by. This wrap method will handle that case.
|
|
261
|
+
*
|
|
262
|
+
* @param {object|function} nodule
|
|
263
|
+
* The source of the properties to wrap, or a single function to wrap.
|
|
264
|
+
*
|
|
265
|
+
* @param {string|array.<string>} [properties]
|
|
266
|
+
* One or more properties to wrap. If omitted, the `nodule` parameter is
|
|
267
|
+
* assumed to be the function to wrap.
|
|
268
|
+
*
|
|
269
|
+
* @return {object|function} The first parameter to this function, after
|
|
270
|
+
* wrapping it or its properties.
|
|
271
|
+
*
|
|
272
|
+
* @see PromiseShim#setClass
|
|
273
|
+
* @see PromiseShim#wrapThen
|
|
274
|
+
*/
|
|
275
|
+
wrapCatch(nodule, properties) {
|
|
276
|
+
return this.wrap(nodule, properties, _wrapThen, [false])
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Wraps the given properties as callback-to-promise conversion methods.
|
|
281
|
+
*
|
|
282
|
+
* - `wrapPromisify(nodule, properties)`
|
|
283
|
+
* - `wrapPromisify(func)`
|
|
284
|
+
*
|
|
285
|
+
* @param {object|function} nodule
|
|
286
|
+
* The source of the properties to wrap, or a single function to wrap.
|
|
287
|
+
*
|
|
288
|
+
* @param {string|array.<string>} [properties]
|
|
289
|
+
* One or more properties to wrap. If omitted, the `nodule` parameter is
|
|
290
|
+
* assumed to be the function to wrap.
|
|
291
|
+
*
|
|
292
|
+
* @return {object|function} The first parameter to this function, after
|
|
293
|
+
* wrapping it or its properties.
|
|
294
|
+
*/
|
|
295
|
+
wrapPromisify(nodule, properties) {
|
|
296
|
+
return this.wrap(nodule, properties, function promisifyWrapper(shim, promisify) {
|
|
297
|
+
if (!shim.isFunction(promisify) || shim.isWrapped(promisify)) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return function __NR_wrappedPromisify() {
|
|
302
|
+
const promisified = promisify.apply(this, arguments)
|
|
303
|
+
if (typeof promisified !== 'function') {
|
|
304
|
+
return promisified
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
Object.keys(promisified).forEach(function forEachProperty(prop) {
|
|
308
|
+
__NR_wrappedPromisified[prop] = promisified[prop]
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
return __NR_wrappedPromisified
|
|
312
|
+
function __NR_wrappedPromisified() {
|
|
313
|
+
const segment = shim.getActiveSegment()
|
|
314
|
+
if (!segment) {
|
|
315
|
+
return promisified.apply(this, arguments)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const prom = shim.applySegment(promisified, segment, true, this, arguments)
|
|
319
|
+
Contextualizer.link(null, prom, segment)
|
|
320
|
+
return prom
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
module.exports = PromiseShim
|
|
327
|
+
|
|
328
|
+
// -------------------------------------------------------------------------- //
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @private
|
|
332
|
+
*/
|
|
333
|
+
function _wrapExecutorContext(shim, args) {
|
|
334
|
+
const context = {
|
|
335
|
+
executor: args[0],
|
|
336
|
+
promise: null,
|
|
337
|
+
self: null,
|
|
338
|
+
args: null
|
|
339
|
+
}
|
|
340
|
+
contextExporter.__NR_executorContext = context
|
|
341
|
+
args[0] = contextExporter
|
|
342
|
+
|
|
343
|
+
function contextExporter(resolve, reject) {
|
|
344
|
+
context.self = this
|
|
345
|
+
context.args = shim.argsToArray.apply(shim, arguments)
|
|
346
|
+
context.args[0] = _wrapResolver(context, resolve)
|
|
347
|
+
context.args[1] = _wrapResolver(context, reject)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @private
|
|
353
|
+
*/
|
|
354
|
+
function _wrapResolver(context, fn) {
|
|
355
|
+
return function wrappedResolveReject(val) {
|
|
356
|
+
const promise = context.promise
|
|
357
|
+
if (promise && promise.__NR_context) {
|
|
358
|
+
promise.__NR_context.getSegment().touch()
|
|
359
|
+
}
|
|
360
|
+
fn(val)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @private
|
|
366
|
+
*/
|
|
367
|
+
function _wrapThen(shim, fn, name, useAllParams) {
|
|
368
|
+
// Don't wrap non-functions.
|
|
369
|
+
if (shim.isWrapped(fn) || !shim.isFunction(fn)) {
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return function __NR_wrappedThen() {
|
|
374
|
+
if (!(this instanceof shim._class)) {
|
|
375
|
+
return fn.apply(this, arguments)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const thenSegment = shim.getSegment()
|
|
379
|
+
const promise = this
|
|
380
|
+
|
|
381
|
+
// Wrap up the arguments and execute the real then.
|
|
382
|
+
let isWrapped = false
|
|
383
|
+
const args = new Array(arguments.length)
|
|
384
|
+
for (let i = 0; i < arguments.length; ++i) {
|
|
385
|
+
args[i] = wrapHandler(arguments[i], i, arguments.length)
|
|
386
|
+
}
|
|
387
|
+
const next = fn.apply(this, args)
|
|
388
|
+
|
|
389
|
+
// If we got a promise (which we should have), link the parent's context.
|
|
390
|
+
if (!isWrapped && next instanceof shim._class && next !== promise) {
|
|
391
|
+
Contextualizer.link(promise, next, thenSegment)
|
|
392
|
+
}
|
|
393
|
+
return next
|
|
394
|
+
|
|
395
|
+
function wrapHandler(handler, i, length) {
|
|
396
|
+
if (
|
|
397
|
+
!shim.isFunction(handler) || // Not a function
|
|
398
|
+
shim.isWrapped(handler) || // Already wrapped
|
|
399
|
+
(!useAllParams && i !== (length - 1)) // Don't want all and not last
|
|
400
|
+
) {
|
|
401
|
+
isWrapped = shim.isWrapped(handler)
|
|
402
|
+
return handler
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return function __NR_wrappedThenHandler() {
|
|
406
|
+
if (!next || !next.__NR_context) {
|
|
407
|
+
return handler.apply(this, arguments)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
let promSegment = next.__NR_context.getSegment()
|
|
411
|
+
const segment = promSegment || shim.getSegment()
|
|
412
|
+
if (segment && segment !== promSegment) {
|
|
413
|
+
next.__NR_context.setSegment(segment)
|
|
414
|
+
promSegment = segment
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
let ret = null
|
|
418
|
+
try {
|
|
419
|
+
ret = shim.applySegment(handler, promSegment, true, this, arguments)
|
|
420
|
+
} finally {
|
|
421
|
+
if (ret && typeof ret.then === 'function') {
|
|
422
|
+
ret = next.__NR_context.continueContext(ret)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return ret
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @private
|
|
433
|
+
*/
|
|
434
|
+
class Context {
|
|
435
|
+
constructor(segment) {
|
|
436
|
+
this.segments = [segment]
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
branch() {
|
|
440
|
+
return this.segments.push(null) - 1
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* @private
|
|
446
|
+
*/
|
|
447
|
+
class Contextualizer {
|
|
448
|
+
constructor(idx, context) {
|
|
449
|
+
this.parentIdx = -1
|
|
450
|
+
this.idx = idx
|
|
451
|
+
this.context = context
|
|
452
|
+
this.child = null
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
static link(prev, next, segment) {
|
|
456
|
+
let ctxlzr = prev && prev.__NR_context
|
|
457
|
+
if (ctxlzr && !ctxlzr.isActive()) {
|
|
458
|
+
ctxlzr = prev.__NR_context = null
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (ctxlzr) {
|
|
462
|
+
// If prev has one child already, branch the context and update the child.
|
|
463
|
+
if (ctxlzr.child) {
|
|
464
|
+
// When the branch-point is the 2nd through nth link in the chain, it is
|
|
465
|
+
// necessary to track its segment separately so the branches can parent
|
|
466
|
+
// their segments on the branch-point.
|
|
467
|
+
if (ctxlzr.parentIdx !== -1) {
|
|
468
|
+
ctxlzr.idx = ctxlzr.context.branch()
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// The first child needs to be updated to have its own branch as well. And
|
|
472
|
+
// each of that child's children must be updated with the new parent index.
|
|
473
|
+
// This is the only non-constant-time action for linking, but it only
|
|
474
|
+
// happens with branching promise chains specifically when the 2nd branch
|
|
475
|
+
// is added.
|
|
476
|
+
//
|
|
477
|
+
// Note: This does not account for branches of branches. That may result
|
|
478
|
+
// in improperly parented segments.
|
|
479
|
+
let parent = ctxlzr
|
|
480
|
+
let child = ctxlzr.child
|
|
481
|
+
const branchIdx = ctxlzr.context.branch()
|
|
482
|
+
do {
|
|
483
|
+
child.parentIdx = parent.idx
|
|
484
|
+
child.idx = branchIdx
|
|
485
|
+
parent = child
|
|
486
|
+
child = child.child
|
|
487
|
+
} while (child)
|
|
488
|
+
|
|
489
|
+
// We set the child to something falsey that isn't `null` so we can
|
|
490
|
+
// distinguish between having no child, having one child, and having
|
|
491
|
+
// multiple children.
|
|
492
|
+
ctxlzr.child = false
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// If this is a branching link then create a new branch for the next promise.
|
|
496
|
+
// Otherwise, we can just piggy-back on the previous link's spot.
|
|
497
|
+
const idx = ctxlzr.child === false ? ctxlzr.context.branch() : ctxlzr.idx
|
|
498
|
+
|
|
499
|
+
// Create a new context for this next promise.
|
|
500
|
+
next.__NR_context = new Contextualizer(idx, ctxlzr.context)
|
|
501
|
+
next.__NR_context.parentIdx = ctxlzr.idx
|
|
502
|
+
|
|
503
|
+
// If this was our first child, remember it in case we have a 2nd.
|
|
504
|
+
if (ctxlzr.child === null) {
|
|
505
|
+
ctxlzr.child = next.__NR_context
|
|
506
|
+
}
|
|
507
|
+
} else if (segment) {
|
|
508
|
+
// This next promise is the root of a chain. Either there was no previous
|
|
509
|
+
// promise or the promise was created out of context.
|
|
510
|
+
next.__NR_context = new Contextualizer(0, new Context(segment))
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
isActive() {
|
|
515
|
+
const segments = this.context.segments
|
|
516
|
+
const segment = segments[this.idx] || segments[this.parentIdx] || segments[0]
|
|
517
|
+
return segment && segment.transaction.isActive()
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
getSegment() {
|
|
521
|
+
const segments = this.context.segments
|
|
522
|
+
let segment = segments[this.idx]
|
|
523
|
+
if (segment == null) {
|
|
524
|
+
segment = segments[this.idx] = segments[this.parentIdx] || segments[0]
|
|
525
|
+
}
|
|
526
|
+
return segment
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
setSegment(segment) {
|
|
530
|
+
return this.context.segments[this.idx] = segment
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
toJSON() {
|
|
534
|
+
// No-op.
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
continueContext(prom) {
|
|
538
|
+
const self = this
|
|
539
|
+
const nextContext = prom.__NR_context
|
|
540
|
+
if (!nextContext) {
|
|
541
|
+
return prom
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// If we have `finally`, use that to sneak our context update.
|
|
545
|
+
if (typeof prom.finally === 'function') {
|
|
546
|
+
return prom.finally(__NR_continueContext)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// No `finally` means we need to hook into resolve and reject individually and
|
|
550
|
+
// pass through whatever happened.
|
|
551
|
+
return prom.then(function __NR_thenContext(val) {
|
|
552
|
+
__NR_continueContext()
|
|
553
|
+
return val
|
|
554
|
+
}, function __NR_catchContext(err) {
|
|
555
|
+
__NR_continueContext()
|
|
556
|
+
throw err // Re-throwing promise rejection, this is not New Relic's error.
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
function __NR_continueContext() {
|
|
560
|
+
self.setSegment(nextContext.getSegment())
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
package/lib/shim/shim.js
CHANGED
|
@@ -18,6 +18,10 @@ try {
|
|
|
18
18
|
logger.debug(err, 'Failed to load es6 shimming methods.')
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// Some modules do terrible things, like change the prototype of functions. To
|
|
22
|
+
// avoid crashing things we'll use a cached copy of apply everywhere.
|
|
23
|
+
const fnApply = Function.prototype.apply
|
|
24
|
+
|
|
21
25
|
/**
|
|
22
26
|
* Constructs a shim associated with the given agent instance.
|
|
23
27
|
*
|
|
@@ -118,6 +122,7 @@ Shim.prototype.isString = isString
|
|
|
118
122
|
Shim.prototype.isNumber = isNumber
|
|
119
123
|
Shim.prototype.isBoolean = isBoolean
|
|
120
124
|
Shim.prototype.isArray = isArray
|
|
125
|
+
Shim.prototype.isNull = isNull
|
|
121
126
|
Shim.prototype.toArray = toArray
|
|
122
127
|
Shim.prototype.argsToArray = argsToArray
|
|
123
128
|
Shim.prototype.normalizeIndex = normalizeIndex
|
|
@@ -592,7 +597,7 @@ function wrap(nodule, properties, spec, args) {
|
|
|
592
597
|
spec = this.setDefaults(spec, {matchArity: false})
|
|
593
598
|
|
|
594
599
|
// If we're just wrapping one thing, just wrap it and return.
|
|
595
|
-
if (
|
|
600
|
+
if (properties == null) {
|
|
596
601
|
this.logger.trace('Wrapping nodule itself.')
|
|
597
602
|
return _wrap(this, nodule, this.getName(nodule), spec, args)
|
|
598
603
|
}
|
|
@@ -696,7 +701,7 @@ function wrapReturn(nodule, properties, spec, args) {
|
|
|
696
701
|
fnArgs.unshift(fn) // `unshift` === `push_front`
|
|
697
702
|
ctx = ret = new (fn.bind.apply(fn, fnArgs))()
|
|
698
703
|
} else {
|
|
699
|
-
ret =
|
|
704
|
+
ret = fnApply.call(fn, ctx, arguments)
|
|
700
705
|
}
|
|
701
706
|
|
|
702
707
|
// Assemble the arguments to hand to the spec.
|
|
@@ -889,7 +894,7 @@ function record(nodule, properties, recordNamer) {
|
|
|
889
894
|
var segDesc = recordNamer.call(this, shim, fn, name, args)
|
|
890
895
|
if (!segDesc) {
|
|
891
896
|
shim.logger.trace('No segment descriptor for "%s", not recording.', name)
|
|
892
|
-
return
|
|
897
|
+
return fnApply.call(fn, this, args)
|
|
893
898
|
}
|
|
894
899
|
segDesc = new specs.RecorderSpec(segDesc)
|
|
895
900
|
|
|
@@ -905,12 +910,14 @@ function record(nodule, properties, recordNamer) {
|
|
|
905
910
|
|
|
906
911
|
if (!parent) {
|
|
907
912
|
shim.logger.debug('Not recording function %s, not in a transaction.', name)
|
|
908
|
-
return
|
|
913
|
+
return fnApply.call(fn, this, arguments)
|
|
909
914
|
}
|
|
910
915
|
|
|
911
|
-
if (
|
|
912
|
-
|
|
913
|
-
|
|
916
|
+
if (
|
|
917
|
+
segDesc.callbackRequired &&
|
|
918
|
+
!_hasValidCallbackArg(shim, args, segDesc.callback)
|
|
919
|
+
) {
|
|
920
|
+
return fnApply.call(fn, this, arguments)
|
|
914
921
|
}
|
|
915
922
|
|
|
916
923
|
// Only create a segment if:
|
|
@@ -1362,7 +1369,7 @@ function applySegment(func, segment, full, context, args, inContextCB) {
|
|
|
1362
1369
|
|
|
1363
1370
|
if (!segment) {
|
|
1364
1371
|
this.logger.trace('No segment to apply to function.')
|
|
1365
|
-
return
|
|
1372
|
+
return fnApply.call(func, context, args)
|
|
1366
1373
|
}
|
|
1367
1374
|
this.logger.trace('Applying segment %s', segment.name)
|
|
1368
1375
|
|
|
@@ -1380,7 +1387,13 @@ function applySegment(func, segment, full, context, args, inContextCB) {
|
|
|
1380
1387
|
|
|
1381
1388
|
// Execute the function and then return the tracer segment to the old one.
|
|
1382
1389
|
try {
|
|
1383
|
-
return
|
|
1390
|
+
return fnApply.call(func, context, args)
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
if (prevSegment === null && process.domain != null) {
|
|
1393
|
+
process.domain.__NR_segment = tracer.segment
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
throw error // Rethrowing application error, this is not an agent error.
|
|
1384
1397
|
} finally {
|
|
1385
1398
|
if (full) {
|
|
1386
1399
|
segment.touch()
|
|
@@ -1416,9 +1429,10 @@ function createSegment(name, recorder, parent) {
|
|
|
1416
1429
|
var opts = null
|
|
1417
1430
|
if (this.isString(name)) {
|
|
1418
1431
|
// createSegment(name [, recorder] [, parent])
|
|
1419
|
-
opts = new specs.SegmentSpec({name
|
|
1432
|
+
opts = new specs.SegmentSpec({name})
|
|
1420
1433
|
|
|
1421
|
-
if
|
|
1434
|
+
// if the recorder arg is not used, it can either be omitted or null
|
|
1435
|
+
if (this.isFunction(recorder) || this.isNull(recorder)) {
|
|
1422
1436
|
// createSegment(name, recorder [, parent])
|
|
1423
1437
|
opts.recorder = recorder
|
|
1424
1438
|
opts.parent = parent
|
|
@@ -1556,6 +1570,19 @@ function isPromise(obj) {
|
|
|
1556
1570
|
return obj && typeof obj.then === 'function'
|
|
1557
1571
|
}
|
|
1558
1572
|
|
|
1573
|
+
/**
|
|
1574
|
+
* Determines if the given value is null.
|
|
1575
|
+
*
|
|
1576
|
+
* @memberof Shim.prototype
|
|
1577
|
+
*
|
|
1578
|
+
* @param {*} val - The value to check.
|
|
1579
|
+
*
|
|
1580
|
+
* @return {bool} True if the value is null, else false.
|
|
1581
|
+
*/
|
|
1582
|
+
function isNull(val) {
|
|
1583
|
+
return val === null
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1559
1586
|
/**
|
|
1560
1587
|
* Converts an array-like object into an array.
|
|
1561
1588
|
*
|
package/lib/shimmer.js
CHANGED
|
@@ -13,6 +13,7 @@ var SHIM_TYPE_MAP = Object.create(null)
|
|
|
13
13
|
SHIM_TYPE_MAP[MODULE_TYPE.GENERIC] = shims.Shim
|
|
14
14
|
SHIM_TYPE_MAP[MODULE_TYPE.DATASTORE] = shims.DatastoreShim
|
|
15
15
|
SHIM_TYPE_MAP[MODULE_TYPE.MESSAGE] = shims.MessageShim
|
|
16
|
+
SHIM_TYPE_MAP[MODULE_TYPE.PROMISE] = shims.PromiseShim
|
|
16
17
|
SHIM_TYPE_MAP[MODULE_TYPE.TRANSACTION] = shims.TransactionShim
|
|
17
18
|
SHIM_TYPE_MAP[MODULE_TYPE.WEB_FRAMEWORK] = shims.WebFrameworkShim
|
|
18
19
|
|