-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Abbreviated stack traces in REPL #40537
base: master
Are you sure you want to change the base?
Conversation
That seems very arbitrary. Many times the error will be thrown going through a package and you need to figure out where in your code that call happened. For example:
would show you
Pretty useless? |
Any decision would be arbitrary, the question IMO is where a signal/noise tradeoff can reasonably be set. And if one does need the full stacktrace, either because this decision is imperfect or they really need the full trace, it just takes one more command to see it. Could add "~/.julia/packages" to the list. Most of the time a registry package's internal stack frames won't be interesting to a user. Though in the case of something like BenchmarkTools, sometimes the evaluated code is shown as originating from inside BenchmarkTools. (e.g. |
I am wondering if we could make this dependent on whether I started Julia interactively or from as a script? My horror scenario is that I am several hours into a HPC run and all I get is a stacktrace that is truncated. |
@vchuravy FWIW, this is what happens in this current PR when an error occurs in pmap:
Appears the worker doesn't use the abbreviated code path. Though it also doesn't use the relative paths like the REPL process, which is good to know. |
This proposal is extreme but goes in the right direction IMO. +1 on only doing this in interactive contexts. CI is another example where you want full stacktraces. lasterr() is a bit annoying to type, but we already use ctrl q for interacting with stack traces, we could use it here to trigger lasterr(). One intermediate option would be to display two frames (the first and the last) for each module. That way you truncate long intra-module call chains, but you keep the flow between the different packages. |
From the perspective of an ordinary user, I believe that something like this is needed. Motivation: I teach a rather basic Julia class for finance/statistics students - and one of the most common complaints from the students is that the error messages are very hard to understand: information overload and not geared towards where their errors are likely to be (in their own code, not in Base or packages). ...and they write scripts, and use the REPL only for trivial things |
If the error information is packed into a specific struct with a
I just figured out how to look up the module of a stack frame, so that's possible. An issue there is that inlined frames don't retain that information. (At least, via Adding: Confirmed that the abbreviated code path is not used here, as well: |
Indeed module information is quite brittle, and not detected reliably. A simpler and more robust method is to just use the file information. Eg collapse callchains that take place inside a single folder. That already takes care of long callchains, and keeps track of the flow among the various folders of more complex packages, which is quite nice. I tested it on some moderately complex stacktraces I had on hand, it did cut the stacktrace by a factor of ~2 and increased readability. Still results in quite large stacktraces though. |
Yes, with e.g. callbacks it is often the case that user code takes the path through a package or |
After filtering out packages and stdlib, now it just shows this:
If it's in function
If instead you were
I do understand the friction here, but I think it's possible to minimize that and I'd like to try. |
Perhaps it would be worth stepping back here, after thinking about this some more. This could instead default to showing all stack frames in code you're controlling, and just hide base/stdlibs/added packages frames. (But they could still be revealed with So it could look like this instead:
Would that be a better balance than just showing the top one? It drops 8 of the 18 lines that are currently output (6 in Base, 2 for Then, if stack frames in the middle are omitted, that can be indicated with vertical dots:
This example cuts number of lines output by 23. |
That looks very nice! I would print an ellipsis every time something is omitted (ie in your example, also before [2]). Also I wonder if it would not be good to print the first and last frame skipped (in your example, [3] and [11]), so the user knows what's going on in between, possibly with a special printing to make it easier to distinguish? Eg something like
(of course here the benchmarktools macro expansion is not detected as belonging to the benchmarktools module...) |
Maybe... My thinking is that most of the time, the problems will be in code someone is actively developing. If they need the intermediate stack frames at all, wouldn't they want to see the whole thing anyway? Here's a more real-world example:
The missing stack frames are:
There's definitely value to [12], but maybe not to [3]? |
It's too bad the stack frame doesn't show the name of the macro being expanded. |
Agreed, but kind of hard to tell in advance. I agree it takes up space and makes it harder to see that it's not user code. So maybe just have the name of the modules involved within the ellipsis? So like
which has the merit of showing why the frames are omitted. Maybe even the function names? In your last example that would be
which is kind of nice?
Yes, the stacktraces still have a lot of rough edges around kwargs, closures and macros unfortunately... |
8dcabb5
to
4cf580a
Compare
The hard thing to get to have reasonable behavior is things like |
I'd be happy to test out edge cases. Let's see if I can construct one that does poorly. (Two issues I found checking this: Really do need to include top-level scope because a top-level macro will show it's own code as the top-level vs. the REPL, and should see the first frame after top-level.)
Those seem fine to me? I don't think the missing frames for
Here it isn't immediately clear why it's complaining about not having an iterate method, but it's clear enough about needing one to be defined on |
@mcabbott suggested this would be easier to demonstrate for people to try out in a separate package, so I'll do that. |
Maybe this feature could be controlled via an option in the |
https://github.com/MichaelHatherly/InteractiveErrors.jl might be of some relevance since it does a similar thing with abbreviating stacktraces. (warning, self-promotion alert 😆) |
That's cool @MichaelHatherly ! Maybe too complex for a default though? :-) |
Thanks, yes too complex as a default, or to be included in base in any way. Take whatever inspiration you want from there. The one thing that I personally find unimportant in stacktraces is the printout of the complete method signature. Maybe that's a controversial view though, does it actually provide anything more useful than just looking at the file and line itself for the typical user? When some highly parameterised signature deep inside the diffeq ecosystem (just to pick a specific example) gives you an error it just gets in the way of more useful info, like source location. Since moving to solely using |
Okay, demonstration code is available in this package: https://github.com/BioTurboNick/AbbreviatedStackTraces.jl I may not register it because it's not really polished.
|
Thanks for making it easier to try it out!
I think this has been discussed before. I believe the main argument against removing it is that types are important to know exactly which method has been called, and so it might be hard to eg debug a stacktrace from a user which does not have this information. If however a mechanism like the one in this PR is merged with a default, simpler stacktrace, and the full one available (with a clear indication to the user how to get it), then it would make a lot of sense to omit type information in the simple stacktrace. |
This form of abbreviation omits certain frames, and is both less and more aggressive than #49102. Was more abbreviation deemed unnecessary? |
This is definitely not completed. |
In 1.10, the The |
Is the plan still to merge this in 1.10? |
No, 1.10 has already had feature freeze. I believe the idea was that more work would start being done here for 1.10, which is currently exemplified by truncated types and some removed frames that are essentially duplicates. I hope more can be done for 1.11. |
cf40dbd
to
1f5ba3b
Compare
19dbb17
to
4161196
Compare
From triage:
|
Summary of PR1. Which frames has the user indicated are important to them, and which are not?We can use already-available information to determine a default: Any modules in code with a root path of By contrast, any modules in code in I can remove this aspect, but it seemed natural that if someone wanted to turn on debug log information using An include/exclude apparatus that could be accessed by a user or package to customize the behavior would be better. VSCodeServer, for instance, would need to mark its frames as for exclusion using these rules; that is fine, the package would know it is doing something nonstandard and exclude itself. One could also imagine a package having a debug mode, which could register itself as to be included when that has been turned on. You could also imagine that Revise could add inclusion rules when e.g. 2. Once we know which frames are the user's, which other frames are informative and should be included, or are included but shouldn't be?
3. Alternatives
|
What does the The example above is certainly shorter and more manageable, but it does seem kind of random to me. I'm not convinced it's better than just limiting the printing to one screen and calling it a day. |
I think the ideal is 3 options-- |
I admit I'm not sure what makes it seem random? In my original proposal, the intent was to show the user frames from code they wrote, and the entry point into code they didn't write. In the screenshot just above, frames 1, 6, and 19 would not be present in this mode. Only top level and With the new Calling out the omitted frames as I'd like to recall that the original motivation for this work was attempting to reconcile 1) the strong opinion among part of the community that hiding frames was very bad and would make it harder to debug user errors, with 2) the strong desire among another part of the community to have shorter traces to make day-to-day work on the REPL less annoying, and so users wouldn't have massive traces to report. If opinions on that have changed to the point that everything in the middle could just be dropped entirely... I'd be surprised but I wouldn't strongly object. I just thought I'd found a nice compromise that gives most people most of what they want most of the time :-). |
Perhaps one could attach those options as fields to the |
I would like to propose something to get this PR (possibly with the discussed tweaks) into Julia for 1.12. This functionality should be:
This way, people could obtain the benefits of having this available natively and automatically, without needing an external package that invalidates Base methods. Because it isn't default, it doesn't invalidate any current documentation or doctests that show stacktraces. The user is in control of enabling it and won't be surprised by a change that then has to be supported. The functionality could be adjusted freely in new versions since it is labeled as experimental. It wouldn't conflict with the more... careful... approach that some of the core devs want to take to shortening stack traces. And it could even be stripped out without much disruption. Would this proposal satisfy everyone? And if it proves itself, later on it could be made default. |
Since I haven't had time to work on any of the alternatives, I won't stand in the way of this. But I'll let others chime in more specifically. |
I'm sorry, I still don't like having such a complex and ugly algorithm for determining which frames are visible. The idea of a hidden stack frame clearly does not have any obvious or canonical definition. I think this code would be very hard to maintain and the complexity is just too much. Nobody should have to update this code if e.g. the implementation of broadcasting changes. I would greatly prefer just truncating it to the screen size. In fact we might want to do what we do for arrays, and omit items from the middle rather than either end, since that will include the line from a script where the error started plus where it ended up. |
This would likely be quite a lot worse than the current behavior, as most often the interesting frames are about 1/4th (which nontrivial operation failed, often coming after several layers of boring indirection) and 3/4th in the stack trace (the general location in the user code, coming before several layers of loading). Cutting the middle part would probably be a great way to demonstrate the need for this PR though, as it would emphasize exactly what this feature aims to cut :) |
Yes, I believe it would. :-)
This is why I asked about putting it in as an "experimental" feature. There may well be ways to simplify the algorithm going forward by identifying formal ways to mark what can be included/excluded. |
I surely don't know how to solve this (technically), but I believe it is important to get something done. New users complain a lot (yes, a lot) about the stack traces. I believe they want something that is targeted to their own code, not base or packages. Some kind of filtering should be possible, or? |
Alright, let me lay this out. I love Julia and want everyone else to love it too. Stack traces are a major problem with Julia usage that turns people off of using it. I put in significant effort in to find a complete solution to the problem. I listened to everyone's needs and implemented something that meets all of them. Unfortunately, solving all those concerns means it is complex, so hearing that complexity is now a barrier is... frustrating. I'm willing to put more effort into it going forward. You have someone who can maintain it right here. And I'm happy to find ways to simplify it for better maintainability if that's really the major objection. I'm also happy to find better solutions for e.g. the broadcast frames than what's present here. And making it experimental and off by default means it could be stripped out at any time. I'm willing to bet users will be so much happier with Julia with this feature available. And it will buy you time to maybe solve it in some perfect way 5 years from now. Please help me help you. |
I haven't seen it mentioned in this thread, but the PR is available as a package here : https://github.com/BioTurboNick/AbbreviatedStackTraces.jl. I've been using it (with the "minimal" setting) for years now, I've extremely rarely needed to look at the complete stack trace, and I bless this package every time I have to look at a default stack trace eg on a student computer. To people who have reservations about the tradeoff (which I understand completely, adding complexity and arbitrariness is never a great solution), try it for a few weeks and see it for yourselves. |
This is just a philosophical difference; I like things to be simple (as opposed to "easy"). This also highlights how it is too bad there is so much stuff in this repo; ideally this could be developed elsewhere as part of e.g. the REPL and I may never even see this PR :) It's not your fault. I'm not holding out for some future more-perfect version of this, because I doubt such a thing exists or is desirable. In fact, if one wants this feature I bet this PR is about as good as it can get.
OK, I guess I owe at least that. I'm trying this branch and I think I see some things that are bugs:
This does not include the location of my REPL input. It also prints
In this case there is no stack trace and no hint about |
I went ahead and implemented a version of my suggestion #40138
Post updated with current state 5/5/22
The philosophy here is that for 95% of purposes, Julia Base, stdlib, and registry packages are black boxes to the user. While it's an important part of Julia that their internals are accessible, they don't need to be shown by default. Plots.jl and BenchmarkTools are excellent example of where cluttered stack traces get in the way and fill the screen:
What does this do?
- Adds a global variableWas added in separate PRerr
that is set in the REPL when an exception is encountered, similar toans
, which is of typeExceptionInfo
, which is just a wrapper around the array, to dispatch to the correctshow
method. Anything hidden in the default view will be shown here.add
ed registry package, except for the first one called into by a visible stack frame, which is the public surface of the other packageAfter this PR, the above example looks like this:
The "Unknown" module comes from inlined stack frames, which are currently missing information. This may be removed with PRs like #41099