Skip to content

Commit 98e0f69

Browse files
astormPeterEinberger
authored andcommitted
feat: delay loading error-callsites module (elastic#2906)
* feat: Delay loading error-callsites module to avoid side effects. elastic#2833
1 parent ee70b45 commit 98e0f69

File tree

5 files changed

+86
-5
lines changed

5 files changed

+86
-5
lines changed

CHANGELOG.asciidoc

+5
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ paths are considered for body capture. ({pull}2873[#2873])
6363
For example `require('node:http')` will now be instrumented.
6464
({issues}2816[#2816])
6565
66+
- Agent will delay loading of the `error-callsites` module until agent start time,
67+
and will not load the module if the agent is disabled/inactive. This prevents the
68+
setting of an `Error.prepareStackTrace` handler until necessary for stacktrace
69+
collection. ({issues}2833[#2833] {pull}2906[#2906])
70+
6671
[float]
6772
===== Bug fixes
6873

lib/agent.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ var { elasticApmAwsLambda } = require('./lambda')
2222
var Metrics = require('./metrics')
2323
var parsers = require('./parsers')
2424
var symbols = require('./symbols')
25-
const { frameCacheStats } = require('./stacktraces')
25+
const { frameCacheStats, initStackTraceCollection } = require('./stacktraces')
2626
const Span = require('./instrumentation/span')
2727
const Transaction = require('./instrumentation/transaction')
2828

@@ -261,6 +261,7 @@ Agent.prototype.start = function (opts) {
261261
}, 'agent configured correctly')
262262
}
263263

264+
initStackTraceCollection()
264265
this._transport = this._conf.transport(this._conf, this)
265266

266267
let runContextClass

lib/stacktraces.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,14 @@ var path = require('path')
1818

1919
const asyncCache = require('async-cache')
2020
const afterAllResults = require('after-all-results')
21-
const errorCallsites = require('error-callsites')
21+
22+
// avoid loading error-callsites until needed to avoid
23+
// Error.prepareStackTrace side-effects
24+
// https://github.com/elastic/apm-agent-nodejs/issues/2833
25+
let errorCallsites
26+
function initStackTraceCollection () {
27+
errorCallsites = require('error-callsites')
28+
}
2229
const errorStackParser = require('error-stack-parser')
2330
const loadSourceMap = require('./load-source-map')
2431
const LRU = require('lru-cache')
@@ -408,7 +415,7 @@ function frameFromCallSite (log, callsite, cwd, sourceLinesAppFrames, sourceLine
408415
function gatherStackTrace (log, err, sourceLinesAppFrames, sourceLinesLibraryFrames, filterCallSite, cb) {
409416
// errorCallsites returns an array of v8 CallSite objects.
410417
// https://v8.dev/docs/stack-trace-api#customizing-stack-traces
411-
let callsites = errorCallsites(err)
418+
let callsites = errorCallsites ? errorCallsites(err) : null
412419

413420
const next = afterAllResults(function finish (_err, stacktrace) {
414421
// _err is always null from frameFromCallSite.
@@ -424,7 +431,7 @@ function gatherStackTrace (log, err, sourceLinesAppFrames, sourceLinesLibraryFra
424431

425432
if (!isValidCallsites(callsites)) {
426433
// When can this happen? Another competing Error.prepareStackTrace breaking
427-
// error-callsites?
434+
// error-callsites? Also initStackTraceCollection not having been called.
428435
log.debug('could not get valid callsites from error "%s"', err)
429436
} else if (callsites) {
430437
if (filterCallSite) {
@@ -447,6 +454,7 @@ function gatherStackTrace (log, err, sourceLinesAppFrames, sourceLinesLibraryFra
447454
module.exports = {
448455
gatherStackTrace,
449456
frameCacheStats,
457+
initStackTraceCollection,
450458

451459
// Exported for testing only.
452460
stackTraceFromErrStackString
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and other contributors where applicable.
3+
* Licensed under the BSD 2-Clause License; you may not use this file except in
4+
* compliance with the BSD 2-Clause License.
5+
*/
6+
7+
// print name of error.prepareStackTrace function to STDOUT
8+
require('../../../').start({
9+
serviceName: 'test-get-prepare-stacktrace',
10+
logUncaughtExceptions: true,
11+
metricsInterval: 0,
12+
centralConfig: false,
13+
logLevel: 'off'
14+
})
15+
function main () {
16+
const name = Error.prepareStackTrace ? Error.prepareStackTrace.name : undefined
17+
console.log(name)
18+
}
19+
20+
main()

test/stacktraces/stacktraces.test.js

+48-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const tape = require('tape')
1616

1717
const logging = require('../../lib/logging')
1818
const { MockAPMServer } = require('../_mock_apm_server')
19-
const { gatherStackTrace, stackTraceFromErrStackString } = require('../../lib/stacktraces')
19+
const { gatherStackTrace, initStackTraceCollection, stackTraceFromErrStackString } = require('../../lib/stacktraces')
2020

2121
const log = logging.createLogger('off')
2222

@@ -304,6 +304,7 @@ tape.test('stackTraceFromErrStackString()', function (t) {
304304
})
305305

306306
tape.test('gatherStackTrace()', function (suite) {
307+
initStackTraceCollection()
307308
function thisIsMyFunction () {
308309
// before 2
309310
// before 1
@@ -398,5 +399,51 @@ tape.test('gatherStackTrace()', function (suite) {
398399
})
399400
})
400401

402+
tape.test('Error.prepareStackTrace is set', function (t) {
403+
const server = new MockAPMServer()
404+
server.start(function (serverUrl) {
405+
execFile(
406+
process.execPath,
407+
['fixtures/get-prepare-stacktrace.js'],
408+
{
409+
cwd: __dirname,
410+
timeout: 3000,
411+
env: Object.assign({}, process.env, {
412+
ELASTIC_APM_ACTIVE: true
413+
})
414+
},
415+
function done (err, _stdout, _stderr) {
416+
t.ok(!err)
417+
t.equals(_stdout.trim(), 'csPrepareStackTrace', 'Error.prepareStackTrace is set')
418+
server.close()
419+
t.end()
420+
}
421+
)
422+
})
423+
})
424+
425+
tape.test('Error.prepareStackTrace is not set', function (t) {
426+
const server = new MockAPMServer()
427+
server.start(function (serverUrl) {
428+
execFile(
429+
process.execPath,
430+
['fixtures/get-prepare-stacktrace.js'],
431+
{
432+
cwd: __dirname,
433+
timeout: 3000,
434+
env: Object.assign({}, process.env, {
435+
ELASTIC_APM_ACTIVE: false
436+
})
437+
},
438+
function done (err, _stdout, _stderr) {
439+
t.ok(!err)
440+
t.equals(_stdout.trim(), 'undefined', 'Error.prepareStackTrace is set')
441+
server.close()
442+
t.end()
443+
}
444+
)
445+
})
446+
})
447+
401448
suite.end()
402449
})

0 commit comments

Comments
 (0)