-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Nicer output when an exception bubbles through main #4968
Nicer output when an exception bubbles through main #4968
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd love an env var or other option to get a "low-level" raw stacktrace which shows pretty much the same as what existed before this PR, minus the @@skip
check. It'd be useful for debugging on occations.
This is much more necessary given how often the debug info is blatantly wrong (see #4538 for the true scale of this). The symbol info is pretty much impossible to be wrong.
src/callstack.cr
Outdated
next if @@skip.includes?(file) | ||
|
||
# Turn to relative to the current dir, if possible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably would get confusing if the executable changes it's directory mid-program. We could have multiple stacktraces from the same program have different representations for the same file. I think it'd be best to record Dir.current
very early in the init.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have ˋProcess::INITIAL_PWDˋ in process/executable_path.cr maybe we can use that? (As a constant its set very early)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice find! I think we can actually expose that constant as it might be useful for others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used it, but I'm not sure now we should expose it. Or maybe find another way/name and document it. So out of the scope of this PR.
src/callstack.cr
Outdated
|
||
# Turn to relative to the current dir, if possible | ||
file = file.lchop(current_dir) | ||
|
||
file_line_column = "#{file} #{line}:#{column}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to have "#{file}:#{line}:#{column}"
because my text editor understands when you tell it to open foo.cr:7:4
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I thought about changing that to be like that but thought it would be harder on the eyes, and easier to copy just the filename. But yes, if it's easier to open than on an editor, let's change it (Ruby also uses that format)
Some editors understand this format so it's easier to jump to that location.
@RX14 Maybe we can keep it all the time. I was thinking maybe like this:
What do you think? |
src/callstack.cr
Outdated
if function.starts_with?("*raise<") || | ||
function.starts_with?("*CallStack::") || | ||
function.starts_with?("*CallStack#") || | ||
function == "main" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I have a custom method called main
it would get hidden from the stacktrace? Just rename foo
to main
in your example to see what I mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. I'll just keep those entries then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it because there's only one "main", I don't know why when there's debug info Foo#main
just appears as main
. But well, it's not a big deal not removing those entries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't you just remove the last entry? This would require to count the size, but I don't think this should be a big concern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or simply break from the loop after __crystal_main
is processed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because en release some traces might not be there. I think it's simpler to just keep things as they are now regarding this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regardless if it might not show up in some scenarios, anything after __crystal_main
shouldn't be of interest so I think it should be possible to break when this function is processed.
Though it's not a big deal having a line more at the end.
@asterite I've never actually needed to use the function hex locations, they're practically random with ASLR anyway. What I have done before is to use |
@RX14 Sounds good. The main issue now is deciding how to trigger this behaviour. We can either use an ENV var, configure something in CallStack itself, or use a compile-time flag. Also, what do you think about removing the |
@RX14 Actually, I think we can show the IP address when there's no file/line/column information. That happens when you compile with |
Wouldn't "symbol location" be a much less confusing name than "ip address"... Regardless, my entire point with the request was to request a way to switch between showing debug info and plain symbols without recompiling. Sorry if I didn't make it clear. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggested a few minor tweaks, otherwise: good job! That really cleans things up.
The symbol address should always be left out as they don't help much the ordinary developer (that includes me), even when the debug info couldn't be found. It's not uncommon (at least on Linux) to have some lines be found in the DWARF sections, while some aren't... we'd have a weirdly mixed backtrace output when that happens for example.
Now, as @RX14 suggested, maybe we could have a flag to disable the pretty output to get the raw backtrace. Maybe just react --no-debug
which can be found out with flag?(:debug)
.
src/callstack.cr
Outdated
|
||
# Crystal methods (their mangled name) start with `*`, so | ||
# we remove that to have less clutter in the output. | ||
function = function.lchop('*') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the unmangling of the function name could happen in the elseif
above (line 168-170), when we failed to decode the function name from DWARF and got the mangled symbol instead.
spec/std/callstack_spec.cr
Outdated
# CallStack tries to make files relative to the current dir, | ||
# so we do the same for tests | ||
current_dir = Dir.current | ||
current_dir += '/' unless current_dir.ends_with?('/') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use File::SEPARATOR
for future-proofing on Windows?
src/callstack.cr
Outdated
@@ -23,6 +23,10 @@ end | |||
|
|||
# :nodoc: | |||
struct CallStack | |||
# Compute current directory at the beginning so filenames | |||
# are always shown relative to the *starting* working directory. | |||
CURRENT_DIR = Process::INITIAL_PWD + '/' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here: File::SEPARATOR
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We really should have a function to get a path relative to another path
https://docs.python.org/3/library/os.path.html#os.path.relpath
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oprypin I agree, though I don't know if I'd like ../../
to appear in the output. I checked Go for example and they always use absolute paths (though Ruby uses relative paths if the path is in the current directory)
@ysbaddaden @RX14 I applied your suggestions. I also made it so that if the env variable Going back to the original example, we have: Without dsymutil:
With dsymutil:
Without dsymutil, with CRYSTAL_CALLSTACK_FULL_INFO:
With dsymutil, with CRYSTAL_CALLSTACK_FULL_INFO:
|
@asterite I used |
@RX14 Updated! Now without dsymutil when full info is requested:
With dsymutil when full info is requested:
That way you can see the real symbol name plus the address. Is it good now? :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation looks good! Would be nice to use --no-debug
and setting ENV["CRYSTAL_CALLSTACK_FULL_INFO"] = "1"
to spec the output in all possible cases though.
I have only a small nitpick-kinda-question actually. 😁 Why 8 spaces of indentation? Been using some shards with some nested path structure and ended with really long lines in the backtrace. Don't see it as a real problem, but always wondered about it and perhaps avoid wrapping at 120 columns like happens under some scenarios. Anyway, thank you for the contribution! ❤️ ❤️ ❤️ |
@luislavena I copied Ruby here. I find that those 8 spaces immediately signals an exception was thrown, but only because I'm used to that. I have no problem changing that to 2 spaces, if @RX14 agrees. I'll also add specs for |
I have no problems with the 8 spaces either, was wondering if there was a particular reason that I was missing. Thanks for the details. And thank you for the improvements! |
I changed it to use 2 spaces instead of 8. I think that's better because it gives more spaces to show the trace. Thanks for the suggestion, @luislavena ! |
@asterite what about the spec changes? |
I'd like |
An update to this issue - would it be better to add colours in the form of ANSI colour formatting to these errors? I personally think it's a little bit of effort that goes a long way to making errors more readable, and I'm willing to do it - just not sure if everyone supports this idea. |
CC @asterite ^ |
When an exception happens in a program and isn't catched, what's printed is a bit hard to digest. For example, for this program:
The output is:
That's at least on Mac without running
dsymutil
. On linux, or on Mac when runningdsymutil
, this is the output:That's a bit better: we can see
raise
andCallStack
are removed from the trace.So I see a few things to improve:
gdb
orlldb
)dsymutil
on Mac,CallStack
andraise
are still there, and also all names have an annoying*
prefix (this is part of the function mangling in Crystal)dsymutil
on Mac, filenames are shown as "??" which clutters the outputmain
is always in the trace but adds little info because it's always the same and adds no infoThis PR improves all of that:
CallStack
andraise
are always removed from the trace__crystal_main
but we show it asmain
, because that will have the line number in the "main" file, and we remove the actualmain
from the trace.I also formatted the output a bit more like how Ruby shows it.
The result, without running
dsymutil
on Mac, is:With
dsymutil
, or in linux, the output is:I think it's a bit better than before.
As a comparison, this code in Ruby:
Gives this output:
I'll send a separate PR to always try to execute
dsymutil
on Mac so we always get files and line numbers (tracked in #4186).We should also investigate why line numbers are incorrect, but probably in a different issue.