The goal of this is to demonstrate an improvement to the display of stack traces in the Julia REPL, associated with this PR: https://github.com/JuliaLang/julia/pull/40537
Julia's stacktraces are often too long to be practical for most users. This can form a barrier to entry and become an annoyance.
So how can we maximize the utility of the stack traces for the common case?
My philosophy is this: 95% of the time, a given exception is due to a mistake in your own code. The probability of the exception being due to a mistake in a well-used dependency is much lower, and the probability of the exception being due to a mistake in Base is even smaller. The probability of an exception being due to a mistake in the compiler is infinitesimal (but nonzero).
Thus, the highest value stack frames, the majority of the time, are those relating to code you have written or are actively developing. The stack frames between your call into a foreign method and the frame where the validation occurs is usually irrelevant to you. And for composed functions, the internal frames between the call into a foreign function and where it emerges back into your code also doesn't usually matter. Those frames are implementation details that don't make a difference to whether your code works.
Which raised the question: what counts as "your" code?
I think clearly these sources can be assumed to not be your code, and more likely stable:
- Julia Base
- Julia Stdlibs
- Dependencies acquired using Pkg
Whereas these sources are "yours":
- Activated local package
- Code defined in the REPL
- Dependencies acquired using Pkg
- Modules or files matching
ENV["JULIA_DEBUG"](file basename and/or module names; comma-separated,
All frames originating from "your" code are shown by default, as well as the immediate next frame to show what function your code called. This information should be sufficient in most cases to understand that you made a mistake, and where that mistake was located. Note that this only works by default in interactive usage. Running unit tests (e.g. via
pkg>test) or execution on distributed processes will show the full trace. This behavior can be customized via the options below.
But in the rarer case where the issue was not in your code, the full trace can be retrieved from the
err global variable.
ENV["JULIA_STACKTRACE_ABBREVIATED"] = trueenables abbreviated stack traces for all traces, not just those originating from an interactive session
ENV["JULIA_STACKTRACE_MINIMAL"] = trueomits type information for a one-line-per-frame minimal variant (see below)
ENV["JULIA_STACKTRACE_PUBLIC"] = truewill re-insert all functions from a module's public API (part of
names(module); Julia < 1.11, this will just be exported names)
Unfortunately, startup.jl is executed before VSCodeServer loads, which means the appropriate methods won't be overwritten. Some workarounds are discussed here: https://github.com/BioTurboNick/AbbreviatedStackTraces.jl/issues/38
Here's an example of a stack trace by chaining BenchmarkTools and Plots:
using AbbreviatedStackTraces # over-writes error-related `Base` methods
using BenchmarkTools, Plots
@btime plot([1,2,3], seriestype=:blah)
It aims to find the stack frames of code you don't control and excludes them by default, except for the first frame into that package. In it's place, it lists the modules called in the intervening frames. The theory is that errors in your code are much more likely than errors inside Base, the Stdlibs, or published packages, so their internals are usually superfluous.
(Note: italics only works on Julia 1.10+)
err variable stores the last error and can show the full, original stack trace easily.
You can also add back functions with public (Julia 1.11) or exported (Julia 1.9, 1.10) names by setting
ENV["JULIA_STACKTRACE_PUBLIC"] = true.
There is an optional minimal display available, accessed by setting
ENV["JULIA_STACKTRACE_MINIMAL"] = true.
Here's an example a beginner might readily run into:
With this package: