-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
proposal: errors: let GODEBUG=errstacktrace request stack backtraces #63358
Comments
The general idea of this resonates with me, but it feels strange to me that this would be a runtime-changeable setting rather than a compile-time-changeable setting. In my application it would mean that setting this environment variable would expose the detail of which end-user-facing error messages are being derived from I tend to bristle a bit each time the Go runtime gains another way to globally change some runtime/library behavior out from under my application, since that adds yet another dimension of variation to consider when someone reports some unexpected behavior, but I suppose I am more sensitive to that than most Go application developers given that I use Go to develop CLI tools for which it should be irrelevant to the user what language they are written in. Probably it's more common to use Go to write server processes that are designed to run only in a particular execution environment and where everybody knows well that the program is written in Go. This is honestly a more broad piece of feedback than just on this proposal, and so I should probably record it somewhere else 🤔 but I would enjoy some way to compile an application such that it does not respond to Since I've already contributed a large number of words to this issue across two comments I'm going to extend this comment to include a response to Ian's later comment about it being an advantage to extend the error string, even though that makes this response appear out of order, just because it's an extension of my existing point above. I consider it to be a disadvantage that it would affect the behavior of existing programs that have not been modified to request or expect stack traces, since some error messages are written for users and turning this on would significantly change those error messages. Even though it's opt-in for a user, there are some applications (such as my own) where the user is not the person that ought to be making this decision, or if it is the user making the decision then it would be better to make it in a way that is more natural for the application. |
Note that the real reason I want this is to report errors to some error reporting services like sentry. Thus the stacktrace must be retrievable via some programatic way, either a public function in This type of functionality is common for long running services were we want to report exceptions / unexpected errors. As a single data point, here's how common it is in our monorepo...
Once we encounter an unexpected error, we have both grpc/http middlewares auto-magically extracting the stack trace and submitting it to sentry. As a success criteria of this change, the previous line count should be drastically reduced. In other words, most callsites should be changed from
To simply
|
The above comment from @fsaintjacques makes me consider a different design, which also happens to be a solution to my earlier feedback. 😄 Building on the design approach for
The default situation is that no errors have backtraces. When The If nothing in the program ever passes an error to In the above I've assumed there's a material cost to capturing a stack trace and thus it would be too expensive to enable by default, but I've not actually measured that. If capturing a stack trace is relatively cheap then it would be nicer to not require the extra environment variable at runtime, but I don't know exactly how the Go runtime tracks stack traces internally. |
As an author of the gitlab.com/tozd/go/errors stack trace attaching errors package, I think having an opt-in stack trace recording of standard errors would be great. But the current proposal has some issues: I do not think it is reasonable or even workable to attach stack trace to Error string:
I think instead:
How exactly is recording of stack traces enabled, using |
The advantage of adding the stack trace to the That said, I agree that it makes sense to add a way to retrieve just the stack trace from the error, if there is one. I also agree that we should arrange that using |
I also agree with @mitar that the ideal signature is |
I would expect that this cost is paid in (hopefully) exceptional case, very rarely will you see an error frequently returned in a hot loop. |
@ianlancetaylor OK, what about then: we do not change |
This proposal is to add stack traces to errors. The Go 1.13 errors proposal was to capture a single stack frame, and allow error wrapping to identify the trace of locations producing the error. This approach reduces the size of error values and can represent a trace that passes across goroutine boundaries, but produces less information in the simplest case. I think any proposal to add stack information to errors needs to consider full stacks vs. frames, and present an argument for which approach is used. |
@mitar Let me restate your suggestion. We keep the @neild This proposal suggests an optional feature that we expect to only be enabled while debugging. As such, I am thinking it should capture a complete stack trace. |
@ianlancetaylor: Sounds good. And for the "so that if it wraps an error it does something intelligent with any stack trace" bit, from my experience: if it wraps more than one error (or no errors), you should record a new stack trace, if it is just one error, you should inherit the stack trace. But good luck reasonably deal with stack traces when joining more than one error. :-) (In my package, I had to resort to indentation when formatting such errors to visually show which stack traces belong where, and there are more than one stack trace involved then.) |
This proposal seems to have a gap where sentinel errors come into the picture. That is, things like: var ErrFoo = errors.New("blah blah blah")
func Bar() error {
return ErrFoo
} For one, that call to It seems like under this proposal, I'd have to instead do: var ErrFoo = errors.New("blah blah blah")
func Bar() error {
return fmt.Errorf("%w", ErrFoo)
} which seems kind of awkward. (Yes I probably want to do that anyway to force the use of |
@rittneje I had this specific gotcha as something that would be required, but I fail (I didn't really put the effort) to see how it could be easily avoided. And the be clear our internal library does something like this:
which means that any sentinel defined errors don't have frames attached. |
Couldn't error stack traces be somehow implemented by the compiler itself? And then each time a function returns a new error (detected as a conversion between concrete type to an error interface, or returning a global variable), the compiler would populate the internal error fields with the stack trace. It would be nice if this would also somehow detect wrapping errors. Not sure how. Don't know whether this is possible in go, but zig has Error return traces, maybe Go can grab some ideas from zig? |
Errors are rarely errors, mostly values, i mean nothing exceptional. It would be overkill to add a trace to all errors, like io.EOF, sql.ErrNoRows, custom errors/values... It's why I like the idea of |
hi To sum up what I understood the objectives are from the conversations:
hopefully I got it more or less right 😅 My opinions:
Possible solutions: As I experimented here https://github.com/golang-exp/errors-poc, configuration can happen at runtime with an
`var ErrFoo = errors.New("blah blah blah") func Bar() error {
Opaque Pop ErrorLevelVar with offsets:
PS:
|
Yea. In gitlab.com/tozd/go/errors I had to introduce Maybe it could differentiate between top-level (or init) call to
That is an interesting approach which otherwise cannot be done. Maybe something which makes sense with the expensive
I think 3rd party libraries can provide tooling around |
I want to emphasize that the goal of this proposal is to permit developers to easily enable a way for errors to include stack traces. In particular, developers can ask users to do this when running on their own systems with their own data. Ideas that rely on rebuilding the program should be a different proposal. Ideas that rely on using a different set of calls, or a different set of packages, to get a stack trace should be a different proposal. Thanks. |
Changing the return value of |
I like @mateusz834 suggested approach with zig Error return traces. From a developer's perspective, I'd prefer to have the option to switch to errors with a full stack trace, even if it comes with a slight performance trade-off, as it enhances code readability. Let's consider the example of the CopyFile function. In doing so, we can observe that adding a stack trace to just one function would require from developers to use func Copy(dst, src string) error {
if dst == src {
return fmt.Errorf("%w", ErrInvalid)
}
srcF, err := Open(src)
if err != nil {
return fmt.Errorf("%w", err)
}
defer srcF.Close()
info, err := srcF.Stat()
if err != nil {
return fmt.Errorf("%w", err)
}
dstF, err := OpenFile(dst, O_RDWR|O_CREATE|O_TRUNC, info.Mode())
if err != nil {
return fmt.Errorf("%w", err)
}
defer dstF.Close()
if _, err := io.Copy(dstF, srcF); err != nil {
return fmt.Errorf("%w", err)
}
return nil
} |
This comment was marked as resolved.
This comment was marked as resolved.
One side idea. Currently, when you call I am writing this here because I was first thinking that Edit: I made the full proposal #63455. |
I want to echo @carlmjohnson's point about the risk with changing the return of A search across GitHub for instances of exact error string matches outside test code: Some popular Go projects from that search: chaosmonkey, GitLab, nomad, istio. |
Re: Zig Error Return Traces, which tracks the code path that an error took to get to the user. Stacktraces are useful in figuring out problems with how an unexpected error happened, but Error Return Traces are useful in figuring out problems in how an expected error was handled (flow control) and surfaced to the end user. Stack traces have a large initial cost, while error return traces scale with each frame through which an error is returned. There’s a library for Error Return Traces in Go: https://github.com/bracesdev/errtrace It seems to me that an ideal hybrid solution would have non-sentinel Similarly, initial wrapping of a sentinel error would attach a StackTrace, but subsequent wrapping would add a single frame reference for a Return Error Trace. |
@StevenACoffman #60873 Was from this same idea ? |
I agree, the full stack trace is really useful only with the first error in the stack. On another note:
Personally my favourite decisions would be:
My favourite questions would be:
|
Adding the environment variable that enable error traces would finally be lead to making as default without environment table.
errors objects are not free, it cause memory allocations and with tracebacks it is more. Might be better create another package in standard libary, like |
I propose |
Errors in Go serve two main purposes: reporting problems to the user of the program, and reporting problems to the developer of the program. The user essentially never wants to see stack traces. For a developer, they can be helpful. Thus it is reasonable for developers to use packages like https://pkg.go.dev/github.com/go-errors/errors to collect developer errors--errors that the user is not expected to see.
That said, while developing a program, there are times when it is helpful for a user error to include a stack trace. For example, this is useful when some program bug is causing an unexpected user error. Getting a stack trace may help speed up debugging. This can also be useful when a user reports an inexplicable user error that is repeatable with proprietary user data; here again a stack trace may help the developer debug the problem.
Therefore, I propose a new
GODEBUG
setting:GODEBUG=errstacktrace=1
. When thisGODEBUG
entry is set in the environment, theerrors.New
andfmt.Errorf
functions will change to incorporate a stack trace into the message. This stack trace will be part of the string returned by theError
method (which will now be a lengthy, multi-line, string).This will permit developers debugging an obscure but repeatable problem to set an environment variable to request a stack trace for the error that they are seeing.
We could also consider providing a simple mechanism for other error generation packages to check whether this
GODEBUG
setting is turned on.This was inspired by a comment by @fsaintjacques : #60873 (comment).
The text was updated successfully, but these errors were encountered: