Thanks, and my apologies for addressing you as Bryan and not Byron. I completely missed that when I read your question.
Yeah, I’m in full agreement that what’s in the stdlib is about as good as you can get for something that should be in the stdlib. For me everything traces back to wanting to use/support error reporting services with the logging calls, and wanting to have the ability to report error values and not just panics. While I always check for and handle errors, there’s a lot of value for me in being able to report those error values when they are truly unexpected. There are plenty of times I write code and check/handle error values but legitimately don’t expect to encounter them. In those cases, I absolutely want to know they occurred and I really appreciate error reporting services (particularly Honeybadger, which is my current favorite). In those cases, I can now return log.Error(err, "Failed to perform X")
instead of just return err
.
However, once you’re reporting values like that to an external service, you run the risk of blocking your whole app if the service goes down, so that’s why I introduced the asynchronous logging support and guaranteed non-blocking behavior. Using atomic config values was the best way to implement that, and had the side effect of optimizing the leveled logging calls over what similar libraries achieve when they use a global mutex.
As typically happens, once you open pandora’s box, other things follow. It helps to report contextual information and stack frames to the error reporting services, so supporting contextual logging made sense, and making the number of captured stack frames configurable and separate for DEBUG/INFO/WARN levels vs ERROR/FATAL levels made sense, too.
I could have written an error reporting abstraction, but to me it seemed like just another case of logging and it made sense to implement everything as a logging library using a single leveled, contextual API. So out of all of that, Cue was born.
It probably has more features than any one application would use, but at the end of the day, I think the Logger interface itself is pretty simple. The remaining API is designed simply to be flexible. Logging is such an opinionated topic. If you want to follow a purely 12-factor approach and log unbuffered to stdout, then you can use cue.Collect
(unbuffered and synchronous) with a cue/collector.Terminal
. If you’re like me and want error reporting on top of that, you use that same config but with an added cue.CollectAsync
(buffered/non-blocking and asynchronous) with a cue/hosted.<service>
. If you like logging to file, you can do that. If you want to write to syslog, that’s supported too.
I think it’s easy to forget that Go is truly general purpose and not everyone is writing a concurrent web app. Some people are writing local utilities or system daemons to run on end user systems. For those, logging to file or syslog is perfectly reasonable.
Anyway, thanks for taking a look and for providing your thoughts/feedback!
Bob