Skip to content

Commit 201e50e

Browse files
committed
lib: add util.getCallSite() API
1 parent 9b3d22d commit 201e50e

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed

doc/api/util.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,56 @@ util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 });
364364
// when printed to a terminal.
365365
```
366366

367+
## `util.getCallSite()`
368+
369+
> Stability: 1.1 - Active development
370+
371+
<!-- YAML
372+
added: REPLACEME
373+
-->
374+
375+
* Returns: {CallSite\[]} An array of CallSite objects, read more at <https://v8.dev/docs/stack-trace-api#customizing-stack-traces>.
376+
377+
Returns an array of V8 CallSite objects containing the stacktrace of
378+
the caller function.
379+
380+
```js
381+
const util = require('node:util');
382+
383+
function exampleFunction() {
384+
const callSites = util.getCallSite();
385+
386+
console.log('Call Sites:');
387+
callSites.forEach((callSite, index) => {
388+
console.log(`CallSite ${index + 1}:`);
389+
console.log(`Function Name: ${callSite.getFunctionName()}`);
390+
console.log(`File Name: ${callSite.getFileName()}`);
391+
console.log(`Line Number: ${callSite.getLineNumber()}`);
392+
console.log(`Column Number: ${callSite.getColumnNumber()}`);
393+
});
394+
// CallSite 1:
395+
// Function Name: exampleFunction
396+
// File Name: /home/example.js
397+
// Line Number: 5
398+
// Column Number: 26
399+
400+
// CallSite 2:
401+
// Function Name: anotherFunction
402+
// File Name: /home/example.js
403+
// Line Number: 22
404+
// Column Number: 3
405+
406+
// ...
407+
}
408+
409+
// A function to simulate another stack layer
410+
function anotherFunction() {
411+
exampleFunction();
412+
}
413+
414+
anotherFunction();
415+
```
416+
367417
## `util.getSystemErrorName(err)`
368418

369419
<!-- YAML

lib/util.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,26 @@ function parseEnv(content) {
275275
return binding.parseEnv(content);
276276
}
277277

278+
/**
279+
* Returns the callSite
280+
* @returns {CallSite[]}
281+
*/
282+
function getCallSite() { /* eslint-disable no-restricted-syntax */
283+
const originalStackFormatter = Error.prepareStackTrace;
284+
Error.prepareStackTrace = (_err, stack) => {
285+
if (stack && stack.length > 1) {
286+
// Remove node:util
287+
return stack.slice(1);
288+
}
289+
return stack;
290+
};
291+
const err = new Error();
292+
// With the V8 Error API, the stack is not formatted until it is accessed
293+
err.stack; // eslint-disable-line no-unused-expressions
294+
Error.prepareStackTrace = originalStackFormatter;
295+
return err.stack;
296+
};
297+
278298
// Keep the `exports =` so that various functions can still be monkeypatched
279299
module.exports = {
280300
_errnoException,
@@ -289,6 +309,7 @@ module.exports = {
289309
format,
290310
styleText,
291311
formatWithOptions,
312+
getCallSite,
292313
getSystemErrorMap,
293314
getSystemErrorName,
294315
inherits,

test/fixtures/get-call-site.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const util = require('node:util');
2+
const assert = require('node:assert');
3+
assert.ok(util.getCallSite().length > 1);
4+
process.stdout.write(util.getCallSite()[0].getFileName());
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
const fixtures = require('../common/fixtures');
6+
const file = fixtures.path('get-call-site.js');
7+
8+
const { getCallSite } = require('node:util');
9+
const { spawnSync } = require('node:child_process');
10+
const assert = require('node:assert');
11+
12+
{
13+
const callsite = getCallSite();
14+
assert.ok(callsite.length > 1);
15+
assert.match(
16+
callsite[0].getFileName(),
17+
/test-util-getCallSite/,
18+
'node:util should be ignored',
19+
);
20+
}
21+
22+
23+
{
24+
const { status, stderr, stdout } = spawnSync(
25+
process.execPath,
26+
[
27+
'-e',
28+
`const util = require('util');
29+
const assert = require('assert');
30+
assert.ok(util.getCallSite().length > 1);
31+
process.stdout.write(util.getCallSite()[0].getFileName());
32+
`,
33+
],
34+
);
35+
assert.strictEqual(status, 0, stderr.toString());
36+
assert.strictEqual(stdout.toString(), '[eval]');
37+
}
38+
39+
{
40+
const { status, stderr, stdout } = spawnSync(
41+
process.execPath,
42+
[file],
43+
);
44+
assert.strictEqual(status, 0, stderr.toString());
45+
assert.strictEqual(stdout.toString(), file);
46+
}
47+
48+
{
49+
const originalStackTraceLimit = Error.stackTraceLimit;
50+
Error.stackTraceLimit = 0;
51+
const callsite = getCallSite();
52+
assert.strictEqual(callsite.length, 0);
53+
Error.stackTraceLimit = originalStackTraceLimit;
54+
}

0 commit comments

Comments
 (0)