Julia 1.6 Stacktrace

Hi good folks!

I like the idea of trying to make the fairly cluttered stacktrace more readable by “syntax” highlighting. However, I don’t get the choices introduced in 1.6 at all. The filename and row number are probably what I look for most often but they are greyed out to the point of hardly being visible. On the other hand all the types are highlighted (not something I look at too much in a stacktrace).

Can this be modified by me? Am I using the stacktrace wrong?

8 Likes

Personally, I tend to agree that file name + line number (not the full path) could be highlighted differently. Something else that we could improve in my opinion is the error bit itself:

IMO, rather than the types of the closest candidate methods ::Type{String} should be highlighted in red. After all it is the type that caused the error. The closest candidate types could get a different color, say, orange.

Also, I would like to have “MethodError”, that is the kind of error, in red as well. But that is less important.

5 Likes

I would go even further to say that file name and line number could be the first thing at all. Something as

ERROR:....
At line XXX of test.jl.

I don’t know if there are technical difficulties involved though. But at least for me, the actual error message is only useful after I looked into the code and didn’t find a trivial error, which is what happens most times.

3 Likes

Just for reference, at some point all the possibilities were discussed in this extreme case of bikeshedding:

https://github.com/JuliaLang/julia/pull/36134
https://github.com/JuliaLang/julia/issues/36026

6 Likes

For me, at least, the exact line that the error happens is not that useful. Because in half of the cases, it is not a line of my code, but instead a line of Base or a third-party library, because I have broken some assumption and passed garbage to a lower level function and this will break an error outside of my sources. It would be very interesting if the stacktrace emphasized strongly the first line from a source that does not come from Base or a “normally” installed library (for the case you are using dev to test your code). The heuristic probably could be improved, but if it was right on about 80% of the cases it would already save considerable time.

12 Likes

Yes, that is what I meant. The line of “my” code. But I understand that is not easily defined in Julia. When using Revise, the tracked files maybe? (Is revise tracking everything down to base? Off topic, but may make my way of thinking meaningless).

2 Likes

This second link seem to have been closed because the PR was accepted, am I wrong? Why the stacktrace is not printed as it appears there?

Yes, second link is like planning and discussion based on the previous package ClearStacktraces.jl and the first one is the actual PR with another fair amount of bikeshedding. I probably should’ve pasted them in inverted order.

Getting people to agree on “optimal” stacktrace formatting is like getting them to agree on editor themes.
Perhaps a solution might be for Base to save the n most recent stacktraces to a file (rather like a REPL history). The user could then run a separate Julia instance with a stacktrace viewer / explorer of their choice.
I would see three benefits:

  1. When stacktraces come from unit tests, there are often several of them. It is tedious to scroll back through the REPL to view them all.
  2. While fixing errors, one has to type into the REPL, which scrolls stacktraces off the screen. This argues in favor of separating a stacktrace viewer from the REPL.
  3. User defined packages (or apps) can be installed. Each user can pick their favorite display format and customize it.
3 Likes

OhMyREPL allows you to customize syntax highlighting. Maybe it could do the same for stacktraces?

1 Like

The color is hardcoded at

https://github.com/JuliaLang/julia/blob/91c297b6c983aed2828600bcd9b8ba6494f747b6/base/errorshow.jl#L735

so it is not easy to change it right now

2 Likes

Would it be possible to store the stack trace in a default global environment variable, such that packages could be written to read that variable and print different kind of information?

1 Like

This might very well mean that I stick to Julia 1.5 for now. Looking up the file+line number and entering in my editor is my preferred way of working as is. Especially because of multiple dispatch (so just going to one of the functions with the correct name won’t do).

By the way, I like the suggestion earlier in this thread about trying to put extra focus on the last part of the stack trace before it “leaves your own code” since this is usually the part of interest for me.

For some sort of workaround, you can put this in your .julia/config/startup.jl file and tweak the color of the paths on the second line:

@eval Base begin

path_color = :red

function print_stackframe(io, i, frame::StackFrame, n::Int, digit_align_width, modulecolor)
    file, line = string(frame.file), frame.line
    stacktrace_expand_basepaths() && (file = something(find_source_file(file), file))
    stacktrace_contract_userdir() && (file = replaceuserpath(file))

    # Used by the REPL to make it possible to open
    # the location of a stackframe/method in the editor.
    if haskey(io, :last_shown_line_infos)
        push!(io[:last_shown_line_infos], (string(frame.file), frame.line))
    end

    inlined = getfield(frame, :inlined)
    modul = parentmodule(frame)

    # frame number
    print(io, " ", lpad("[" * string(i) * "]", digit_align_width + 2))
    print(io, " ")

    StackTraces.show_spec_linfo(IOContext(io, :backtrace=>true), frame)
    if n > 1
        printstyled(io, " (repeats $n times)"; color=:light_black)
    end
    println(io)

    # @
    printstyled(io, " " ^ (digit_align_width + 2) * "@ ", color = :light_black)

    # module
    if modul !== nothing
        printstyled(io, modul, color = modulecolor)
        print(io, " ")
    end

    # filepath
    pathparts = splitpath(file)
    folderparts = pathparts[1:end-1]
    if !isempty(folderparts)
        printstyled(io, joinpath(folderparts...) * (Sys.iswindows() ? "\\" : "/"), color = path_color)
    end

    # filename, separator, line
    # use escape codes for formatting, printstyled can't do underlined and color
    # codes are bright black (90) and underlined (4)
    function print_underlined(io::IO, s...)
        colored = get(io, :color, false)::Bool
        start_s = colored ? text_colors[path_color] * "\033[4m" : ""
        end_s   = colored ? "\033[0m"    : ""
        print(io, start_s, s..., end_s)
    end
    print_underlined(io, pathparts[end], ":", line)

    # inlined
    printstyled(io, inlined ? " [inlined]" : "", color = path_color)
end

end

12 Likes

Note that for the example eigvals(fill("test", 3,3)) , the part under “Closest candidates are:” (in the screenshot) hasn’t changed in Julia 1.6. The big change (starting from ClearStacktraces.jl) was what’s printed under “Stacktrace:”. Not making “MethodError: no method…” red by default was another small PR, partly to make it easier for custom errors to decide their own colours.

Improving the “Closest candidates” printing to better highlight relevant details would be great. And that for methods(eigvals) too, perhaps. Presumably both would, like the new stacktrace, follow how @warn prints module, file & line numbers on a separate line.

Restoring more colour to e.g. MethodError would also be nice, I think.

1 Like

Thank you for that!

Tackar så mycket! Hälsningar från andra kusten!

1 Like

One problem with finding a good general solution was that I’ve treated the available colors simply as less or more salient, but some themes just make them quite hard to read. So if your favorite theme has very low contrast light black text, for example, there’s not much one can do to remedy that. At least it seemed to me at the time the PR was discussed, a lot of diverging opinions stemmed from people using more or less contrasty themes.

2 Likes

This now (Julia 1.6.1) gives the error replaceuserpath not defined.
What might I be doing wrong here?
Example:

julia> sin("x")
ERROR: MethodError: no method matching sin(::String)
Closest candidates are:
  sin(::Float16) at math.jl:1159
  sin(::ComplexF16) at math.jl:1160
  sin(::Complex{T}) where T at complex.jl:831
  ...
Stacktrace:

SYSTEM (REPL): showing an error caused an error
ERROR: UndefVarError: replaceuserpath not defined
Stacktrace:

SYSTEM (REPL): caught exception of type UndefVarError while trying to handle a nested exception; giving up
2 Likes

Change replaceuserpath to contractuser.

4 Likes

Thank you!