|
|
|
|
|
by jrockway
1061 days ago
|
|
I log stacks for every error-level log and have never found it that useful. It's better than just logging "EOF" with no context of course, but manually annotating each frame with information not known to the caller is the way to go. Shifting to Go specifics; stack traces miss things like channel recvs and loops. Consider: for _, datum := range data {
if err := DoSomethingWithDatum(datum); err != nil {
log.Error(...)
}
}
In that case, the stack trace misses the most important thing: which datum failed.Another common case: type Thing struct {
Value any
Err error
}
func Produce() {
ch <- MakeThing()
}
func Consume() {
for _, thing := range ch {
if thing.Err != nil {
log.Error(...)
}
}
}
This one is easier to get right; capture the stack when MakeThing's implementation produces a Thing with err != nil. But, a lot of people just log the stack at log.Error which is basically useless. (Adding to the fun, sometimes Consume() is going to be an RPC to another service written in a different language. But you're still going to want a stack to help debug it.)TL;DR stack traces are better than nothing, but a comprehensive way of handling errors and writing the information you need to fix it to the log is going to be more valuable. It is a lot of work, but I've always found it worthwhile. |
|
OK we agree that the stacktrace isn't _enough_, but it's still a really useful thing to have to understand what exactly happened (and quite often the single most useful thing). Of course we still expect devs to capture the information that led to the `log.Error`, so that we don't have to play guess games.
Rather than manually-annotated logs, I'd prefer getting rid of all logging altogether and use tracing (opentelemetry), which is precisely designed for observability.