Logo

dev-resources.site

for different kinds of informations.

Detect what calls your Node.js code

Published at
12/11/2023
Categories
node
stack
javascript
typescript
Author
afl_ext
Categories
4 categories in total
node
open
stack
open
javascript
open
typescript
open
Author
7 person written this
afl_ext
open
Detect what calls your Node.js code

While most of the time it's actively discouraged to detect and react to how your code is called, there are times when you would really want to know how the execution came around calling it.

Examples when this would be very useful are:

  • Logging libraries that autodetect and describe the caller
  • Invisible caching for example if you were to reimplement useEffect on the backend

I'm sure you will find more creative ways to utilize this madness. Let's jump in and see how to do it.

How is this possible

The trick is to utilize V8 stack capturing functionality. No errors are thrown here, just a simple V8 function is being called to get the stack, and then some parsing happens to make the result useful.

Prepare

Let's first set up a function with following signature:

export function getExecutionPath(
  filterDir = process.cwd(),
  limit = 10
)
Enter fullscreen mode Exit fullscreen mode
  • filterDir - will be used to filter out calls from outside your code, for example, from node_modules or linked libraries
  • limit - how far into the execution history we will reach

Capture the stack

Now get the most important part, a fake Error stack trace:

const inobj = { stack: "" } satisfies { stack: string };

const oldLimit = Error.stackTraceLimit;
Error.stackTraceLimit = limit;
Error.captureStackTrace(inobj);
Error.stackTraceLimit = oldLimit;
Enter fullscreen mode Exit fullscreen mode

This code sets the stack limit from the function parameter, captures the stack trace at this very point and restores the original limit.

The result is in pretty rough format:

Error
    at getExecutionPath (C:\Code\server-side-state\src\state\getExecutionPath.ts:23:9)
    at new SynchronizedStateful (C:\Code\server-side-state\src\state\state.ts:27:33)
    at state (C:\Code\server-side-state\src\state\state.ts:90:24)
    at Index (C:\Code\server-side-state\src\test\Index.tsx:7:20)
    at C:\Code\server-side-state\src\server.ts:18:25
    at Layer.handle [as handle_request] (C:\Code\server-side-state\node_modules\express\lib\router\layer.js:95:5)
    at next (C:\Code\server-side-state\node_modules\express\lib\router\route.js:144:13)
    at Route.dispatch (C:\Code\server-side-state\node_modules\express\lib\router\route.js:114:3)
    at Layer.handle [as handle_request] (C:\Code\server-side-state\node_modules\express\lib\router\layer.js:95:5)
    at C:\Code\server-side-state\node_modules\express\lib\router\index.js:284:15
Enter fullscreen mode Exit fullscreen mode

We need to process that to make it useful.

Parse the stack

First run some regex on the lines to get only lines starting with at and extract the identifier and its location. At the end remove the call of our detection method from the results and reverse the order.

const detectorRegex = /at (.+?) \((.+?)\)/;

const stack = inobj.stack
  .split("\n")
  .map((x) => x.trim())
  .filter((x) => x.startsWith("at"))
  .map((x) => x.match(detectorRegex))
  .filter((x) => !!x) as RegExpMatchArray[];
stack.shift();
stack.reverse();
Enter fullscreen mode Exit fullscreen mode

Then, transform the array of matches by parsing those strings inside. Get the identifier, and parse the second parameter to get the path, line and column in mre useful form.

const detections: StackEntry[] = stack.map((x) => {
  const identifier = x[1];
  const pathlinecol = x[2].split(":");
  const column =
    pathlinecol.length > 2 ? parseInt(pathlinecol.pop() ?? "0") : 0;
  const line =
    pathlinecol.length > 1 ? parseInt(pathlinecol.pop() ?? "0") : 0;
  const p = pathlinecol.join(":");
  return { identifier, path: p, line, column };
});
Enter fullscreen mode Exit fullscreen mode

After those steps, the detections array looks like this:

[
  {
    identifier: 'Layer.handle [as handle_request]',
    path: 'C:\\Code\\server-side-state\\node_modules\\express\\lib\\router\\layer.js',
    line: 95,
    column: 5
  },
  {
    identifier: 'Route.dispatch',
    path: 'C:\\Code\\server-side-state\\node_modules\\express\\lib\\router\\route.js',
    line: 114,
    column: 3
  },
  {
    identifier: 'next',
    path: 'C:\\Code\\server-side-state\\node_modules\\express\\lib\\router\\route.js',
    line: 144,
    column: 13
  },
  {
    identifier: 'Layer.handle [as handle_request]',
    path: 'C:\\Code\\server-side-state\\node_modules\\express\\lib\\router\\layer.js',
    line: 95,
    column: 5
  },
  {
    identifier: 'Index',
    path: 'C:\\Code\\server-side-state\\src\\test\\Index.tsx',
    line: 9,
    column: 7
  },
  {
    identifier: 'once',
    path: 'C:\\Code\\server-side-state\\src\\state\\once.ts',
    line: 40,
    column: 26
  },
  {
    identifier: 'OnceRunner.produce',
    path: 'C:\\Code\\server-side-state\\src\\state\\once.ts',
    line: 23,
    column: 21
  }
]
Enter fullscreen mode Exit fullscreen mode

Filter the stack

We see we got some calls that are outside our code, let's filter them out and make the paths relative to our base directory:

const local = detections.filter(
  (x) =>
    x.path.startsWith(path.resolve(filterDir)) &&
    !x.path.includes("node_modules")
);
Enter fullscreen mode Exit fullscreen mode

For breadcrumbs, we still need to filter out anonymous functions and some other stuff, but better to put it in a separate variable named, because the things inside local can be very useful too.

  const named = local
    .filter((x) => !x.identifier.includes("<anonymous>"))
    .filter((x) => !x.identifier.startsWith("Object."))
    .filter((x) => !x.identifier.startsWith("Module."));
Enter fullscreen mode Exit fullscreen mode

Final results

And now finally make the breadcrumbs string:

const breadcrumbs = named.map((x) => x.identifier).join("/");
Enter fullscreen mode Exit fullscreen mode

And that's it! In this particular example I used, the breadcrumbs will look like this:
Index/once/OnceRunner.produce

and the local and named:

local: [
  {
    identifier: 'Index',
    path: 'C:\\Code\\server-side-state\\src\\test\\Index.tsx',
    line: 9,
    column: 7
  },
  {
    identifier: 'once',
    path: 'C:\\Code\\server-side-state\\src\\state\\once.ts',
    line: 40,
    column: 26
  },
  {
    identifier: 'OnceRunner.produce',
    path: 'C:\\Code\\server-side-state\\src\\state\\once.ts',
    line: 23,
    column: 33
  }
],
named: [
  {
    identifier: 'Index',
    path: 'C:\\Code\\server-side-state\\src\\test\\Index.tsx',
    line: 9,
    column: 7
  },
  {
    identifier: 'once',
    path: 'C:\\Code\\server-side-state\\src\\state\\once.ts',
    line: 40,
    column: 26
  },
  {
    identifier: 'OnceRunner.produce',
    path: 'C:\\Code\\server-side-state\\src\\state\\once.ts',
    line: 23,
    column: 33
  }
]
Enter fullscreen mode Exit fullscreen mode

Cheers and happy coding as always!

stack Article's
30 articles in total
Favicon
Stack Developer Web
Favicon
Whats your TECH stack ?? How did you get into that??
Favicon
Building a Stack Implementation in Java: Mastering Data Structure Fundamentals.
Favicon
Pattern 7: Stack
Favicon
Stacks: 50 Leetcode Questions
Favicon
Why Is Stack Memory Faster Than Heap Memory? Hereโ€™s What You Need to Know!
Favicon
Stack: Concepts and Applications โ€” Java
Favicon
Top 7 Reasons to Hire a MERN Stack Development Company for Scalable Web Solutions
Favicon
Maximum swap
Favicon
Comprehensive ๐—š๐˜‚๐—ถ๐—ฑ๐—ฒ ๐˜๐—ผ ๐—ฆ๐˜๐—ฎ๐—ฐ๐—ธ ๐——๐—ฎ๐˜๐—ฎ ๐—ฆ๐˜๐—ฟ๐˜‚๐—ฐ๐˜๐˜‚๐—ฟ๐—ฒ: ๐—œ๐—บ๐—ฝ๐—น๐—ฒ๐—บ๐—ฒ๐—ป๐˜๐—ฎ๐˜๐—ถ๐—ผ๐—ป, ๐—ข๐—ฝ๐—ฒ๐—ฟ๐—ฎ๐˜๐—ถ๐—ผ๐—ป๐˜€, ๐—ฎ๐—ป๐—ฑ ๐—ฃ๐—ฟ๐—ผ๐—ฏ๐—น๐—ฒ๐—บ-๐—ฆ๐—ผ๐—น๐˜ƒ๐—ถ๐—ป๐—ด
Favicon
Understanding Stack as an Abstract Data Type
Favicon
Heap vs Stack: como o Java gerencia o que deve ser lembrado ou esquecido
Favicon
Factors to consider in choosing a tech stack for a project
Favicon
stack in PyTorch
Favicon
Becoming a Full Stack Developer: A Step-by-Step Guide
Favicon
Full Stack Development: A Comprehensive Guide
Favicon
The Complete Guide to Full Stack Development: Essential Skills and Strategies
Favicon
Stack & Heap
Favicon
Stacks in Action: A Dive into the Concept and Implementation
Favicon
An ode to Stacks and Pointers in Go!
Favicon
Queue and Stack Essentials: A Python Programmer's Guide
Favicon
Navigating the Seas of Web Development
Favicon
Stack are not Queue
Favicon
Solving DSA Problems. HackerRank: Balanced Brackets
Favicon
Understanding Stack Memory and Heap Space inย Java.
Favicon
Join Our Stack Programming Community!
Favicon
Understanding the Stack Data Structure: A Java Implementation
Favicon
A Brief Look At Spotify's Tech Stack
Favicon
Implementation of Stack Using Array
Favicon
Detect what calls your Node.js code

Featured ones: