Skip to content

Commit

Permalink
v0.2.0 new features and fixes (#64)
Browse files Browse the repository at this point in the history
* Optionally use `names(module)` to look up frames to re-insert into the stack trace to expose the public API calls; use `ENV["JULIA_STACKTRACE_PUBLIC"] = true`
* Simplified display of internal frames by eliminating the superfluous frame numbers, adding italics (1.10+), and making the commas gray
* Restores function of `ENV["JULIA_STACKTRACE_MINIMAL"] = true`
* Includes temporary fix for inline frame module assignment bug to be fixed by [Remove fallback that assigns a module to inlined frames. JuliaLang/julia#51405](JuliaLang/julia#51405)
* Fixes [Remove VSCode extension overrides setting `err` global once incorporated into it. #41](#41) , as VSCode extension now includes `err` global.
* Removed code to bring same-location frame collapsing to 1.9, as it was misbehaving.
  • Loading branch information
BioTurboNick authored Sep 25, 2023
1 parent 9335d91 commit b487ff0
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 219 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "AbbreviatedStackTraces"
uuid = "ac637c84-cc71-43bf-9c33-c1b4316be3d4"
authors = ["Nicholas Bauer <[email protected]>"]
version = "0.1.14"
version = "0.2.0"

[deps]
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ But in the rarer case where the issue was *not* in your code, the full trace can
## Options
* `ENV["JULIA_STACKTRACE_ABBREVIATED"] = true` enables abbreviated stack traces for all traces, not just those originating from an interactive session
* `ENV["JULIA_STACKTRACE_MINIMAL"] = true` omits type information for a one-line-per-frame minimal variant (see below)
* `ENV["JULIA_STACKTRACE_PUBLIC"] = true` will re-insert all functions from a module's public API (part of `names(module)`; Julia < 1.11, this will just be exported names)

## startup.jl and VSCode
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

## Examples

Expand All @@ -43,26 +48,35 @@ using AbbreviatedStackTraces # over-writes error-related `Base` methods
using BenchmarkTools, Plots
@btime plot([1,2,3], seriestype=:blah)
```
![image](https://user-images.githubusercontent.com/1438610/115907559-0c36b300-a437-11eb-87c3-ba314ab6db72.png)

<img width="848" alt="image" src="https://github.com/BioTurboNick/AbbreviatedStackTraces.jl/assets/1438610/7d32ab8d-ff92-47d1-93b1-d683edc6cb85">

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.

![image](https://user-images.githubusercontent.com/1438610/116329328-1dfeba00-a799-11eb-8b86-f5c28e5b78e0.png)
<img width="736" alt="image" src="https://github.com/BioTurboNick/AbbreviatedStackTraces.jl/assets/1438610/a03ff4ca-9113-4546-9269-00526b7323b4">

(Note: italics only works on Julia 1.10+)

The global `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 be setting `ENV["JULIA_STACKTRACE_PUBLIC"] = true`.

<img width="737" alt="image" src="https://github.com/BioTurboNick/AbbreviatedStackTraces.jl/assets/1438610/66b77163-e3a1-424a-9c28-7df52caa1ebb">

There is an optional minimal display available, accessed by setting `ENV["JULIA_STACKTRACE_MINIMAL"] = true`.
![image](https://user-images.githubusercontent.com/1438610/116329297-0b848080-a799-11eb-9d71-32650092b3a5.png)

<img width="838" alt="image" src="https://github.com/BioTurboNick/AbbreviatedStackTraces.jl/assets/1438610/9379b2a9-7880-4122-8727-64cd6c5fed18">



Here's an example a beginner might readily run into:
![image](https://user-images.githubusercontent.com/1438610/121451945-8a5e0300-c96c-11eb-9070-d431b1cadc56.png)

<img width="845" alt="image" src="https://github.com/BioTurboNick/AbbreviatedStackTraces.jl/assets/1438610/b6af91a2-bff2-4a0f-91fd-c33b8727e165">

**Yikes!**

With this package:
![image](https://user-images.githubusercontent.com/1438610/121452028-b4172a00-c96c-11eb-961b-300cbcbf5ad9.png)

<img width="845" alt="image" src="https://github.com/BioTurboNick/AbbreviatedStackTraces.jl/assets/1438610/ec413046-bb1e-43e6-bc93-ca29852a69c7">

**Much better!**
124 changes: 106 additions & 18 deletions src/AbbreviatedStackTraces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,51 +108,139 @@ function find_visible_frames(trace::Vector)
return visible_frames_i
end

function find_visible_frames_public(trace::Vector)
public_frames_i = findall(trace) do frame
framemodule = parentmodule(frame[1])
framemodule === nothing && return false
module_public_names = names(framemodule)
frame[1].func module_public_names
end

user_frames_i = findall(trace) do frame
file = String(frame[1].file)
!is_julia(file) && !is_ide_support(file)
end

# construct set of visible modules
all_modules = convert(Vector{Module}, filter!(!isnothing, unique(t[1] |> parentmodule for t trace)))
user_modules = convert(Vector{Module}, filter!(!isnothing, unique(t[1] |> parentmodule for t @view trace[user_frames_i])))
Main user_modules || push!(user_modules, Main)

debug_entries = split(get(ENV, "JULIA_DEBUG", ""), ",")
debug_include = filter(x -> !startswith(x, "!"), debug_entries)
debug_exclude = lstrip.(filter!(x -> startswith(x, "!"), debug_entries), '!')

debug_include_modules = filter(m -> string(m) debug_include, all_modules)
debug_exclude_modules = filter(m -> string(m) debug_exclude, all_modules)
setdiff!(union!(user_modules, debug_include_modules), debug_exclude_modules)

# construct set of visible frames
visible_frames_i = findall(trace) do frame
file = String(frame[1].file)
filenamebase = file |> basename |> splitext |> first
mod = parentmodule(frame[1])
return (mod user_modules || filenamebase debug_include) &&
!(filenamebase debug_exclude) ||
is_top_level_frame(frame[1]) && is_repl(file) ||
!is_julia(file) && !is_ide_support(file)
end

# add one additional frame above each contiguous set of user code frames, removing 0.
filter!(>(0), sort!(union!(visible_frames_i, visible_frames_i .- 1)))

# remove Main frames that originate from internal code (e.g. BenchmarkTools)
filter!(i -> parentmodule(trace[i][1]) != Main || !is_julia(string(trace[i][1].file)), visible_frames_i)

# for each appearance of an already-visible `materialize` broadcast frame, include
# the next immediate hidden frame after the last `broadcast` frame
broadcasti = []
for i visible_frames_i
trace[i][1].func == :materialize || continue
push!(broadcasti, findlast(trace[1:i - 1]) do frame
!is_broadcast(String(frame[1].file))
end)
end
sort!(union!(visible_frames_i, filter!(!isnothing, broadcasti)))

# add back public frames
sort!(union!(visible_frames_i, public_frames_i))

if length(trace) > 1 && visible_frames_i[end] != length(trace)
# add back the top level if it's not included (as can happen if a macro is expanded at top-level)
push!(visible_frames_i, length(trace))
end

if length(visible_frames_i) > 0 && visible_frames_i[end] == length(trace)
# remove REPL-based top-level
# note: file field for top-level is different from the rest, doesn't include ./
startswith(String(trace[end][1].file), "REPL") && pop!(visible_frames_i)
end

if length(visible_frames_i) == 1 && trace[only(visible_frames_i)][1].func == :materialize
# remove a materialize frame if it is the only visible frame
pop!(visible_frames_i)
end

return visible_frames_i
end

function show_compact_backtrace(io::IO, trace::Vector; print_linebreaks::Bool)
#= Show the lowest stackframe and display a message telling user how to
retrieve the full trace =#
num_frames = length(trace)
ndigits_max = ndigits(num_frames) * 2 + 1
ndigits_max = ndigits(num_frames)

modulecolordict = copy(STACKTRACE_FIXEDCOLORS)
modulecolorcycler = Iterators.Stateful(Iterators.cycle(STACKTRACE_MODULECOLORS))

function print_omitted_modules(i, j)
# Find modules involved in intermediate frames and print them
modules = filter!(!isnothing, unique(t[1] |> parentmodule for t @view trace[i:j]))
if i < j
print(io, " " ^ (ndigits_max - ndigits(i) - ndigits(j)))
print(io, "[" * string(i) * "-" * string(j) * "] ")
print(io, " " ^ (ndigits_max + 4))
printstyled(io, "", bold = true)
if VERSION v"1.10"
printstyled(io, "internal", color = :light_black, italic=true)
else
print(io, " " ^ (ndigits_max - ndigits(i) + 1))
print(io, "[" * string(i) * "] ")
printstyled(io, "internal", color = :light_black)
end
printstyled(io, "", bold = true)
printstyled(io, "internal", color = :light_black)
if !parse(Bool, get(ENV, "JULIA_STACKTRACE_MINIMAL", "false"))
println(io)
print(io, " " ^ (ndigits_max + 2))
print(io, " ")
if VERSION v"1.10"
printstyled(io, "@ ", color = :light_black, italic=true)
else
print(io, " ")
printstyled(io, "@ ", color = :light_black)
end
printstyled(io, "@ ", color = :light_black)
if length(modules) > 0
for (i, m) enumerate(modules)
modulecolor = get_modulecolor!(modulecolordict, m, modulecolorcycler)
printstyled(io, m, color = modulecolor)
i < length(modules) && print(io, ", ")
if VERSION v"1.10"
printstyled(io, m, color = modulecolor, italic=true)
i < length(modules) && printstyled(io, ", ", color = :light_black, italic=true)
else
printstyled(io, m, color = modulecolor)
i < length(modules) && printstyled(io, ", ", color = :light_black)
end

end
end
# indicate presence of inlined methods which lack module information
# (they all do right now)
if any(isnothing(parentmodule(t[1])) for t @view trace[i:j])
length(modules) > 0 && print(io, ", ")
printstyled(io, "Unknown", color = :light_black)
if VERSION v"1.10"
length(modules) > 0 && printstyled(io, ", ", color = :light_black, italic=true)
printstyled(io, "Unknown", color = :light_black, italic=true)
else
length(modules) > 0 && printstyled(io, ", ", color = :light_black)
printstyled(io, "Unknown", color = :light_black)
end
end
end

# select frames from user-controlled code
is = find_visible_frames(trace)
if VERSION >= v"1.11.0-DEV.511"
is = parse(Bool, get(ENV, "JULIA_STACKTRACE_PUBLIC", "false")) ? find_visible_frames_public(trace) : find_visible_frames(trace)
else
is = find_visible_frames(trace)
end

num_vis_frames = length(is)

Expand Down
Loading

0 comments on commit b487ff0

Please sign in to comment.