The most convenient way to debug applications
In this article, I will show you an interesting way to debug applications. This is somewhat different from the usual methods that you use in your daily work.
How do we debug today
- π¨οΈ Print to stdout
βΆοΈ Step-by-step debugger in IDEπ Maybe even time-travel debuggerπ Viewing logs
Disadvantages of current debug techniqes
π¨οΈ Print to stdout
π Slower debug performanceβοΈ Context switch between your code and stdoutπ Often need to rerun your program again and again
βΆοΈ Step-by-step debugger in IDE
- π Slower debug performance
π Often need to rerun your program again and again
π Time-travel debugger
- ποΈ Often very hard to implement
π² Often not free
π Viewing logs
- π Slower debug performance
βοΈ Context switch between your code and logs
Another way to debug - Smart Logs
As you see on the screenshot, Smart Logs attached to functions and trace call of every function: parameters, response, possible error and so on. Pros:
- π Faster debug performance
π€ No context switch between your code and logsπ No need to run the program again and again
Implementation example
Further we will see an implementation example. It's based on JavaScript/TypeScript and Node.js runtime, but it can be implemented on lot programming languages and runtimes. VSCode used as IDE, but other IDE should be suitable too for this purpose.
Requirements
π Ability to reflect function location in code (https://github.com/midrissi/func-loc)ποΈ Line note ability in IDE (https://marketplace.visualstudio.com/items?itemName=tkrkt.linenote)
Code
import { locate } from 'func-loc';
import { appendFile, mkdir } from 'fs/promises';
import endent from 'endent';
const ln = fn => {
return function (...params) {
const resp = fn.apply(this, params);
Promise.allSettled([locate(fn), resp]).then(async ([loc, respOrErr]) => {
await mkdir(`${process.cwd()}/.vscode/linenote`, { recursive: true });
await appendFile(
loc.value.path.replace(process.cwd(), `${process.cwd()}/.vscode/linenote`) + `#L${loc.value.line}.md`,
endent.default`
params: ${params},
${respOrErr.value ? `response: ${respOrErr.value}` : `error: ${respOrErr.reason}`}
\\n\\n
`
);
});
return resp;
};
};
ln
(alias to line note) is wrapper of target functionfn
ln
intercepts parameters and response offn
or possible error insidefn
ln
reflectsfn
location in code- using
fn
code location,ln
create/append Line Note file on proper line with paramaters and response offn
in content
By the way
- In code above
βοΈ parameters and response interpolated to string "as is". On real production code you deal with complex JSON structures, which should be properly serialized - You can add your own additional info: duration, timestamp and so on
- You can write TypeScript class decorator, which wrap all class methods for Smart Logs support
- On example used FILO via
appendFile
, but with https://github.com/hemanth/node-prepend-file you can change to FIFO - You may get
EBUSY
error via concurrent access to same Line Note file. This sutation can be fixed via repeat strategy, using something like https://github.com/sindresorhus/p-retry - You can write custom import to Smart Logs. For example: ELK logs -> Smart Logs
What about compiled to JavaScript languages like TypeScript?
func-loc supports source maps https://github.com/midrissi/func-loc#using-source-maps
What about frontend?
func-loc uses Node.js inspector underhood https://nodejs.org/dist/latest-v17.x/docs/api/inspector.html
But despite this, I think you can use Smart Logs with frontend using Electron, which support Node.js API and Browser API simuateniously https://www.electronjs.org/
What if my programming language / runtime can't reflect function location dynamically?
Maybe it can reflect function location statically via compiler/transpiler/macro and so on.