-
Notifications
You must be signed in to change notification settings - Fork 138
Added support for rendering errors with causes #233
base: master
Are you sure you want to change the base?
Conversation
Nice! One question is should we add a knob to control backtrace printing? I’d say no: if you want to customize the rendering, just iterate causes yourself. |
@matklad i do want to auto trim the backtraces in the default rendering not to show useless internals but that's about it. |
@mitsuhiko What is your plan for allowing others to control the formatting of error messages? For example, I'd be unlikely to use something like this, since I'd much rather use the (IMO) more standard |
@BurntSushi: just formatting or also what’s in the text? For formatting a builder pattern probably would be useful but also the default should be good. |
I agree the default should be good. I don't think the formatting/text in this PR is not good per se, for what it's worth. It just seems somewhat non-standard to me, at least for command line applications. I'm used to seeing things like this:
That is, the general format here might be fn pretty_error(err: &failure::Error) -> String {
let mut pretty = err.to_string();
let mut prev = err.cause();
while let Some(next) = prev.cause() {
pretty.push_str(": ");
pretty.push_str(&next.to_string());
prev = next;
}
pretty
} (The omission of the backtrace here isn't meant to be important. I like the idea of how you include it in this PR.) Maybe there's room for "display pretty" and "display condensed." Not sure. |
@BurntSushi one thing I noticed is that it's pretty unclear how contexts are used and how errors are wrapped. For instance in one of our sentry projects a common chained error looks like this rendered right now:
As an example for a broken config. These errors are so long and involved that I don't like rendering them in a single lie. But I suppose it depends on how these errors are actually structured. |
@mitsuhiko Yeah I hear ya. I agree that multiple line formatting in cases like that is better. |
@BurntSushi i think it would be interesting to start collecting uses of failure in the wild to see how errors are actually used. For instance for the error above our own code also runs into #189 and we have to do quite a lot of extra handling to get extra info (the path) into the error. There is no pattern for this in failure right now and different libraries come up with different solutions. If we could collect real world uses of failure in small examples it could be really helpful for driving both default formatting decisions as well as helping evolving the API and guide users. |
@mitsuhiko Yeah that's a good idea. I've also struggled with getting file paths into my errors. The only way I've succeeded has been by being very diligent. |
One tiny drawback of the proposed API is that it's unclear, without referring to the docs, what is the difference between |
How about |
sigh, how about |
@matklad I cannot change how |
(Steps up to bat a second time, this time with helmet on): How about naming it |
Oh, also... :) If the |
(Ball 3): Or a_fail.as_display(FailDisplayOpts::LineBreaks) …for self documenting, and to give it some future expansion room. (Oh dear, let this not regress into another exhaustive matching debate.) |
Makes no sense to call it displayln because it does not end with a newline. Also there is absolutely no precedent for that. |
@mitsuhiko Um, but shouldn't it end with a newline if its going to contain new lines? Like for predictable console/log output? (Apologies, I should have made one comment instead of 3 with edits. ) |
There are no APIs that I know about that work that way. |
Probably not the best example, but the first that comes to my mind is the Ruby language, puts(["a", "b", "c"]) # =>
a
b
c Note all lines, including the final "c" are newline terminated in the output. Its also how I immediately interpreted your PR would behave based on the block formatting of your first example. If there is also great examples of not doing it this way, then I stand corrected. See also (Ball 3) alternative naming. :) |
Here is much better example: #[macro_use] extern crate serde_json;
fn main() {
let obj = json!({"foo":1,"bar":2});
println!("{}", serde_json::to_string_pretty(&obj).unwrap());
} Output includes a terminal NEWLINE:
|
Erm... your output is using |
Good point! But this was the first example usage I found of |
Least surprising to me is to not have a terminal newline. The various "pretty-printers" I'm familiar with in Rust do not emit them; nor does, say, Python's |
OK, I stand corrected. |
I'm going to lock this conversation for now because it went into IMO irrelevant bikeshedding territory and I keep getting notifications for this. |
This is the first time I've seen an issue locked on github. Apologies for having made a nuisance of myself here. At the start I was assuming you (@mitsuhiko) were attempting to include this in 0.1.2, and while I think @matklad was correct that the As is, In a custom |
|
Here's an alternative proposal, which I am not sure is better. We specify that That is, I feel like this would be the actually intended use-case for |
Use of "{:#}" ( https://doc.rust-lang.org/std/fmt/struct.Formatter.html#method.alternate Access to the feature via "{:#}", or an optional (not compiler required) public method of any name, are equally in need of a doctest example to be discovered, so I don't see that as much of a downside in itself. Perhaps the bicycle storage(?) issue is more about giving the impression of a default vs a secondary/alternative |
We cannot change the rendering of |
OK, but we could do two things to help the situation, I believe:
pub fn fmt_with_lines(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
//...
}
Then the user still has the option of overriding |
I don't think that's either user friendly or a good idea. What exactly is the issue with a separate display method on the trait? |
Because this:
In the Disclaimer: This is a reply to your direct question and not in any way because I have a bicycle storage problem! I don't intend to comment further. |
Your comment suggests the only issue with |
@mitsuhiko Given your tireless encouragement of my participation, I've prototyped my suggestions, learned some of the limitations, and refined the naming and features in #244. In summary this uses --- a/examples/simple.rs
b/examples/simple.rs
fn main() {
- println!("{}", Fail::display(&bad_function().unwrap_err()));
println!("### default fail(display = \"...\") ###");
if let Err(ref e) = bad_function() {
println!("{}", e);
println!("{:#} (with {{:#}})", e);
}
println!();
println!("### line ({{}}) chain_display ###");
if let Err(ref e) = bad_function() {
println!("{}", chain_display(e));
}
println!();
println!("### block ({{:#}}) chain_display ###");
if let Err(ref e) = bad_function() {
println!("{:#}", chain_display(e));
}
println!(); The examples/simple changes are a bit pedantic and might be better moved to unit or doctests (also lacking for this PR). |
It also made sense to impl
|
When
.display()
is called on an error or fail this renders out thefail and its causes into a human readable string: