Intercepting error messages - HOW?

In my Term.jl project I aim to provide functionality to print nicely formatted, pretty, error messages. A preview:

image

This is inspired by Rich in python that prints error stacks like:

In python you can define an exception hook such that all errors go through it and you can print them as you’d like. I would like to the same in Julia, but haven’t found a way yet.

I haven’t found good docs on how errors are handled internally, but going through the code I’ve found functions like show_backtrace and showerror in Base.errorshow.jl and display_error in Base.client.jl as well as other similarly named functions.

I can re-define this to print out formatted error messages as above, but there’s several things I’m not clear with:

  1. what calls show_backtrace and errorshow? Does it depend on where the code is run (e.g. in VSCode or in the Julia app?). I can re-define show-backtrace and errorshow, but ideally I’d like to intercept error messages before.
  2. what prints out LoadErrors? It seems to bypass show_backtrace and errorshow as I can’t find a way to style that.
  3. Accessing locals: in python’s exceptions hook you have access to locals at all levels in the callstack (see second image above). I know there’s Base.@locals but that only gives the locals where the macro is used. Is there a way to get locals at each frame in a stacktrace or backtrace?
  4. Julia’s errors come with hints provided by Base.Experimental.show_error_hints, which is really nice. However when I try to use show_error_hints in my re-defined errorshow functions it doesn’t give any hint (it returns nothing). Is there a way to access error hints?

Thank you in advance for any help!

18 Likes

Yes, it depends on the IDE. In the REPL it’s handled by the following line: julia/REPL.jl at 2338f5d3040ced10d69f4d15cca5fdca03364d9a · JuliaLang/julia · GitHub. Note however that refining any of these functions is type piracy and strongly discouraged, since it can result in inconsistent behavior and excessive invalidations. Base.Experimental.register_error_hint is how additional hints are intended to be added.

They should go through the REPL as well. Do you have an example?

Yes, @locals always needs to be inserted into the source code. Otherwise you can’t access all locals inside a function unless you use an interpreter like GitHub - JuliaDebug/JuliaInterpreter.jl: Interpreter for Julia code

show_error_hints always returns nothing, the hints are printed to the supplied IO object. You can of course use sprint to get the output as a string or take a look at its source code if you want each separate hint.

1 Like

Thank you, that’s very helpful!

Note however that refining any of these functions is type piracy and strongly discouraged

Arrrr!

I would very much prefer not having to do that! But is there an alternative to it like python’s exception hook?
If lines like Base.invokelatest(Base.display_error, errio, val) that you’ve send above (and similar ones e.g. in VSCode’s Julia Extension) are what prints out the error, then I’m guessing I have to re-define Base.display_error?

At any rate, it wouldn’t be done by default in my package, it would be an option that users have to explicitly activate and I will warn them that it’s a hacky solution.

There is Base.Experimental.register_error_hint, but you seem to already have discovered that. Your projects looks very exciting, so if you have a concrete proposal for what kind of hooks you would need, I think the core devs would be open to discuss potential changes to base.

display_error should end up calling showerror, so redefining that should have similar effects.

Thanks!

I will try starting a discussion with the Julia devs, and perhaps use the hacky fix in the meanwhile.

display_error should end up calling showerror , so redefining that should have similar effects.

Correct, for now I have re-defined display_error and show_backtrace and seems to get me most of the way there.

I’ve also seen redirect_stderr, but I’m not sure if/how I could use that to achieve my goal here.

Either way, this was very helpful thank you for your time!

@simeonschaub I just wanted to thank you again.
The error formatting is coming along fairly well!

8 Likes

Perhaps a solution would be to push your own display to the top of the display stack. Literate.jl uses pushdisplay and popdisplay to catch display calls. I believe something similar is done for e.g. the plot pane in the VS code extensions.

Any chance that these pretty stack traces will become the default REPL experience? :heart_eyes:

1 Like

Thanks @fredrikekre I’ll look into that.

@juliohm that seems unlikely (for now at least). But using term is just a couple lines of code to set them up!

@friederikemeier

AbstractDisplay is something that I didn’t know about, and it might be very useful in the future, but as far as I can tell I can’t make much use of it here. Thanks anyway!

This is an exciting project to see being worked on!

IMO, one of the main disadvantages of Julia in terms of user experience, apart form TTFX, is that error messages are often verbose and intimidating.

It is not uncommon to receive three-page nested error messages when (ab)using DataFrames.jl, for example, and I think this is a significant problem for Julia’s general adoption. Especially for those coming from languages like Python, whose traceback messages are (mostly) very succinct and lucid, such long error messages can be scary and hard to glean information from.

I don’t think it’s appropriate for Term.jl’s style of displaying error messages to become Julia’s default, but I would love to see Julia’s error messages become something in between what they are now and the proof-of-concept shown in this post.


I hope the Julia devs agree that providing an interface for intercepting and display error messages is a good investment. I’m thinking this might also be a boon for HTML-based front-ends like Pluto.jl.

One thing that might help with this in the near future is to replace the parser with JuliaSyntax, which is now possible thanks to

1 Like