Live data from Hacker News

Structured Errors in Go (2022)

southcla.ws

11–20 of 75 posts

Re: Structured Errors in Go (2022)

#11
post #8

Not a Go engineer but Go-curious - shouldn’t this use slog[0] for structured logging rather than a third party?

The article is from 2022 which predates slog in stdlib. Article also references Wrap() & Wrapf() which are also not part of stdlib (odd they don't mention that imo).

Re: Structured Errors in Go (2022)

#12

These are good general tips applicable to other languages too. I strongly dislike when code returns errors as arbitrary strings rather than classes, as it makes errors extremely difficult to handle; one would presumably want to handle a http 502 diffrernetly to a 404, but if a programmer returns that in a string, I have to do some wonky regex instead of checking the type of error class (or pulling a property from an…

I think what you want are not dedicated classes but error codes.

If you find yourself needing to branch on error classes it may mean error handling is too high up.

ps. personally I always prefer string error codes, ie. "not-found" as opposed to numeric ones ie. 404.

Re: Structured Errors in Go (2022)

#13
post #2

Yeah always thought error handling is a bit wonky in Go. (Un)fortunately, most of my tinkering with Go are just toy level scripts. Thanks for the write up, will check the library!

Go itself is wonky, yet another programming language that is a fine example of worse is better mentality in the industry, whose adoption was helped by having critical infrastructure software written in it.

Re: Structured Errors in Go (2022)

#14

These are good general tips applicable to other languages too. I strongly dislike when code returns errors as arbitrary strings rather than classes, as it makes errors extremely difficult to handle; one would presumably want to handle a http 502 diffrernetly to a 404, but if a programmer returns that in a string, I have to do some wonky regex instead of checking the type of error class (or pulling a property from an…

I think what you want are not dedicated classes but error codes. If you find yourself needing to branch on error classes it may mean error handling is too high up. ps. personally I always prefer string error codes, ie. "not-found" as opposed to numeric ones ie. 404.

Agree on error codes, but I disagree on branching. An api request failing with 429 is retry able (after a period), but a 401 isn’t. A 401 might require a refreshing an authorisation token, but a 400 maybe needs the user to change their input.

> I always prefer string error codes

My parent company provides an API for us to use for “admin-y” things, but they use stringy errors in the response payload of the body. Except they’re in mandarin, and you’d be surprised (or maybe not) at how many tools barf at it. Getting them to localise the error codes is as likely to happen as us fixing the referer heading. The really nice thing about status codes is that there’s only a fixed amount of them so you don’t get two slightly different responses from two different endpoints (not-found vs not_found), and there’s no locale issues involved.

Re: Structured Errors in Go (2022)

#15
post #9

The implementation of WithMeta() is flawed. Not only is it not concurrency-safe, every nested call will be modifying the parent map. The way to do this in a safe and performant manner is to structure the metadata as a tree, with a parent pointing to the previous metadata. You'd probably want to do some pooling and other optimizations to avoid allocating a map every time. Then all the maps can be immutable and therefo…

I think this is the way to bubble up error messages that I like the most. Simple, not needing any additional tools, and very practical (sometimes even better than a stack trace). The idea is to only add information that the caller isn't already aware of . Error messages shouldn't include the function name or any of its arguments, because the caller will include those in its own wrapping of that error. This is done wi…

> This message shows at a quick glance which participant, which database selection, and which integer value where used when the call failed.

And it's completely useless for looking up the errors linked to a participant in an aggregator, which is pretty much the first issue the article talks about, unless you add an entire parsing and extraction layer overtop.

> Much more useful than Stack Traces, which don't show argument values.

No idea how universal it is, but there are at least some languages where you can get full stackframes out of the stacktrace.

That's how pytest can show the locals of the leaf function out of the box in case of traceback:

      def test_a():
  >       a(0)
  
  test.py:11: 
  test.py:2: in a
      b(f)
  test.py:5: in b
      c(f, 5/g)
  
  f = 0, x = 5.0
  
      def c(f, x):
  >       assert f
  E       assert 0
  
  test.py:8: AssertionError
and can do so in every function of the trace if requested:

      def test_a():
  >       a(0)
  
  test.py:11: 
  f = 0
  
      def a(f):
  >       b(f)
  
  test.py:2: 
  
  f = 0, g = 1
  
      def b(f, g=1):
  >       c(f, 5/g)
  
  test.py:5: 
  
  f = 0, x = 5.0
  
      def c(f, x):
  >       assert f
  E       assert 0
  
  test.py:8: AssertionError
So this is just a matter of formatting.

Re: Structured Errors in Go (2022)

#16
post #4

Alright, so this looks pretty comprehensive for error handling. But I gotta ask – for smaller to mid-size projects, is there a point where this level of structure becomes more work than it's worth?

IMO error handling is the sort of thing you really want to get right early on, even in toy projects. It’s very hard to retrofit, and the actual payoff is low until you need it - at which point you definitely don’t want to do the work.

As antithetical as it might be, I tend to just stuff sentry in (no affiliation just a happy user) when I’m setting up the scaffolding, and insert rich context at the edges (in the router, at a DB/serialization/messagebus layer) and the rest usually just works itself out.

Re: Structured Errors in Go (2022)

#17
This is good stuff. After riding a unicorn and seeing systems mature over time as things change and scale grows, I've learned that Structured (Error) Logs are the only way to run systems at scale with sanity.

I wrote my Go version of this same error wrapping utility for the same reasons: https://github.com/sethgrid/kverr

My current work uses python and I am hoping to change us over to structured logs that play well with structured exceptions.

Re: Structured Errors in Go (2022)

#18

These are good general tips applicable to other languages too. I strongly dislike when code returns errors as arbitrary strings rather than classes, as it makes errors extremely difficult to handle; one would presumably want to handle a http 502 diffrernetly to a 404, but if a programmer returns that in a string, I have to do some wonky regex instead of checking the type of error class (or pulling a property from an…

I think what you want are not dedicated classes but error codes. If you find yourself needing to branch on error classes it may mean error handling is too high up. ps. personally I always prefer string error codes, ie. "not-found" as opposed to numeric ones ie. 404.

No, I want dedicated classes. Be they thrown or returned as a value. Error codes are limiting and serve a different purpose.

Error codes contain only the type of error that occurred and cannot contain any more data. With an error class you can provide context - a 400 happened when making a request, which URL was hit? What did the server say? Which fields in our request were incorrect? From a code perspective, if an error happens I want to know as much detail as possible about it, and that simply cannot be summarised by an error code.

If I want to know the type of an error and do different things based on its type, I can think of no better tool to use than my language's type system handling error classes. I could invent ways to switch on error codes (I hope I'm using a language like Rust that would assert that my handling of the enum of errors is exhaustive), but that doesn't seem very well-founded. For example, using error enums, how do I describe that an HTTP_404 is a type of REQUEST_ERROR, but not a type of NETWORK_CONN_ERROR? It's important to know if the problem is with us or the network. I could write some one-off code to do it, or I could use error classes and have my language's typing system handle the polymorphism for me.

Not that error codes are not useful. You can include an error code within an error class. Error codes are useful for presenting to users so they can reference an operator manual or provide it to customer support. Present the user with a small code that describes the exact scenario instead of an incomprehensible stack trace, and they have a better support experience.

Side note: please don't use strings for things that have discrete values that you switch on. Use enums.

Re: Structured Errors in Go (2022)

#19

The `WithMeta` func will panic if not passed a multiple of 2 varargs. This is exactly what makes go error handling difficult in the first place. Imagine panicking in production because you passed a key without value to your error wrapper for some reason.

My solution handled that by inserting default value for the unbalanced key, and a new error key describing the log error

Re: Structured Errors in Go (2022)

#20
There are several existing Go error libraries with a similar approach sans context.

The approach I ended up taking is to use slog attributes. It allows for reuse of existing logging attributes.

This is explained here (skip to the “adding metadata” portion). https://blog.gregweber.info/blog/go-errors-library/

Go package: https://pkg.go.dev/github.com/gregwebs/errors/slogerr

Post reply on HN