As you've discovered, Go has nothing comparable to thread-local
storage. This is because in Go it's easy to start a new goroutine.
Should that new goroutine share the thread-local storage or get new
copies? Neither choice is always correct, depending on how the
goroutine is being used. Instead, Go code uses function closures or
explicit value passing, as appropriate.
In order to translate code from a language that does use thread-local
storage, you are going to have to use a context value, as you mention.
That's the best and really the only approach. Yes, it will wind up
everywhere.
Ian