├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── benchmark.js ├── benchmark ├── memory-readstream.js ├── memory.js ├── plot-memory.R ├── time-fsaccess.js ├── time-setimmediate.js └── time.js ├── example.js ├── package.json ├── test ├── expected │ ├── promise-child-stack.txt │ ├── regression-delayed-formater.txt │ ├── regression-http-close.txt │ ├── regression-http-parser.txt │ ├── regression-net-connect.txt │ ├── regression-net-socket.txt │ ├── regression-timer-clear.txt │ ├── simple-fs-read.txt │ ├── simple-next-tick.txt │ └── simple-timer.txt ├── scripts │ ├── promise-child-stack.js │ ├── regression-delayed-formater.js │ ├── regression-http-close.js │ ├── regression-http-parser.js │ ├── regression-net-connect.js │ ├── regression-net-socket.js │ ├── regression-timer-clear.js │ ├── simple-fs-read.js │ ├── simple-next-tick.js │ └── simple-timer.js └── test.js └── trace.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [**] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | charset = utf-8 10 | indent_style = space 11 | trim_trailing_whitespace = true 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "rules": { 7 | "quotes": [1, "single", "avoid-escape"], 8 | "curly": 0, 9 | "strict": [2, "global"], 10 | "no-shadow": 0, 11 | "no-underscore-dangle": 0, 12 | "no-use-before-define": [1, "nofunc"], 13 | "no-new-func": 0, 14 | "prefer-const": 1, 15 | "no-var": 1 16 | }, 17 | "extends": [ 18 | "eslint:recommended" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules/ 3 | /test/temp/ 4 | npm-debug.log 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | sudo: false 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Andreas Madsen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # trace [![Build Status](https://secure.travis-ci.org/AndreasMadsen/trace.svg)](http://travis-ci.org/AndreasMadsen/trace) 2 | 3 | > Creates super long stack traces 4 | 5 | See https://trace.js.org for examples. 6 | 7 | **Trace only works with node.js v8.x and newer.** Use `npm install trace@^2` 8 | for node.js v6 and v4. 9 | 10 | ## How 11 | 12 | ```sheel 13 | npm install trace --save 14 | node --stack_trace_limit=100 -r trace debug-me.js 15 | ``` 16 | 17 | This will provide a very long stack trace, if you are not interested in 18 | node internal lines, take a look at [clarify](https://github.com/AndreasMadsen/clarify). 19 | 20 | ```sheel 21 | npm install clarify --save 22 | node --stack_trace_limit=100 -r trace -r clarify debug-me.js 23 | ``` 24 | 25 | For specific examples see https://trace.js.org. 26 | 27 | ## Found a bug? 28 | 29 | I encourage you to file any bugs you may find, even if you can't reduce the 30 | issue to only involve nodecore modules. I will then use all my power to fix it. 31 | 32 | > I want this module to be the best and most trusted async trace module ever! 33 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const summary = require('summary'); 3 | 4 | function timeit(top, callback) { 5 | const times = new Float64Array(top); 6 | (function recursive(i) { 7 | const tick = process.hrtime(); 8 | setImmediate(function () { 9 | if (i === top) { 10 | callback(summary(times)); 11 | } else { 12 | const tock = process.hrtime(tick); 13 | times[i] = tock[0] * 1e9 + tock[1]; 14 | 15 | recursive(i + 1); 16 | } 17 | }); 18 | })(0); 19 | } 20 | 21 | ({ 22 | 'master': function () { 23 | const fork = require('child_process').fork; 24 | 25 | function bench(name, callback) { 26 | const cp = fork(__filename, [name]); 27 | cp.once('message', function (stat) { 28 | console.log(name + ': ' + stat.mean.toFixed(4) + ' ± ' + (1.96 * stat.sd).toFixed(4) + ' ns/tick'); 29 | }); 30 | cp.once('close', callback); 31 | } 32 | 33 | bench('baseline', function () { 34 | bench('trace', function () { 35 | console.log('done'); 36 | }); 37 | }); 38 | }, 39 | 40 | 'baseline': function () { 41 | const top = 500000; 42 | timeit(top, function (stat) { 43 | process.send({ 'mean': stat.mean(), 'sd': stat.sd() }); 44 | }); 45 | }, 46 | 47 | 'trace': function () { 48 | require('./trace.js'); 49 | const top = 100000; 50 | timeit(top, function (stat) { 51 | process.send({ 'mean': stat.mean(), 'sd': stat.sd() }); 52 | }); 53 | } 54 | })[process.argv[2] || 'master'](); 55 | -------------------------------------------------------------------------------- /benchmark/memory-readstream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tracker = require('./memory.js'); 4 | const fs = require('fs'); 5 | require('../trace.js'); 6 | 7 | tracker.start(); 8 | 9 | const stream = fs.createReadStream('/dev/random'); 10 | stream.resume(); 11 | 12 | setTimeout(function () { 13 | stream.pause(); 14 | setTimeout(function () { 15 | tracker.stop(); 16 | }, 5 * 1000); 17 | }, 5 * 60 * 1000); 18 | -------------------------------------------------------------------------------- /benchmark/memory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const setInterval = global.setInterval; 6 | 7 | function MemoryTracker() { 8 | this.stream = fs.createWriteStream( 9 | path.basename(process.argv[1], '.js') + '.csv' 10 | ); 11 | this.stream.write('"time", "rss", "heapTotal", "heapUsed"\n'); 12 | this.timer = null; 13 | } 14 | 15 | MemoryTracker.prototype.start = function () { 16 | const self = this; 17 | this.timer = setInterval(function() { 18 | const time = Date.now(); 19 | const usage = process.memoryUsage(); 20 | self.stream.write(`${time}, ${usage.rss}, ${usage.heapTotal}, ${usage.heapUsed}\n`); 21 | }, 200); 22 | }; 23 | 24 | MemoryTracker.prototype.stop = function () { 25 | clearTimeout(this.timer); 26 | this.stream.end(); 27 | }; 28 | 29 | module.exports = new MemoryTracker(); 30 | -------------------------------------------------------------------------------- /benchmark/plot-memory.R: -------------------------------------------------------------------------------- 1 | library("reshape2") 2 | library("ggplot2") 3 | 4 | args <- commandArgs(trailingOnly = TRUE); 5 | sourcefile = args[1]; 6 | 7 | data = read.csv(sourcefile); 8 | data = melt(data, id="time"); 9 | data$time = (data$time - data$tim[1]) / 1000; 10 | data$value = data$value / (1024 * 1024); 11 | 12 | p = ggplot(data=data, aes(x=time, y=value, colour=variable)); 13 | p = p + geom_line(); 14 | p = p + xlab('time [sec]'); 15 | p = p + ylab('usage [MB]'); 16 | 17 | png(gsub('.csv', '.png', sourcefile), width = 700, height = 400); 18 | print(p); 19 | dev.off(); 20 | -------------------------------------------------------------------------------- /benchmark/time-fsaccess.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const benchmark = require('./time.js'); 4 | 5 | benchmark({ 6 | baseline: 500000, 7 | trace: 100000 8 | }, test); 9 | 10 | function test(done) { 11 | setImmediate(done); 12 | } 13 | -------------------------------------------------------------------------------- /benchmark/time-setimmediate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const benchmark = require('./time.js'); 4 | 5 | benchmark({ 6 | baseline: 500000, 7 | trace: 100000 8 | }, test); 9 | 10 | function test(done) { 11 | setImmediate(done); 12 | } 13 | -------------------------------------------------------------------------------- /benchmark/time.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const summary = require('summary'); 3 | 4 | function TimeIt(runs, asyncFn) { 5 | this.runs = runs; 6 | this.asyncFn = asyncFn; 7 | 8 | this.times = new Float64Array(runs); 9 | } 10 | 11 | TimeIt.prototype._addTimes = function (index, callback) { 12 | const self = this; 13 | const tick = process.hrtime(); 14 | this.asyncFn(function (err) { 15 | const tock = process.hrtime(tick); 16 | 17 | if (err) return callback(err); 18 | 19 | if (index === self.runs) return callback(null); 20 | 21 | self.times[index] = tock[0] * 1e9 + tock[1]; 22 | self._addTimes(index + 1, callback); 23 | }); 24 | } 25 | 26 | TimeIt.prototype.run = function (callback) { 27 | const self = this; 28 | this._addTimes(0, function (err) { 29 | if (err) return callback(err); 30 | callback(null, summary(self.times)); 31 | }); 32 | }; 33 | 34 | const programs = { 35 | master: function () { 36 | const fork = require('child_process').fork; 37 | 38 | function bench(name, callback) { 39 | const cp = fork(process.argv[1], [name]); 40 | cp.once('message', function (stat) { 41 | console.log(name + ': ' + stat.mean.toFixed(4) + ' ± ' + (1.96 * stat.sd).toFixed(4) + ' ns/tick'); 42 | }); 43 | cp.once('close', callback); 44 | } 45 | 46 | bench('baseline', function () { 47 | bench('trace', function () { 48 | console.log('done'); 49 | }); 50 | }); 51 | }, 52 | 53 | baseline: function (runs, asyncFn) { 54 | new TimeIt(runs.baseline, asyncFn).run(function (err, stat) { 55 | if (err) throw err; 56 | process.send({ 'mean': stat.mean(), 'sd': stat.sd() }); 57 | }); 58 | }, 59 | 60 | trace: function (runs, asyncFn) { 61 | require('../trace.js'); 62 | new TimeIt(runs.baseline, asyncFn).run(function (err, stat) { 63 | if (err) throw err; 64 | process.send({ 'mean': stat.mean(), 'sd': stat.sd() }); 65 | }); 66 | } 67 | }; 68 | 69 | 70 | function benchmark(runs, asyncFn) { 71 | const program = process.argv[2] || 'master'; 72 | programs[program](runs, asyncFn); 73 | } 74 | module.exports = benchmark; 75 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | require('trace'); // active long stack trace 2 | require('clarify'); // Exclude node internal calls from the stack 3 | 4 | var fs = require('fs'); 5 | 6 | // There is no limit for the size of the stack trace (v8 default is 10) 7 | Error.stackTraceLimit = Infinity; 8 | 9 | setTimeout(function () { 10 | fs.readFile(__filename, function () { 11 | process.nextTick(function () { 12 | throw new Error("custom error"); 13 | }); 14 | }); 15 | }, 200); 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trace", 3 | "description": "Creates super long stack traces", 4 | "version": "3.2.0", 5 | "author": "Andreas Madsen ", 6 | "main": "./trace.js", 7 | "scripts": { 8 | "test": "tap ./test/test.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/AndreasMadsen/trace.git" 13 | }, 14 | "keywords": [ 15 | "trace", 16 | "long stacks", 17 | "long trace", 18 | "stack", 19 | "call", 20 | "async" 21 | ], 22 | "dependencies": { 23 | "stack-chain": "^2.0.0" 24 | }, 25 | "devDependencies": { 26 | "interpreted": "^0.6.0", 27 | "execspawn": "^1.0.1", 28 | "endpoint": "^0.4.0", 29 | "clarify": "^2.0.0", 30 | "summary": "^0.3.0", 31 | "tap": "^11.0.0" 32 | }, 33 | "license": "MIT", 34 | "engines": { 35 | "node": ">=8.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/expected/promise-child-stack.txt: -------------------------------------------------------------------------------- 1 | Error: hello 2 | at scopeThrow (/scripts/promise-child-stack.js:r:c) 3 | at Promise.then () 4 | at Object. (/scripts/promise-child-stack.js:r:c) 5 | at Function.resolve () 6 | at Object. (/scripts/promise-child-stack.js:r:c) 7 | at Module._compile (node:internal/modules/cjs/loader:r:c) 8 | at Module._extensions..js (node:internal/modules/cjs/loader:r:c) 9 | at Module.load (node:internal/modules/cjs/loader:r:c) 10 | at Module._load (node:internal/modules/cjs/loader:r:c) 11 | at TracingChannel.traceSync (node:diagnostics_channel:r:c) 12 | at wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 13 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 14 | at node:internal/main/run_main_module:r:c 15 | -------------------------------------------------------------------------------- /test/expected/regression-delayed-formater.txt: -------------------------------------------------------------------------------- 1 | /scripts/regression-delayed-formater.js:17 2 | throw new Error('trace'); 3 | ^ 4 | 5 | Error: trace 6 | at-hack Object. (/scripts/regression-delayed-formater.js:r:c) 7 | at-hack Module._compile (node:internal/modules/cjs/loader:r:c) 8 | at-hack Module._extensions..js (node:internal/modules/cjs/loader:r:c) 9 | at-hack Module.load (node:internal/modules/cjs/loader:r:c) 10 | at-hack Module._load (node:internal/modules/cjs/loader:r:c) 11 | at-hack TracingChannel.traceSync (node:diagnostics_channel:r:c) 12 | at-hack wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 13 | at-hack Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 14 | at-hack node:internal/main/run_main_module:r:c { 15 | [Symbol(originalCallSite)]: [ 16 | CallSite {}, CallSite {}, 17 | CallSite {}, CallSite {}, 18 | CallSite {}, CallSite {}, 19 | CallSite {}, CallSite {}, 20 | CallSite {} 21 | ], 22 | [Symbol(mutatedCallSite)]: [ 23 | CallSite {}, CallSite {}, 24 | CallSite {}, CallSite {}, 25 | CallSite {}, CallSite {}, 26 | CallSite {}, CallSite {}, 27 | CallSite {} 28 | ] 29 | } 30 | 31 | Node.js vx.y.z 32 | -------------------------------------------------------------------------------- /test/expected/regression-http-close.txt: -------------------------------------------------------------------------------- 1 | /scripts/regression-http-close.js:20 2 | throw new Error('trace'); 3 | ^ 4 | 5 | Error: trace 6 | at Server.close (/scripts/regression-http-close.js:r:c) 7 | at Object.onceWrapper (node:events:r:c) 8 | at Server.emit (node:events:r:c) 9 | at emitCloseNT (node:net:r:c) 10 | at process.processTicksAndRejections (node:internal/process/task_queues:r:c) 11 | at nextTick (node:internal/process/task_queues:r:c) 12 | at Server._emitCloseIfDrained (node:net:r:c) 13 | at Server.close (node:net:r:c) 14 | at Server.close (node:_http_server:r:c) 15 | at IncomingMessage.end (/scripts/regression-http-close.js:r:c) 16 | at Object.onceWrapper (node:events:r:c) 17 | at IncomingMessage.emit (node:events:r:c) 18 | at endReadableNT (node:internal/streams/readable:r:c) 19 | at process.processTicksAndRejections (node:internal/process/task_queues:r:c) 20 | at createServerHandle (node:net:r:c) 21 | at Server.setupListenHandle [as _listen2] (node:net:r:c) 22 | at listenInCluster (node:net:r:c) 23 | at GetAddrInfoReqWrap.callback (node:net:r:c) 24 | at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:r:c) 25 | at Object.lookup (node:dns:r:c) 26 | at lookupAndListen (node:net:r:c) 27 | at Server.listen (node:net:r:c) 28 | at Object. (/scripts/regression-http-close.js:r:c) 29 | at Module._compile (node:internal/modules/cjs/loader:r:c) 30 | at Module._extensions..js (node:internal/modules/cjs/loader:r:c) 31 | at Module.load (node:internal/modules/cjs/loader:r:c) 32 | at Module._load (node:internal/modules/cjs/loader:r:c) 33 | at TracingChannel.traceSync (node:diagnostics_channel:r:c) 34 | at wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 35 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 36 | at node:internal/main/run_main_module:r:c { 37 | [Symbol(originalCallSite)]: [ CallSite {}, CallSite {}, CallSite {}, CallSite {}, CallSite {} ], 38 | [Symbol(mutatedCallSite)]: [ 39 | CallSite {}, CallSite {}, CallSite {}, 40 | CallSite {}, CallSite {}, CallSite {}, 41 | CallSite {}, CallSite {}, CallSite {}, 42 | CallSite {}, CallSite {}, CallSite {}, 43 | CallSite {}, CallSite {}, CallSite {}, 44 | CallSite {}, CallSite {}, CallSite {}, 45 | CallSite {}, CallSite {}, CallSite {}, 46 | CallSite {}, CallSite {}, CallSite {}, 47 | CallSite {}, CallSite {}, CallSite {}, 48 | CallSite {}, CallSite {}, CallSite {}, 49 | CallSite {} 50 | ] 51 | } 52 | 53 | Node.js vx.y.z 54 | -------------------------------------------------------------------------------- /test/expected/regression-http-parser.txt: -------------------------------------------------------------------------------- 1 | node:internal/url:816 2 | const href = bindingUrl.parse(input, base, raiseException); 3 | ^ 4 | 5 | TypeError: Invalid URL 6 | at new URL (node:internal/url:r:c) 7 | at new ClientRequest (node:_http_client:r:c) 8 | at request (node:http:r:c) 9 | at Object.get (node:http:r:c) 10 | at Server. (/scripts/regression-http-parser.js:r:c) 11 | at Object.onceWrapper (node:events:r:c) 12 | at Server.emit (node:events:r:c) 13 | at emitListeningNT (node:net:r:c) 14 | at process.processTicksAndRejections (node:internal/process/task_queues:r:c) 15 | at nextTick (node:internal/process/task_queues:r:c) 16 | at Server.setupListenHandle [as _listen2] (node:net:r:c) 17 | at createServerHandle (node:net:r:c) 18 | at Server.setupListenHandle [as _listen2] (node:net:r:c) 19 | at listenInCluster (node:net:r:c) 20 | at GetAddrInfoReqWrap.callback (node:net:r:c) 21 | at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:r:c) 22 | at Object.lookup (node:dns:r:c) 23 | at lookupAndListen (node:net:r:c) 24 | at Server.listen (node:net:r:c) 25 | at Object. (/scripts/regression-http-parser.js:r:c) 26 | at Module._compile (node:internal/modules/cjs/loader:r:c) 27 | at Module._extensions..js (node:internal/modules/cjs/loader:r:c) 28 | at Module.load (node:internal/modules/cjs/loader:r:c) 29 | at Module._load (node:internal/modules/cjs/loader:r:c) 30 | at TracingChannel.traceSync (node:diagnostics_channel:r:c) 31 | at wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 32 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 33 | at node:internal/main/run_main_module:r:c { 34 | code: 'ERR_INVALID_URL', 35 | input: 'http://::r:c', 36 | [Symbol(originalCallSite)]: [ 37 | CallSite {}, CallSite {}, 38 | CallSite {}, CallSite {}, 39 | CallSite {}, CallSite {}, 40 | CallSite {}, CallSite {}, 41 | CallSite {} 42 | ], 43 | [Symbol(mutatedCallSite)]: [ 44 | CallSite {}, CallSite {}, CallSite {}, 45 | CallSite {}, CallSite {}, CallSite {}, 46 | CallSite {}, CallSite {}, CallSite {}, 47 | CallSite {}, CallSite {}, CallSite {}, 48 | CallSite {}, CallSite {}, CallSite {}, 49 | CallSite {}, CallSite {}, CallSite {}, 50 | CallSite {}, CallSite {}, CallSite {}, 51 | CallSite {}, CallSite {}, CallSite {}, 52 | CallSite {}, CallSite {}, CallSite {}, 53 | CallSite {} 54 | ] 55 | } 56 | 57 | Node.js vx.y.z 58 | -------------------------------------------------------------------------------- /test/expected/regression-net-connect.txt: -------------------------------------------------------------------------------- 1 | /scripts/regression-net-connect.js:11 2 | throw error; 3 | ^ 4 | 5 | AggregateError [ECONNREFUSED]: 6 | at internalConnectMultiple (node:net:r:c) 7 | at afterConnectMultiple (node:net:r:c) 8 | at internalConnectMultiple (node:net:r:c) 9 | at internalConnectMultiple (node:net:r:c) 10 | at afterConnectMultiple (node:net:r:c) 11 | at internalConnectMultiple (node:net:r:c) 12 | at GetAddrInfoReqWrap.emitLookup [as callback] (node:net:r:c) 13 | at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:r:c) 14 | at Socket.connect (node:net:r:c) 15 | at Object.connect (node:net:r:c) 16 | at Object. (/scripts/regression-net-connect.js:r:c) 17 | at Module._compile (node:internal/modules/cjs/loader:r:c) 18 | at Module._extensions..js (node:internal/modules/cjs/loader:r:c) 19 | at Module.load (node:internal/modules/cjs/loader:r:c) 20 | at Module._load (node:internal/modules/cjs/loader:r:c) 21 | at TracingChannel.traceSync (node:diagnostics_channel:r:c) 22 | at wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 23 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 24 | at node:internal/main/run_main_module:r:c { 25 | code: 'ECONNREFUSED', 26 | [Symbol(originalCallSite)]: [ CallSite {}, CallSite {}, CallSite {} ], 27 | [Symbol(mutatedCallSite)]: [ 28 | CallSite {}, CallSite {}, 29 | CallSite {}, CallSite {}, 30 | CallSite {}, CallSite {}, 31 | CallSite {}, CallSite {}, 32 | CallSite {}, CallSite {}, 33 | CallSite {}, CallSite {}, 34 | CallSite {}, CallSite {}, 35 | CallSite {}, CallSite {}, 36 | CallSite {}, CallSite {}, 37 | CallSite {} 38 | ], 39 | [errors]: [ 40 | Error: connect ECONNREFUSED ::r:c 41 | at createConnectionError (node:net:r:c) 42 | at afterConnectMultiple (node:net:r:c) 43 | at process.nextTick (node:internal/process/task_queues:r:c) 44 | at onDestroy (node:internal/streams/destroy:r:c) 45 | at Socket._destroy (node:net:r:c) 46 | at _destroy (node:internal/streams/destroy:r:c) 47 | at Socket.destroy (node:internal/streams/destroy:r:c) 48 | at Writable.destroy (node:internal/streams/writable:r:c) 49 | at internalConnectMultiple (node:net:r:c) 50 | at afterConnectMultiple (node:net:r:c) 51 | at internalConnectMultiple (node:net:r:c) 52 | at internalConnectMultiple (node:net:r:c) 53 | at afterConnectMultiple (node:net:r:c) 54 | at internalConnectMultiple (node:net:r:c) 55 | at GetAddrInfoReqWrap.emitLookup [as callback] (node:net:r:c) 56 | at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:r:c) 57 | at Socket.connect (node:net:r:c) 58 | at Object.connect (node:net:r:c) 59 | at Object. (/scripts/regression-net-connect.js:r:c) 60 | at Module._compile (node:internal/modules/cjs/loader:r:c) 61 | at Module._extensions..js (node:internal/modules/cjs/loader:r:c) 62 | at Module.load (node:internal/modules/cjs/loader:r:c) 63 | at Module._load (node:internal/modules/cjs/loader:r:c) 64 | at TracingChannel.traceSync (node:diagnostics_channel:r:c) 65 | at wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 66 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 67 | at node:internal/main/run_main_module:r:c { 68 | errno: -61, 69 | code: 'ECONNREFUSED', 70 | syscall: 'connect', 71 | address: '::1', 72 | port: 24075 73 | }, 74 | Error: connect ECONNREFUSED 127.0.0.1:24075 75 | at createConnectionError (node:net:r:c) 76 | at afterConnectMultiple (node:net:r:c) 77 | at process.nextTick (node:internal/process/task_queues:r:c) 78 | at onDestroy (node:internal/streams/destroy:r:c) 79 | at Socket._destroy (node:net:r:c) 80 | at _destroy (node:internal/streams/destroy:r:c) 81 | at Socket.destroy (node:internal/streams/destroy:r:c) 82 | at Writable.destroy (node:internal/streams/writable:r:c) 83 | at internalConnectMultiple (node:net:r:c) 84 | at afterConnectMultiple (node:net:r:c) 85 | at internalConnectMultiple (node:net:r:c) 86 | at internalConnectMultiple (node:net:r:c) 87 | at afterConnectMultiple (node:net:r:c) 88 | at internalConnectMultiple (node:net:r:c) 89 | at GetAddrInfoReqWrap.emitLookup [as callback] (node:net:r:c) 90 | at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:r:c) 91 | at Socket.connect (node:net:r:c) 92 | at Object.connect (node:net:r:c) 93 | at Object. (/scripts/regression-net-connect.js:r:c) 94 | at Module._compile (node:internal/modules/cjs/loader:r:c) 95 | at Module._extensions..js (node:internal/modules/cjs/loader:r:c) 96 | at Module.load (node:internal/modules/cjs/loader:r:c) 97 | at Module._load (node:internal/modules/cjs/loader:r:c) 98 | at TracingChannel.traceSync (node:diagnostics_channel:r:c) 99 | at wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 100 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 101 | at node:internal/main/run_main_module:r:c { 102 | errno: -61, 103 | code: 'ECONNREFUSED', 104 | syscall: 'connect', 105 | address: '127.0.0.1', 106 | port: 24075 107 | } 108 | ] 109 | } 110 | 111 | Node.js vx.y.z 112 | -------------------------------------------------------------------------------- /test/expected/regression-net-socket.txt: -------------------------------------------------------------------------------- 1 | /scripts/regression-net-socket.js:6 2 | throw new Error('trace'); 3 | ^ 4 | 5 | Error: trace 6 | at Server. (/scripts/regression-net-socket.js:r:c) 7 | at Server.emit (node:events:r:c) 8 | at TCP.onconnection (node:net:r:c) 9 | at createServerHandle (node:net:r:c) 10 | at Server.setupListenHandle [as _listen2] (node:net:r:c) 11 | at listenInCluster (node:net:r:c) 12 | at GetAddrInfoReqWrap.callback (node:net:r:c) 13 | at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:r:c) 14 | at Object.lookup (node:dns:r:c) 15 | at lookupAndListen (node:net:r:c) 16 | at Server.listen (node:net:r:c) 17 | at Object. (/scripts/regression-net-socket.js:r:c) 18 | at Module._compile (node:internal/modules/cjs/loader:r:c) 19 | at Module._extensions..js (node:internal/modules/cjs/loader:r:c) 20 | at Module.load (node:internal/modules/cjs/loader:r:c) 21 | at Module._load (node:internal/modules/cjs/loader:r:c) 22 | at TracingChannel.traceSync (node:diagnostics_channel:r:c) 23 | at wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 24 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 25 | at node:internal/main/run_main_module:r:c { 26 | [Symbol(originalCallSite)]: [ CallSite {}, CallSite {}, CallSite {}, CallSite {} ], 27 | [Symbol(mutatedCallSite)]: [ 28 | CallSite {}, CallSite {}, 29 | CallSite {}, CallSite {}, 30 | CallSite {}, CallSite {}, 31 | CallSite {}, CallSite {}, 32 | CallSite {}, CallSite {}, 33 | CallSite {}, CallSite {}, 34 | CallSite {}, CallSite {}, 35 | CallSite {}, CallSite {}, 36 | CallSite {}, CallSite {}, 37 | CallSite {}, CallSite {} 38 | ] 39 | } 40 | 41 | Node.js vx.y.z 42 | -------------------------------------------------------------------------------- /test/expected/regression-timer-clear.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasMadsen/trace/4c45a48807f0618f2cd9f1c5ee7c20a5974b2081/test/expected/regression-timer-clear.txt -------------------------------------------------------------------------------- /test/expected/simple-fs-read.txt: -------------------------------------------------------------------------------- 1 | /scripts/simple-fs-read.js:10 2 | throw new Error('trace'); 3 | ^ 4 | 5 | Error: trace 6 | at callback (/scripts/simple-fs-read.js:r:c) 7 | at FSReqCallback.readFileAfterClose [as oncomplete] (node:internal/fs/read/context:r:c) 8 | at ReadFileContext.close (node:internal/fs/read/context:r:c) 9 | at FSReqCallback.readFileAfterRead [as oncomplete] (node:internal/fs/read/context:r:c) 10 | at ReadFileContext.read (node:internal/fs/read/context:r:c) 11 | at FSReqCallback.readFileAfterStat [as oncomplete] (node:fs:r:c) 12 | at FSReqCallback.readFileAfterOpen [as oncomplete] (node:fs:r:c) 13 | at Object.readFile (node:fs:r:c) 14 | at Object. (/scripts/simple-fs-read.js:r:c) 15 | at Module._compile (node:internal/modules/cjs/loader:r:c) 16 | at Module._extensions..js (node:internal/modules/cjs/loader:r:c) 17 | at Module.load (node:internal/modules/cjs/loader:r:c) 18 | at Module._load (node:internal/modules/cjs/loader:r:c) 19 | at TracingChannel.traceSync (node:diagnostics_channel:r:c) 20 | at wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 21 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 22 | at node:internal/main/run_main_module:r:c { 23 | [Symbol(originalCallSite)]: [ CallSite {}, CallSite {}, CallSite {} ], 24 | [Symbol(mutatedCallSite)]: [ 25 | CallSite {}, CallSite {}, 26 | CallSite {}, CallSite {}, 27 | CallSite {}, CallSite {}, 28 | CallSite {}, CallSite {}, 29 | CallSite {}, CallSite {}, 30 | CallSite {}, CallSite {}, 31 | CallSite {}, CallSite {}, 32 | CallSite {}, CallSite {}, 33 | CallSite {} 34 | ] 35 | } 36 | 37 | Node.js vx.y.z 38 | -------------------------------------------------------------------------------- /test/expected/simple-next-tick.txt: -------------------------------------------------------------------------------- 1 | /scripts/simple-next-tick.js:8 2 | throw new Error('trace'); 3 | ^ 4 | 5 | Error: trace 6 | at callback (/scripts/simple-next-tick.js:r:c) 7 | at process.processTicksAndRejections (node:internal/process/task_queues:r:c) 8 | at process.nextTick (node:internal/process/task_queues:r:c) 9 | at Object. (/scripts/simple-next-tick.js:r:c) 10 | at Module._compile (node:internal/modules/cjs/loader:r:c) 11 | at Module._extensions..js (node:internal/modules/cjs/loader:r:c) 12 | at Module.load (node:internal/modules/cjs/loader:r:c) 13 | at Module._load (node:internal/modules/cjs/loader:r:c) 14 | at TracingChannel.traceSync (node:diagnostics_channel:r:c) 15 | at wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 16 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 17 | at node:internal/main/run_main_module:r:c { 18 | [Symbol(originalCallSite)]: [ CallSite {}, CallSite {} ], 19 | [Symbol(mutatedCallSite)]: [ 20 | CallSite {}, CallSite {}, 21 | CallSite {}, CallSite {}, 22 | CallSite {}, CallSite {}, 23 | CallSite {}, CallSite {}, 24 | CallSite {}, CallSite {}, 25 | CallSite {}, CallSite {} 26 | ] 27 | } 28 | 29 | Node.js vx.y.z 30 | -------------------------------------------------------------------------------- /test/expected/simple-timer.txt: -------------------------------------------------------------------------------- 1 | /scripts/simple-timer.js:8 2 | throw new Error('trace'); 3 | ^ 4 | 5 | Error: trace 6 | at Timeout.callback [as _onTimeout] (/scripts/simple-timer.js:r:c) 7 | at listOnTimeout (node:internal/timers:r:c) 8 | at process.processTimers (node:internal/timers:r:c) 9 | at initAsyncResource (node:internal/timers:r:c) 10 | at new Timeout (node:internal/timers:r:c) 11 | at setTimeout (node:timers:r:c) 12 | at Object. (/scripts/simple-timer.js:r:c) 13 | at Module._compile (node:internal/modules/cjs/loader:r:c) 14 | at Module._extensions..js (node:internal/modules/cjs/loader:r:c) 15 | at Module.load (node:internal/modules/cjs/loader:r:c) 16 | at Module._load (node:internal/modules/cjs/loader:r:c) 17 | at TracingChannel.traceSync (node:diagnostics_channel:r:c) 18 | at wrapModuleLoad (node:internal/modules/cjs/loader:r:c) 19 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:r:c) 20 | at node:internal/main/run_main_module:r:c { 21 | [Symbol(originalCallSite)]: [ CallSite {}, CallSite {}, CallSite {} ], 22 | [Symbol(mutatedCallSite)]: [ 23 | CallSite {}, CallSite {}, 24 | CallSite {}, CallSite {}, 25 | CallSite {}, CallSite {}, 26 | CallSite {}, CallSite {}, 27 | CallSite {}, CallSite {}, 28 | CallSite {}, CallSite {}, 29 | CallSite {}, CallSite {}, 30 | CallSite {} 31 | ] 32 | } 33 | 34 | Node.js vx.y.z 35 | -------------------------------------------------------------------------------- /test/scripts/promise-child-stack.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const p0 = Promise.resolve(1); // will have `scope` in stack trace 4 | const p1 = p0.then(function scopeThrow() { 5 | console.error(new Error('hello').stack); 6 | }); 7 | -------------------------------------------------------------------------------- /test/scripts/regression-delayed-formater.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Error.prepareStackTrace = function (error, frames) { 4 | const lines = [ error.toString() ]; 5 | for (let i = 0; i < frames.length; i++) { 6 | lines.push(' at-hack ' + frames[i].toString()); 7 | } 8 | return lines.join('\n'); 9 | }; 10 | 11 | // Do something async-like 12 | // Before fix this would result in a 13 | // Cannot read property 'slice' of undefined 14 | process.stdout.write(''); 15 | 16 | // Try getting a stack trace 17 | throw new Error('trace'); 18 | -------------------------------------------------------------------------------- /test/scripts/regression-http-close.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | 5 | // 6 | // test an issue with http where events wheren't removed 7 | // 8 | 9 | http.createServer(function (req, res) { 10 | res.end('ho away :('); 11 | }).listen(0, 'localhost', function listen() { 12 | const server = this; 13 | const addr = this.address(); 14 | 15 | http.get('http://localhost:' + addr.port, function get(res) { 16 | res.on('data', function () {}); 17 | res.once('end', function end() { 18 | 19 | server.close(function close() { 20 | throw new Error('trace'); 21 | }); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/scripts/regression-http-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | 5 | // 6 | // Test an issue where the async context from HTTP parser isn't correctly 7 | // captured, because MakeCallback is called synchronously. 8 | // 9 | 10 | const server = http.createServer(function (req, res) { 11 | res.end('hallo world'); 12 | }); 13 | 14 | server.listen(0, 'localhost', function () { 15 | const addr = server.address(); 16 | http.get(`http://${addr.address}:${addr.port}`, function (res) { 17 | 18 | // there is no public endpoint where we lose context. However 19 | // the internal _read that is called from TCP.onread -> socket.push 20 | // does lose context. 21 | // An alternative is also to overwrite res.socket.read, but that 22 | // is perhaps less public. 23 | const _read = res.socket._read; 24 | res.socket._read = function () { 25 | throw new Error('trace'); 26 | return _read.apply(this, arguments); 27 | }; 28 | 29 | res.resume(); 30 | res.once('end', server.close.bind(server)); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/scripts/regression-net-connect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const net = require('net'); 4 | 5 | // 6 | // test an issue where there are multiply internal async events 7 | // 8 | 9 | const socket = net.connect(24075); // connection refused 10 | socket.once('error', function (error) { 11 | throw error; 12 | }); 13 | -------------------------------------------------------------------------------- /test/scripts/regression-net-socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const net = require('net'); 4 | 5 | const server = net.createServer(function (socket) { 6 | throw new Error('trace'); 7 | }); 8 | 9 | server.listen(0, 'localhost', function () { 10 | const addr = server.address(); 11 | const socket = net.connect(addr.port, addr.address, function () { 12 | // just hang, it will throw 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/scripts/regression-timer-clear.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const a = setTimeout(function () { 4 | throw new Error('setTimeout callback should not be called'); 5 | }, 1); 6 | 7 | clearTimeout(a); 8 | 9 | const b = setInterval(function () { 10 | throw new Error('setInterval callback should not be called'); 11 | }, 1); 12 | 13 | clearInterval(b); 14 | 15 | const c = setImmediate(function () { 16 | throw new Error('setImmediate callback should not be called'); 17 | }); 18 | 19 | clearImmediate(c); 20 | -------------------------------------------------------------------------------- /test/scripts/simple-fs-read.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | // 6 | // test module method (fs.readFile) 7 | // 8 | 9 | fs.readFile(__filename, function callback() { 10 | throw new Error('trace'); 11 | }); 12 | -------------------------------------------------------------------------------- /test/scripts/simple-next-tick.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 4 | // test process method (nextTick) 5 | // 6 | 7 | process.nextTick(function callback() { 8 | throw new Error('trace'); 9 | }); 10 | -------------------------------------------------------------------------------- /test/scripts/simple-timer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 4 | // test global method (setTimeout) 5 | // 6 | 7 | setTimeout(function callback() { 8 | throw new Error('trace'); 9 | }, 0); 10 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const interpreted = require('interpreted'); 4 | const execspawn = require('execspawn'); 5 | const endpoint = require('endpoint'); 6 | const path = require('path'); 7 | const process = require('process'); 8 | 9 | const TRACE_PATH = path.resolve(__dirname, '../trace.js'); 10 | const SCRIPTS_PATH = path.resolve(__dirname, 'scripts'); 11 | const EXPECTED_PATH = path.resolve(__dirname, 'expected'); 12 | 13 | interpreted({ 14 | source: SCRIPTS_PATH, 15 | expected: EXPECTED_PATH, 16 | readSource: false, 17 | 18 | update: false, 19 | 20 | test: function (name, callback) { 21 | const filepath = path.join(SCRIPTS_PATH, name + '.js'); 22 | const p = execspawn(`${process.execPath} -r ${TRACE_PATH} --stack-trace-limit=1000 ${JSON.stringify(filepath)} 2>&1`); 23 | p.stdout.pipe(endpoint(function (err, output) { 24 | if (err) return callback(err); 25 | callback(null, stripNodeVersion(stripPosInfo(stripPath(output.toString('ascii'))))); 26 | })); 27 | }, 28 | 29 | types: { 30 | 'txt': { 31 | test: function (t, actual, expected) { 32 | t.strictEqual(actual, expected); 33 | }, 34 | update: function (actual) { 35 | return actual; 36 | } 37 | } 38 | } 39 | }); 40 | 41 | function stripPath(output) { 42 | return output.split(__dirname).join(''); 43 | } 44 | 45 | function stripPosInfo(text) { 46 | return text.replace(/:[0-9]+:[0-9]+/g, ':r:c'); 47 | } 48 | 49 | function stripNodeVersion(text) { 50 | return text.replace(`Node.js ${process.version}`, 'Node.js vx.y.z'); 51 | } 52 | -------------------------------------------------------------------------------- /trace.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chain = require('stack-chain'); 4 | const asyncHook = require('async_hooks'); 5 | 6 | // Contains init asyncId of the active scope(s) 7 | // Because we can't know when the root scope ends, a permanent Set is keept 8 | // for the root scope. 9 | const executionScopeInits = new Set(); 10 | let executionScopeDepth = 0; 11 | // Contains the call site objects of all active scopes 12 | const traces = new Map(); 13 | 14 | // 15 | // Mainiputlate stack trace 16 | // 17 | // add lastTrace to the callSite array 18 | chain.filter.attach(function (error, frames) { 19 | return frames.filter(function (callSite) { 20 | const name = callSite && callSite.getFileName(); 21 | return (!name || (name !== 'async_hooks.js' && name !== 'internal/async_hooks.js' && name !== 'node:internal/async_hooks')); 22 | }); 23 | }); 24 | 25 | chain.extend.attach(function (error, frames) { 26 | const lastTrace = traces.get(asyncHook.executionAsyncId()); 27 | frames.push.apply(frames, lastTrace); 28 | return frames; 29 | }); 30 | 31 | // 32 | // Track handle objects 33 | // 34 | const hooks = asyncHook.createHook({ 35 | init: asyncInit, 36 | before: asyncBefore, 37 | after: asyncAfter, 38 | destroy: asyncDestroy 39 | }); 40 | hooks.enable(); 41 | 42 | function getCallSites(skip) { 43 | const limit = Error.stackTraceLimit; 44 | 45 | Error.stackTraceLimit = limit + skip; 46 | const stack = chain.callSite({ 47 | extend: false, 48 | filter: true, 49 | slice: skip 50 | }); 51 | Error.stackTraceLimit = limit; 52 | 53 | return stack; 54 | } 55 | 56 | function equalCallSite(a, b) { 57 | const aFile = a.getFileName(); 58 | const aLine = a.getLineNumber(); 59 | const aColumn = a.getColumnNumber(); 60 | 61 | if (aFile === null || aLine === null || aColumn === null) { 62 | return false; 63 | } 64 | 65 | return (aFile === b.getFileName() && 66 | aLine === b.getLineNumber() && 67 | aColumn === b.getColumnNumber()); 68 | } 69 | 70 | function asyncInit(asyncId, type, triggerAsyncId, resource) { 71 | const trace = getCallSites(2); 72 | 73 | // In cases where the trigger is in the same synchronous execution scope 74 | // as this resource, the stack trace of the trigger will overlap with 75 | // `trace`, this mostly happens with promises. 76 | // Example: 77 | // p0 = Promise.resolve(1); // will have `root` in stack trace 78 | // p1 = p0.then(() => throw new Error('hello')); // will also have `root` 79 | // The async stack trace should be: root, p0, p1 80 | // 81 | // To avoid (n^2) string matching, it is assumed that `Error.stackTraceLimit` 82 | // hasn't changed. By this assumtion we know the current trace will go beyond 83 | // the trigger trace, thus the check can be limited to trace[-1]. 84 | if (executionScopeInits.has(triggerAsyncId)) { 85 | const parentTrace = traces.get(triggerAsyncId); 86 | 87 | let i = parentTrace.length; 88 | while(i-- && trace.length > 1) { 89 | if (equalCallSite(parentTrace[i], trace[trace.length - 1])) { 90 | trace.pop(); 91 | } 92 | } 93 | } 94 | 95 | // Add all the callSites from previuse ticks 96 | if (triggerAsyncId !== 0) { 97 | trace.push.apply(trace, traces.get(triggerAsyncId)); 98 | } 99 | 100 | // Cut the trace so it don't contain callSites there won't be shown anyway 101 | // because of Error.stackTraceLimit 102 | trace.splice(Error.stackTraceLimit); 103 | 104 | // `trace` now contains callSites from this ticks and all the ticks leading 105 | // up to this event in time 106 | traces.set(asyncId, trace); 107 | 108 | // add asyncId to the list of all inits in this execution scope 109 | executionScopeInits.add(asyncId); 110 | } 111 | 112 | function asyncBefore(asyncId) { 113 | if (executionScopeDepth === 0) { 114 | executionScopeInits.clear(); 115 | } 116 | executionScopeDepth += 1; 117 | } 118 | 119 | function asyncAfter(asyncId) { 120 | executionScopeDepth -= 1; 121 | } 122 | 123 | function asyncDestroy(asyncId) { 124 | traces.delete(asyncId); 125 | } 126 | --------------------------------------------------------------------------------