Live data from Hacker News

Structured logging with slog

go.dev

161–170 of 174 posts

Re: Structured logging with slog

#161
post #139

Earlier quoted context omitted.

Every error should be annotated at the call site. fmt.Errorf("...: %w", err) isn't litter, it should be a basic expectation of any code which passes code review. > Errors are akin to exceptions in C++/Java: no happy path should rely on errors for control flow (except io.EOF, but that won't generate a call stack). They should be rare enough that any cost below about 1ms and 10k is negligible. This may be true in C++ o…

I think there's less daylight between us than it seems. > Errors are normal, not exceptional. The _handling_ of errors is normal. Code that doesn't consider errors is not production code. And granted, in Go, control flow is driven by errors more often than in C++ or Java. Sentinel error values are common. See for example all usage of error.Is, checking for io.EOF, packages that define ErrSituationA and ErrSituationB,…

> worrying too much about the performance of error handling in these relatively rare cases is absolutely premature optimization.

It's not something to worry about, but it's also a premature optimization to include when there is no need. The Go team considered adding stack traces as described before 1.13, postulating that it would be useful, but measurement determined that they were rarely used in the real world.

If your measurements (you are measuring, right?) that pertain to your specific situation tells a different story, they aren't something to be afraid of, but would be silly to make the default for everyone. The standard tools don't need to serve every single use case ever imagined.

The reality is, unless you forget how to program every time you see the word error (which seems to be a thing), in the real world you are never going to just `return err` up, up, up the stack anyway. Even ignoring traceability concerns, that is going to introduce horrible coupling. You wouldn't do that for any arbitrary type T, so why would you for type error? There is nothing special about errors.

Re: Structured logging with slog

#162
post #139

Earlier quoted context omitted.

Every error should be annotated at the call site. fmt.Errorf("...: %w", err) isn't litter, it should be a basic expectation of any code which passes code review. > Errors are akin to exceptions in C++/Java: no happy path should rely on errors for control flow (except io.EOF, but that won't generate a call stack). They should be rare enough that any cost below about 1ms and 10k is negligible. This may be true in C++ o…

I think there's less daylight between us than it seems. > Errors are normal, not exceptional. The _handling_ of errors is normal. Code that doesn't consider errors is not production code. And granted, in Go, control flow is driven by errors more often than in C++ or Java. Sentinel error values are common. See for example all usage of error.Is, checking for io.EOF, packages that define ErrSituationA and ErrSituationB,…

> But my argument was about errors that can't be dealt with locally, where the origination and ultimate handling are very far apart. A given flow will encounter these errors relatively rarely compared to the happy path (and if it's not rare, you probably need to fix or change something). Having an intuition about this is important for predicting your code's performance.

When code encounters an error, it can either deal with that error programmatically, or return that error to its caller. I don't think you can make any generalized assertions about whether one or the other of these cases is more common, and I'm confident that you can't assert that one or the other of these cases is better or worse than the other, or that one of them represents a problem worth fixing.

Errors potentially occur at every fallible expression. Where an error is handled is orthogonal, and generally unknowable, to the given bit of code that receives that error.

I agree with you that "the performance of error handling" should never be a first-order concern when writing code.

I don't agree with you that capturing a call stack is fast enough to ignore. Calling runtime.Callers (https://pkg.go.dev/runtime#Callers) takes time proportional to the size of the pc []uintptr slice, and can easily get to O(ms) or beyond. It's fine if a given bit of code opts in to this cost, but it's not something that you should do by default; the threshold for performance critical code is O(ns), not O(us).

Re: Structured logging with slog

#163
post #139

Earlier quoted context omitted.

Every error should be annotated at the call site. fmt.Errorf("...: %w", err) isn't litter, it should be a basic expectation of any code which passes code review. > Errors are akin to exceptions in C++/Java: no happy path should rely on errors for control flow (except io.EOF, but that won't generate a call stack). They should be rare enough that any cost below about 1ms and 10k is negligible. This may be true in C++ o…

> Any method or function which is not guaranteed to succeed by the language specification should, generally, return an error. Most Go programmers are too scared to panic and abort when invariants are violated. I think most codebases contain at least 2x as much error handling as is really necessary.

Nope.

Panic isn't an ersatz error reporting mechanism, it's a tool of absolute last resort. Any function or method that can fail should return an error, and should signal failure via that error. Callers that invoke any fallible function or method should always receive, inspect, and respond to the returned error.

Re: Structured logging with slog

#164
post #163

Earlier quoted context omitted.

> Any method or function which is not guaranteed to succeed by the language specification should, generally, return an error. Most Go programmers are too scared to panic and abort when invariants are violated. I think most codebases contain at least 2x as much error handling as is really necessary.

Nope. Panic isn't an ersatz error reporting mechanism, it's a tool of absolute last resort. Any function or method that can fail should return an error, and should signal failure via that error. Callers that invoke any fallible function or method should always receive, inspect, and respond to the returned error.

Who said panic should report errors? I specifically said abort…

Re: Structured logging with slog

#165
post #163

Earlier quoted context omitted.

Nope. Panic isn't an ersatz error reporting mechanism, it's a tool of absolute last resort. Any function or method that can fail should return an error, and should signal failure via that error. Callers that invoke any fallible function or method should always receive, inspect, and respond to the returned error.

Who said panic should report errors? I specifically said abort…

Panic doesn't reliably abort the program.

And, in any case, arbitrary code doesn't have the right to abort the program in the first place! Only func main is allowed to terminate the process. Errors in any other context should always be reported to the caller via normal control flow, i.e. return.

Re: Structured logging with slog

#166

Earlier quoted context omitted.

Are you suggesting it's OK if ParseInt failures take 1ms? Or should ParseInt use a different "kind of error" that's not commensurate with the regular error kind? Do you think most errors look more like ParseInt, or more like sql.Open where 1ms might be acceptable? (Do you think a call stack from the insides of sql.Open would be useful? My experience, mostly not...) So the stacks should probably only be for "complex e…

See my response to a sibling. I wasn't clear; I was implicitly differentiating between these: 1. errors that can be handled locally (such as parsing; in other languages, these situations are often signaled with return values instead of exceptions) 2. errors that can't be handled locally (such as network errors; other languages use exceptions for these) My argument was that worrying too much about error handling perfo…

An error is an error. The immediate caller is always responsible for detecting and handling errors in whatever way is appropriate for their calling context.

Re: Structured logging with slog

#167
post #165

Earlier quoted context omitted.

Who said panic should report errors? I specifically said abort…

Panic doesn't reliably abort the program. And, in any case, arbitrary code doesn't have the right to abort the program in the first place! Only func main is allowed to terminate the process. Errors in any other context should always be reported to the caller via normal control flow, i.e. return.

This is exactly the broken view I mean.

Re: Structured logging with slog

#168
post #165

Earlier quoted context omitted.

Panic doesn't reliably abort the program. And, in any case, arbitrary code doesn't have the right to abort the program in the first place! Only func main is allowed to terminate the process. Errors in any other context should always be reported to the caller via normal control flow, i.e. return.

This is exactly the broken view I mean.

If you allow arbitrary code to terminate the process, then the control flow of the program is effectively non-deterministic, and impossible to model, or even really understand. Software written in this way is fundamentally unreliable.

Re: Structured logging with slog

#169
post #168

Earlier quoted context omitted.

This is exactly the broken view I mean.

If you allow arbitrary code to terminate the process, then the control flow of the program is effectively non-deterministic, and impossible to model, or even really understand. Software written in this way is fundamentally unreliable.

Reasoning about control flow is much easier when you have so much less control flow because you just let it crash.

Re: Structured logging with slog

#170
post #168

Earlier quoted context omitted.

If you allow arbitrary code to terminate the process, then the control flow of the program is effectively non-deterministic, and impossible to model, or even really understand. Software written in this way is fundamentally unreliable.

Reasoning about control flow is much easier when you have so much less control flow because you just let it crash.

What an insane way to think about programming.

A service should never crash in response to a runtime error, or any kind of business logic conditional! Crashes are not normal, they signal critical problems and/or programmer errors. A service that crashes in production is a priority-0 bug that needs to be addressed and fixed immediately.

More specifically, if you can't trust that calling a function will always return execution to the call site, then there is no way to build a practical and deterministic model of control flow, and it becomes impossible to manage resource lifecycles.

Consider a network protocol that expects an explicit disconnect/teardown procedure. If your process creates a connection with this sort of protocol, you need to at least try to tear the connection down before terminating the process outright. You can't just yolo kill the client and expect the server to deal with it. And this is just one example among infinitely many possible examples: basically every API that understands a concept of a resource has similar expectations: filesystems, consensus protocols, etc. etc.

Post reply on HN