Julia: What's needed to include ClearStacktrace.jl in Base?

Created on 25 May 2020  ·  99Comments  ·  Source: JuliaLang/julia

The title says it all. I think I figured out a good format to present stack traces, and I think all users could benefit from it. It's currently in ClearStacktrace.jl and depends on Crayons.jl for the colors.

I want to know what I can do to make this PR-ready. I don't know how Julia Base handles REPL colors, which ones I'm allowed to use, how I access them without Crayons etc. I also don't know if there might be system-specific intricacies of stack traces that I have overlooked, testing this only a Windows and a Mac machine.

The basic idea is to have three columns, function, module and signature. Function and module together should basically always fit in a REPL horizontally, while signatures can be very long and are therefore allowed to overflow into new lines, while staying intact when resizing the REPL (compared to more complex but brittle table layouting). The code paths get a new line each, and are also allowed to overflow if they are too long. This keeps clickable links in Atom / VSCode and the like intact.

Just for reference, here is the before and after comparison from the README:
Before:
grafik
After:
grafik

Most helpful comment

another iteration: I tried getting variable names in there, even though it all feels super hacky to me as I'm not very familiar with all the internals. I re-colored the modules because it seemed to get a positive response in general, even though it's debatable how the colors should be assigned. But with the colors, I feel that the module of one entry clashes a bit with the function name of the next. So I introduced line breaks. Makes everything longer of course, but I kind of like the additional breathing room and clarity

grafik

All 99 comments

This would be really nice to have. Or at least maybe a trimmed down version which improves printing of stacktraces by default in Base.

I'm all in favor. The main thing I don't get is the colors in the signatures. They seem...random? I guess the different colors are there to make it easier to pick out different elements, but it's a bit weird. How about color == nesting depth? I think the main weirdness is in e.g. Tuple{Symbol,Symbol} where the two Symbols are different colors.

There's another thing I'd really like to see:

julia> foo(x::T, y::T) where T = error("whoops")
foo (generic function with 1 method)

julia> function bar()
           a = rand(3, 5)
           b = view(a, :, 2:3)
           c = reinterpret(Float32, b)
           foo(c, c)
       end
bar (generic function with 1 method)

julia> bar()
ERROR: whoops
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] foo(::Base.ReinterpretArray{Float32,2,Float64,SubArray{Float64,2,Array{Float64,2},Tuple{Base.Slice{Base.OneTo{Int64}},UnitRange{Int64}},true}}, ::Base.ReinterpretArray{Float32,2,Float64,SubArray{Float64,2,Array{Float64,2},Tuple{Base.Slice{Base.OneTo{Int64}},UnitRange{Int64}},true}}) at ./REPL[1]:1
 [3] bar() at ./REPL[2]:5
 [4] top-level scope at REPL[3]:1

but since

julia> m = first(methods(foo))
foo(x::T, y::T) where T in Main at REPL[1]:1

julia> m.sig
Tuple{typeof(foo),T,T} where T

it seems that we should be able to print entry 2 as something like

[2] foo(::T, ::T) where T = Base.ReinterpretArray{Float32,2,Float64,SubArray{Float64,2,Array{Float64,2},Tuple{Base.Slice{Base.OneTo{Int64}},UnitRange{Int64}},true}}

A small comment, but to me it is currently maybe a bit too much color "salad". The stackframe counter being blue, all the different colors in the signature etc. For Base colors so far, we've only used the 16 system colors (8 + 8 bright ones) and that makes fancy coloring a bit harder.

Good suggestions overall. Maybe a couple comments why I made the color choices so far:

The numbers are blue because they are important, so shouldn't be grayed out, but making them white felt to me like they were fighting for attention with the function names.

The modules use the primary colors in a cycle, I think this is very useful because it allows to quickly filter the trace for relevant modules. This is something that is very to do with the current stack trace format. The idea is that people should be forced to read as little as possible to find what they’re looking for. (I mostly did eye movement and attention research in my academic life so far, so those aspects are important to me ;) )

The colors for the signatures are very debatable. The ones on the screenshot above were chosen to be only slightly different from dark gray. At the same time, I wanted the different semantic components to be discriminable. That’s why all types and type parameters (curly braces and commas) cause a color switch. There are three colors which means there won’t be two neighbors with the same color. I found that it’s quite difficult to make out single words in huge type signatures if they are all just white. The slightly different shades help a lot but also make everything a bit more noisy, visually.

As the 16 color set doesn’t have colors that are only marginally different from dark gray, it’s probably not something that can be included in Base. In a recent update, I replaced the colors with three slightly different shades of gray from the ANSI set, but even that will not be supported everywhere I guess.

Oh also the stack frame color and the module colors as well as the dark gray and white are all system colors. It’s just my particular VSCode theme that renders them like you see here.

This would be great to have.

When scanning argument types, often it's pretty hard to figure out where one stops and the next starts. Colouring the top level differently might be very helpful here, and perhaps even numbering the arguments:

[3]   (_1::DataFrames.var"#fun##309#"{...lighter...}, _2::NamedTuple{...lighter...}, _3::Int64})

We should be able to show the argument names, like we do in the printing of methods(f).

how's this? I've replaced the signature colors with dark gray, but the :: is white so it's salient where argument types begin. especially helpful with the type monster at position 11
grafik

I like it. Will be even better with argument names.

It's too bad that the order "function - module - arguments" works so well for the layout but doesn't really make sense semantically. I don't have a solution though; clearly it's best for the function to come first. Maybe the module could go on the next line before the file location? It is part of the location after all. Then maybe we wouldn't need the header either.

I think we could also put the numbers in normal text, and the function names in bold and/or white.

Could the filename at the end of the codepath (and possibly the line number) get their own lighter color too?

Maybe the module could go on the next line before the file location? It is part of the location after all. Then maybe we wouldn't need the header either.

I'll try out a couple things with these suggestions.

Could the filename at the end of the codepath (and possibly the line number) get their own lighter color too?

It technically can of course ;) It's always a tradeoff between better visibility and attention-grabbing noise.

Why not write the modules first, since they are part of the function's full name? Perhaps with colour it may be clear enough just to write Core.eval etc, or they could still be aligned as columns. (Or perhaps you tried this and it looked awful.)

Really crazy thought: if printing the argument types would take up more than one line, print the signature in folded form and then use REPL.TerminalMenus to allow viewers to expand it. Something like:

julia> run_command(args...)
    Function    Arguments              Location
[1] f           (x::Int, y::Float64)   MyModule1, /path/to/file1.jl: 15
[2] g          +(...)                  MyModule2, /path/to/file2.jl: 64
...

julia> lasttrace()
# presents the above in menu form, allowing user to expand line 2

Related: #35915 (which I plan to use to create a folded-tree menu, https://github.com/JuliaCollections/FoldingTrees.jl).

How does it look with codepath rearranged in this order: LineNumber Filename Path
Then your eyes have a stable location to look for the line number and filename with no extra color needed for them (I seem to always be searching for the filename and line number buried in the details, can you tell?)
And for interactive REPL work I love the folded idea above :)

Why not write the modules first, since they are part of the function's full name

Yeah I kind of like the idea, I think I started out without color and that didn't work so well, but like this it's actually quite readable. I'll try without columns next, although columns are always nice because they guide your eyes.

grafik

Not sure about that; the function name is a vastly more important piece of information than the module. For me at least, the most important info is definitely the function name, file name, and line number. Also for scripts the left column would be lots of Main or blank, which is not ideal for that use case.

I think when I’m reading stacktraces, I’m mostly looking for the last call in my code before it goes to Base/package code, since usually the bug is in my code. So in that case the module is pretty important and the function call isn’t what I’m scanning first.

How about this:

[1]  get_ticklabels   ::AbstractPlotting.Automatic, ::Int64
     in MakieLayout at ~/.julia/packages/MakieLayout/COfBu/src/lineaxis.jl:372

Module names would still line up so they would be easy to scan.

I had actually just tried out something close to that. I colored the function names themselves this time, so they are still the most salient but carry the Module information in their color. So there's still an obvious grouping, but with less focus on all the module names.

grafik

The first time I saw that I'd probably go nuts trying to figure out what the colors meant :joy: If there are going to be per-module colors, seems better to just apply the color to the module name.

I like the module names, they get a bit lost in this latest picture.

If they are at the bottom, perhaps making them the same bright colour would connect things? (And make clear where you cross a module boundary.) And, perhaps, if the module name appears in the path, it could simply be highlighted there instead -- that would also move the colourful name off the left edge where the functions are.

For some inspiration, I post a few images from the last time tweaking the stacktrace formatting came up.

bild

bild

Often the exact package is not so much of interest as the nature of the code: is it core julia, a library I'm using, the library I'm developing, or a script? We could have four colors: core/base/dev/stdlib, packages, packages currently ]deved, and all the rest (including script/REPL). Some of these distinctions are pretty arbitrary and the classification is a bit brittle, but coloring the method based on this would (at least for me) maximize the information without displaying any extra text and keeping a small, easy to memorize set of colors.

For me, the colors are more about boundary changes, where code from one module begins and the other ends. That's why I probably wouldn't assign inherent meaning to them. On the other hand, that might be confusing.

I've tried to simplify more, and removed columns again because without the module column they are not so useful anymore. Then first I colored only line number by modules, but this still left the file name not so visible, which many comments said is important. So I colored that in the module color as well. The module name itself is not colored because it's too close to the function name and that's too noisy.

Here is the version with both numbers and filenames colored:

grafik

here without filenames colored

grafik

Can you also try coloring the full file path?

Some ideas...
Filename and line number in a consistent location with double spaces to set them off.
Stack level color and module color match each other to visually pair the related lines.
Filename, line number, and path in slightly different hue than parameter types to distinguish without clutter.
stacktrace
[Colors in this example are arbitrary, more thinking about how their positions relate.
Missing stack levels in example and shortened parameter type list on last item are because I typed example by hand.]

Code used to produce the sample above in case anyone wants to play with it:

function main()

    errors = [
              ("1", "get_ticklabels", ("AbstractPlotting.Automatic", "Int64"), "372", "lineaxis.jl", "MakieLayout", "~/.julia/packages/MakieLayout/COfBu/src")
              ("2", "get_ticklabels", ("AbstractPlotting.Automatic", "Int64"), "351", "lineaxis.jl", "MakieLayout", "~/.julia/packages/MakieLayout/COfBu/src")
              ("3", "#105", ("AbstractPlotting.Automatic",), "152", "lineaxis.jl", "MakieLayout", "~/.julia/packages/MakieLayout/COfBu/src")
              ("8", "OnUpdate", ("Tuple{Float32,Float32}",), "218", "Observables.jl", "Observables", "~/.julia/packages/Observables/0wrF6/src")
              ("9", "#setindex!#5", ("Observables.var\"#6#8\"",), "138", "Observables.jl", "Observables", "~/.julia/packages/Observables/0wrF6/src")
              ("10", "setindex!", ("Observables.Observable{Any}",), "126", "Observables.jl", "Observables", "~/.julia/packages/Observables/0wrF6/src")
              ("11", "#LineAxis#95", ("Base.Iterators.Pairs{Symbol,Observables.Observable,NTuple{28,Symbol},NamedTuple{(:endpoints, :limits, :flipped, :ticklabelrotation, :ticklabelalign, :lables
ize, :labelpadding, :ticklabelpad, :labelvisible, :label, :labelfont, :ticklabelfont, :ticklabelcolor}",), "270", "lineaxis.jl", "MakieLayout", "~/.julia/packages/MakieLayout/COfBu/src/lobjects")
    ]

    println()
    for (idx, err) in enumerate(errors)
        # Module color
        mc = idx <= 3 ? :light_red : (idx >= 7 ? :light_red : :light_yellow)
        # Path color
        pc = :blue
        printstyled("[", color=mc)
        printstyled("$(err[1])", color=mc)     # errorno
        printstyled("]  ", color=mc)
        printstyled("$(err[5])", color=pc)     # filename
        printstyled(":", color=pc)             # colon
        printstyled("$(err[4])", color=pc)     # lineno
        printstyled("  $(err[7])", color=pc)   # path
        println()
        printstyled("$(err[6]) ", color=mc)    # module
        printstyled("$(err[2]) ", color=:bold) # function
        printstyled("(", color=:light_blue)    # param types
        for t in err[3]
            printstyled("::", color=:white)
            printstyled("$t", color=:light_blue)
        end
        printstyled(")", color=:light_blue)
        println()
    end
end

The problem with this is that this doesn't leave the links clickable in Atom / VSCode, etc. This is something I use very often and I believe others do as well. In fact, I even explicitly expand the base paths to make them clickable. This does of course print more. But I think it increases utility. I know there's some way to jump to stack trace entries by number in the REPL with some shortcut, but this is far less intuitive than just clicking on a link in my opinion.

It might make sense to take the opportunity to unify the way line info is printed with the logging system, for example:

bild

Also, note that the logging system does contract the homedir

julia> include("foo.jl")
┌ Warning: foo
└ @ Main ~/julia/foo.jl:1

I think there are two core ideas in the proposal here:

  • Have the line info on a separate line from the signature (and perhaps delimited it with some color to make it easier to find).
  • Show the module.

So here is a slightly more conservative attempt with both those ideas:

bild

A version with variable names added in, and colour just on the module names (but otherwise matching how @info prints paths):

Screenshot 2020-05-28 at 15 55 56

This one doesn't have very long type signatures, but perhaps having variable names like this (if that's possible?) will make it easier to spot the outermost type of each argument.

If there are three levels of neutral available (like bold/normal/grey), then printing the file path more lightly than the signature seems (IMO) like a good way to stop things running together. (This is also what @info does.)

Adapting @timotaularson 's code

let
    printstyled("\njulia> ", color=:green)
    println("f()")
    printstyled("""ERROR: DimensionMismatch("A has dimensions (1,2) but B has dimensions (3,4)")""", color=:light_red)
    println("\nStacktrace:")
    for (idx, err) in enumerate(errors)
        mc = idx <= 3 ? :blue : (idx >= 7 ? :blue : :yellow) # Module color
        printstyled("[$(err[1])] ", color=:normal, bold=true) # errorno
        printstyled(err[2], color=:normal, bold=true) # function
        printstyled("(", color=:normal)    # param types
        for (i,t) in enumerate(err[3])
            i != 1 && printstyled(", ", color=:normal)
            printstyled(rand('a':'z')^2, color=:light_black)
            printstyled("::", color=:normal)
            printstyled("$t", color=:normal)
        end
        printstyled(")", color=:normal)
        println()
        printstyled("  @ ", color=:light_black)
        printstyled(err[6], color=mc)    # module
        printstyled(" $(err[7])/$(err[5]):$(err[4])", color=:light_black)   # path
        println()
    end
end

I think there are two core ideas in the proposal here:

* Have the line info on a separate line from the signature (and perhaps delimited it with some color to make it easier to find).

* Show the module.

So here is a slightly more conservative attempt with both those ideas:

I like this. I think the term is "kill your darlings", and my darling might be the color. But I can see how something toned-down might be better suited for something so basic. I changed it a bit so that the module name and the function name line up, which creates an almost column-like vertical contrast line. That should help to find the relevant information. Though I still think I wouldn't put the types in white, because they get so messy. If we had variable names, maybe those in white would look good.

grafik

I like this. I think the term is "kill your darlings", and my darling might be the color.

Just so you know that I know your situation, https://github.com/JuliaLang/julia/pull/18228. This is a topic that is very fun to bikeshed and everyone has a bucket of paint, heh. As you can see, that PR also started quite ambitiously but slowly got more and more conservative heh.

To me, the last proposal is definitely an improvement over status quo. Two things I am thinking:

  • If possible, it would be nice to have some kind of "hook" for your eyes for the line information because it tend to blend together with the signature when the signature is long.
  • We should probably align the stackframe numbers when there are more than 10 of them:
    [ 8] [ 9] [10] [11]
    etc. That would make the @ also line up.

I like it, I think we're getting somewhere. Being judicious with colors is always good, since that exposes less surface area for bikeshedding, plus decreases the risk that a color will be unreadable on somebody's terminal.

Spacing things out is good, but unfortunately the syntax f (...) is explicitly disallowed, so I think we need to either remove the space or remove the parens.

The most recent sample generally looks good, but I second the idea:

If possible, it would be nice to have some kind of "hook" for your eyes for the line information because it tend to blend together with the signature when the signature is long.

And I hope that hook is a different color or (preferably) brightness.

Maybe you could do the cycle of colors for the modules but only apply it to the [ ] on either side of the stack level number so if you cannot see the colors for some reason not much is lost.

Maybe you could do the cycle of colors for the modules but only apply it to the [ ]

It's hard to even see the color correctly if it's only brackets and they are in a contrasty area, so I don't think that works too well..

the syntax f (...) is explicitly disallowed

I think it works well even without the space, because of the contrast difference.

If possible, it would be nice to have some kind of "hook" for your eyes for the line information

I agree, I think it works well to leave the line numbers gray but bold them, that makes them just slightly salient if you look for them but the next step of making them white is way too noisy in my opinion.

We should probably align the stackframe numbers

I tried it with spaces inside the brackets like in your example but found that looked a bit weird, so now I'm just right aligning the bracketed numbers. I think that works pretty well.

Here's the newest version with all those ideas incorporated:

grafik

And as a bonus, here's how it could look with (random) variable names. Again I'm just using bold gray because all these variable names should not fight for your attention, but should be a bit discernible if you look for them
grafik

I do like where this is going too. I want to just put a vote in for some kind of highlighting of the module name. I actually quite like the style where the same module is colored the same way and it cycles through the colors (there's a graph color problem in here if you consider two stack trace lines being adjacent as a graph edge between their modules). It is, unfortunately, pretty non-obvious what that color mean, however. Perhaps coloring all the module names in a single color?

This alignment looks good to me.

I still think that having the paths more clearly distinct from the signatures would help break things up -- 3 levels of importance, name/signature/path. Logging macros @info etc. print them ligher than other lines.

Variable names would be extremely nice to have. Would vote that they be less significant than signature information, probably?

Having the module names lined up at the left does help you scan them, although I'm sad to see colours go.

My eyes seem to know where to find things on this one :)
Would a blank line between modules use too much vertical space?

Coloring rows with 2 different colors so that alternate rows have the same color - as is often done in tables might help readability.

Color cycling the @ next to the module name instead of the module name itself stands out (because @ is a pretty big character) without competing with the highlighted function name too much.

I have often wished for the parameter names since they help you find the right parameter type without having to count through them.

Could the path (and perhaps the module name next to it) be a different brightness than the types? Maybe just matching the parameter names? (Or all low-lighted together if you do mcabbott's suggestion of making the names dimmer than the types)

You could explicitly mirror what logging uses, although ideally with different colours:

for i in 1:3
    printstyled("┌[", color=:magenta, bold=true); print(i); printstyled("] ", color=:magenta, bold=true)
    printstyled("get_ticklabels", bold=true); print("("); printstyled("style", color=:light_black); print("::AbstractPlotting.Automatic, "); printstyled("n", color=:light_black); print("::Int64")
    i!=2 ? println(")") : begin print(", "); printstyled("xs", color=:light_black);  print("::Array{Float64,2}, ");  printstyled("ys", color=:light_black);  print("::Array{Float64,1}, ");  printstyled("yes", color=:light_black);  print("::Bool, ");  printstyled("no", color=:light_black);  println("::Bool)") end
    printstyled("└ @ ", color=:magenta, bold=true); printstyled("MakieLayout", color=:magenta); printstyled(" ~/.julia/packages/MakieLayout/COfBu/src/lineaxis.jl:372", color=:light_black); println();
end
@error join([join(rand('a':'z', rand(1:9))) for _ in 1:25]," ") pi

Edit: now with one longer line which wraps, and a picture:
Screenshot 2020-05-28 at 20 21 48

You could explicitly mirror what logging uses

The thing that I don't like so much about this is that line breaks that are only there because of the specific REPL width don't transfer well if you resize your window / REPL (if you want to make room for longer lines for example) or copy / paste the text into windows of different width

Yes, I would not propose explicitly breaking the lines to make the markers continuous. In fact it may be a good thing to let wrapped lines break through, and hence look different. Mostly a thought on how to tie the colour to the function name, and how to make it perhaps less obtrusive, if it's part of the border instead of looking like the most important heading.

I must say I liked the colour but I can understand the preference to be conservative with it.

I want, however, to throw my support behind something like

Often the exact package is not so much of interest as the nature of the code: is it core julia, a library I'm using, the library I'm developing, or a script? We could have four colors: core/base/dev/stdlib, packages, packages currently ]deved, and all the rest (including script/REPL). [...]

Since I'm not hacking on base and rarely on packages (as most people do, I presume), 90% of errors need to be fixed in my script, 9% in a package and 1% in base. If I made a mistake calling a function I don't need to know here exactly in base the error was thrown since I'm not going in there to fix it anyway, I need to fix my code.

So I'd appreciate some visual helper to see where I'm leaving my code (and maybe also where I'm leaving package code, going into base) because in most cases anything after that I can safely ignore for now. If not via colour, maybe with -------------------------------------------- or something? There should only ever be 2-3 of those.

If modules get coloured, then perhaps Base/Core should be left out and given no colour.

I actually quite like the style where the same module is colored the same way and it cycles through the colors

I'm fine with that too. If we're going to use many colors, I think they should be used for that. It also helps pick out boundaries between user and package code without taking up too much vertical space.

If modules get coloured, then perhaps Base/Core should be left out and given no colour.

Yes, Base and Core could just always be dark gray or something, leaving more colors for everybody else :)

If this gives up working perfectly in 16 colours, then it might be nice to make them a hash of the module names, so that you will memorise some of them.

Or only have two colors, one for Base/Core and one for external modules?

I think it shouldn't be much of a problem to cycle colors because my current version has 6 non-grayscale options:

crayon"blue",
crayon"yellow",
crayon"red",
crayon"green",
crayon"cyan",
crayon"magenta",

There are light variants too I guess, but my color scheme in VSCode (Material) has them set the same as the darker siblings, so I assume that could happen in some schemes. But how likely is it that a stack trace goes through more than 6 different non-base or non-standard library modules? Of course packages use code from many modules, but in one particular stack there shouldn't be that many. At least that shouldn't cause a huge problem when deciphering the stack trace, even if there are color collisions.

then it might be nice to make them a hash of the module names, so that you will memorise some of them

This is actually kind of brilliant.

Great to see people working on this.
I'd be interested to see how right alignment of the function names looks:

 [1] get_ticklabels(code_lowered::...
 [2] get_ticklabels(real::AbstractPlotting
 [3] #105(mtime::
 [4] OnUpdate(vecormat::
 [5] #setindex!#5(atexti::

vs

 [1] get_ticklabels(code_lowered::...
 [2] get_ticklabels(real::AbstractPlotting
 [3]           #105(mtime::
 [4]       OnUpdate(vecormat::
 [5]   #setindex!#5(atexti::
 [6]      setindex!(mapslices

It might be easier for the eye to catch where the function arguments start and offer assistance on parsing the chain of function calls while not wasting much space. It still needs to be supported by colors, otherwise the locations infos and arguments would drown everything.

then it might be nice to make them a hash of the module names, so that you will memorise some of them

This is actually kind of brilliant.

Oh, hello darkish teal, my old friend...

then it might be nice to make them a hash of the module names, so that you will memorise some of them

I used to have emacs do exactly that for IRC nicknames. It didn't work out so well because some of my friends got assigned the same color and it was more confusing than anything, so I ended up hard-coding colors for my friends. I wouldn't recommend going down that route, however cool it seems. Having a reduced set of colors for each code type (eg Julia, package, deved package, user) seems more useful.

I landed on an editor colour scheme which does this, and it's moderately useful -- all the variables which might otherwise be blue are various shades of green & blue, with strings & keywords etc. different. Perhaps the ideal would be something like this within broad classes like the ones you suggest (e.g. standard library in red-purple, downloaded in green-blue, dev-ed in orange-yellow, base in gray). Or perhaps this is much too much work!

another iteration: I tried getting variable names in there, even though it all feels super hacky to me as I'm not very familiar with all the internals. I re-colored the modules because it seemed to get a positive response in general, even though it's debatable how the colors should be assigned. But with the colors, I feel that the module of one entry clashes a bit with the function name of the next. So I introduced line breaks. Makes everything longer of course, but I kind of like the additional breathing room and clarity

grafik

I like your last sample, it is very clear and readable :)

Below is just an experiment applying the module coloring to the "@" instead of the module name to reduce the clash you mentioned with the function name, and trying out highlighting the types so the path can be distinguished a bit easier without having to introduce blank lines.

stacktrace2

@jkrumbiegel That's beautiful. I need this in my life.

I like those soft blues

@jkrumbiegel Great, I am not sure the @ symbol is needed, it might look cleaner without it. If you want to explain what that information is, you might as well just write it like Jeff suggested:
in MakieLayout at ~/.julia/packages/MakieLayout/COfBu/src/lineaxis.jl:372

@jkrumbiegel That looks great! Is there a way to highlight just the filename (not the full path) and line number? Perhaps a slightly brighter grey?

Lovely! I actually really like the line breaks—they really help visually and our stack traces tend to be so long I have to scroll anyway, I’d rather it be clear than reduce the scrolling by a little bit.

The highlighted :: are brilliant. If I would have to plot a curve showing the information content of each character of the parameter list against character position, the :: exactly mark the local maxima of that curve. I know to look to left of them for the name and to the right for the most important part of the type, after that the information content only drops.

I disagree with this comment: the highlighting should emphasize important things so that the eye knows what to read, and the :: definitely is not an important thing I should read. As it stands the white is much more noticeable than the bold dark blue. Distinguishing the parameters would be clearer by just having in white the argument names (perhaps with the exception of when there's no named argument, in which case :: might as well be in white to delimit arguments). Same goes for the line number, I'd rather emphasize the file name than the line number.

The colored package names run the risk of overshadowing the most important thing, the function name, but having those in bold and with linebreaks really limits that risk, well done!

I thought about the :: a lot before I made them white. Of course the argument names and the types are the actual information, so you might think they should be highlighted. But my strong impression when highlighting them is that they call for your attention. But it’s not important to read every name and type. You should just be able to find those that you’re looking for in a specific function. How I see the steps of reading a stack trace:

  1. Get a general overview
  2. What function in what module errored?
  3. What other functions called this function from which other modules?
  4. Is there a critical boundary between my own code and base or other package code? Where is it?
  5. What function is at that boundary?
  6. What types / arguments were used? Where does one argument end and the next one begin? This is especially tough with long parametric types right now.
  7. Does that explain the error?
  8. If this didn’t help, go through everything with a fine comb

So the type / argument information is in my opinion used only after orienting and understanding the general structure. One thing to understand about highlighting is that it doesn’t necessarily make it easier to find things, only if the highlights are relatively sparse. So I think the :: highlight doesn’t get in the way and grab your attention until you start looking for variable / type information. And at that step it gives your eyes hooks to jump to, nothing more, nothing less. I find that quite efficient.

As a side note, not every argument has a name, so highlighting variable names would not always give you good separation. I think the :: is always there.

That seems like a good list, my main complaint is point 6, finding the gaps in long type signatures. Printing :: super-bright does help, although I agree it's a little weird. Printing the argument names (or perhaps some _1 placeholder if none) would help a bit. Perhaps printing the outermost type normally, and then all its arguments in gray, would help a lot?

How many levels of highlighting are actually available? :white won't stand out on light backgrounds, in fact I think we may be limited to normal, bold, and :light_black (plus colours). Right now the function name and the path are bold. In @error etc, the path is light_black, which seems great.

I like the use of colour to highlight modules. In some of the samples above it outshines the other text, but is this true of simple colours? In a few different themes, "Module" here is relatively muted (and ::Array, ::Adjoint, ::Int pretty clearly the top-level signature):

    printstyled("\nfunction", bold=true); print("(x::Array"); printstyled("{Float64,1}", color=:light_black); print(", y::Adjoint"); printstyled("{Float64,2,Array{Float64,2}}", color=:light_black); println(", z::Int64)")
    printstyled(" @ ", color=:light_black); printstyled("Module", color=:blue); printstyled(" ~/.julia/dev/Package/src/Package.jl:33 \n", color=:light_black)
end

Screenshot 2020-05-31 at 16 14 35

another iteration: I tried getting variable names in there, even though it all feels super hacky to me as I'm not very familiar with all the internals. I re-colored the modules because it seemed to get a positive response in general, even though it's debatable how the colors should be assigned. But with the colors, I feel that the module of one entry clashes a bit with the function name of the next. So I introduced line breaks. Makes everything longer of course, but I kind of like the additional breathing room and clarity

grafik

Might be a bit off-topic but since we have this, can we have a prettier show for the output of methods and methodswith? It is also a pain.

Also, the color of modules could be a dimmed color, like not exact yellow, but gray-yellow. Or, can we have those colors configurable, in like, startup.jl?

@jkrumbiegel That looks great! Is there a way to highlight just the filename (not the full path) and line number? Perhaps a slightly brighter grey?

It could: https://github.com/JuliaLang/julia/issues/36026#issuecomment-634481656. But it is a bit weird to me since the file name is part of the path so why it uses a different color? Usually, I just click the path, and VSCode will open it for me so it doesn't matter what name the file has.

I've moved away from using Crayon.jl, now that I don't actually use complex colors anymore. Helps with the load time of the package, too. I found a way to enhance the visibility of file name and line number without it becoming visually overwhelming by underlining in dark grey. That looks sensible, too, as we're used to paths / links being underlined. White or other highlighting was too strong there, bold too weak.

Also, I've switched to light colors as the first colors in the cycler, which I should have done in the first place, but they look the same in my theme so I never noticed. This should be better for themes where dark blue is hardly visible (that's the theme's fault though).

I've registered this style as version 0.2 in ClearStacktrace.jl so we can try it out a bit more.

Two examples:

example 1

example 2

That is really nice work.
You have the parameter names lighter and the types darker. Did that turn out to look better/read better that way than with the types lighter and the names darker?

Yes, as the types are usually much longer, it doesn’t help much to make them lighter.

Random thoughts:

  • maybe horizontal rules between groups of stack trace entries in the same module? maybe too busy looking
  • might be good at the top of the stack trace too, to separate it clearly from the error message

All of these are so much better than what we have now...

I've moved away from using Crayon.jl, now that I don't actually use complex colors anymore. Helps with the load time of the package, too.

Did it cause a significant load time? It loads pretty quickly on its own for me

julia> @time using Crayons
  0.014410 seconds (22.60 k allocations: 2.274 MiB)

This version is great, let's put this in Base.

Agree: let’s just do that last version.

Thanks for doing all the work here, @jkrumbiegel . Great to have a version we can try out...

And fork: I guess I think it's a bit concerning that rand(5) .* rand(7) plus error occupies 35 lines? So I made https://github.com/mcabbott/ClearStacktrace.jl/tree/milder to try out. (Plus colour issues etc discussed above.) This is almost https://github.com/JuliaLang/julia/issues/36026#issuecomment-635294818 with more colours.

Note in the current stacktrace printing frames 8-11 in that example are not shown (they are part of the REPL and would be in every REPL stacktrace).

Indeed, this has improved which is great. But it still goes from 8 lines (for the stack trace alone) to 20 (ClearStacktrace 0.2). There's a bit of a trade-off between how pretty it is & not losing your place.

The paths are also printed much more compactly in Base, ./broadcast.jl:495 instead of //Applications/Julia-1.5.app/Contents/Resources/julia/bin/../share/julia/base/broadcast.jl:495, this would also reduce the need for empty lines.

The base paths are expanded on purpose to make them clickable. You can disable that in ClearStacktrace. I could also make the newlines optional for people who don’t like them. Could just be an environment variable.

And I guess I must have missed copying the part of the function that clips off the last couple of frames that never change

I've moved away from using Crayon.jl, now that I don't actually use complex colors anymore. Helps with the load time of the package, too. I found a way to enhance the visibility of file name and line number without it becoming visually overwhelming by underlining in dark grey. That looks sensible, too, as we're used to paths / links being underlined. White or other highlighting was too strong there, bold too weak.

Also, I've switched to light colors as the first colors in the cycler, which I should have done in the first place, but they look the same in my theme so I never noticed. This should be better for themes where dark blue is hardly visible (that's the theme's fault though).

I've registered this style as version 0.2 in ClearStacktrace.jl so we can try it out a bit more.

Two examples:

example 1

example 2

Curious about why there is an extra / in //Applications/Julia-1.4.app/?

Probably a bug from splitting and rejoining the path

I've moved away from using Crayon.jl, now that I don't actually use complex colors anymore. Helps with the load time of the package, too.

Did it cause a significant load time? It loads pretty quickly on its own for me

julia> @time using Crayons

  0.014410 seconds (22.60 k allocations: 2.274 MiB)

No, I mostly removed it because I wouldn’t have it in base :) Load time was just a guess

I guess there might be too many blank lines if we have many frames of stacktrace as shown in https://github.com/JuliaLang/julia/issues/36026#issuecomment-636912686 ?

Not to derail the discussion (the last version is great and a big improvement) but on the topic of too many newlines, the problem is that when working at the terminal it seems a lot better to me to print the stacktrace "inverted" (as I originally proposed in https://github.com/JuliaLang/julia/pull/18228) as:

...
[3] frame
[2] frame
[1] frame
Error: Some error happened
blah blah

The most important information is the error message itself and the frames towards the top of the stack (closer to the error) and printing it in this order that will always be visible without scrolling. Right now I frequently have to scroll up to even see the error message and not just the tail of a stacktrace.

However, when reading a stacktrace on a webstite that has been copy-pasted from the terminal you want the opposite order because you scroll from top to bottom as opposed to bottom to top as you do in a terminal... Kinda tricky to get a good design for both these cases.

However, when reading a stacktrace on a webstite that has been copy-pasted from the terminal you want the opposite order because you scroll from top to bottom as opposed to bottom to top as you do in a terminal... Kinda tricky to get a good design for both these cases.

I actually had code in ClearStacktrace.jl for a moment that allowed me to reprint the last stacktrace. I had envisioned that for cutting off super long types at some character maximum and being able to reprint in full if the whole information was needed. But your use case would also be interesting. I can imagine a reprint(inverted = true) or even reprint(html = true) where it would print a html version that would retain coloring for pasting into a website.

Also, I agree that given the scrolling direction it could make sense to invert the whole thing by default.

ipython prints frames in the opposite order, and despite not having to scroll I've always found it inexplicably confusing. Perhaps that's just due to my prior experience with gdb and other systems where the innermost frame is at the top, or perhaps other people feel this way too?

Speaking of gdb, they have a reasonable solution to very long traces with their pager: "press enter for more frames".

By the way, I Iove the latest visual design in https://github.com/JuliaLang/julia/issues/36026#issuecomment-636912686 and would be extremely happy if we merge that. Changing the frame order or adding interactive features seem like separate issues.

Regarding file names, I hope that we can eventually use terminal hyperlink OSC sequences (yes hyperlinks in terminals are somewhat widely supported!) and have a way for the user's editor to pick that up.

Speaking of finding the "innermost frame", I use enough languages in the course of my work that I can never remember if I should be looking at the top or the bottom of a stacktrace for my code in any particular language. So I end up scanning through looking at the filename until I see one I recognize. The underlining shown here would help, so this is a clear improvement. But I still wonder if there is a goodway to call out if I should be looking at the top or the bottom. In principle printing YOUR CODE HERE on one end and OTHER CODE HERE on the other end would help me, but it doesn't seem very elegant.

I've made a PR to Base here https://github.com/JuliaLang/julia/pull/36134
It works as far as I can tell, but I'll need some help to make it ready for merging

Is there a way to omit type parameters? The problem we see a lot of the time is that the amount of type parameters in DiffEq, ForwardDiff, etc. can make things... daunting. If by default it just said Dual, except in the case where there is dispatch to other methods due to type parameters, then I think you'd reduce 90% of the noise in most stack traces I read (and in the other cases, @timholy 's suggestion is really what's required, since it's usually about making types or matching type parameters)

If they correspond to some existing type alias, they'll just go away automatically now (#36107). Otherwise, they indicate possible compilation performance bottlenecks—so possibly worth investigating?

This is done now.

For some people, but most people just get confused when it prints out a type that would take 3 pages of printed paper, so it should probably be something that's opt-in. I'll open up another issue to discuss that.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tknopp picture tknopp  ·  171Comments

stevengj picture stevengj  ·  174Comments

juliohm picture juliohm  ·  146Comments

StefanKarpinski picture StefanKarpinski  ·  141Comments

kmsquire picture kmsquire  ·  283Comments