Cue, a contextual logger with "batteries included"

Hi Gophers!

I just released Cue, a contextual logger with “batteries included”:

A couple of the key features:

  • Supports logging to file, syslog, structured syslog, Loggly, stdout/stderr, and sockets.
  • Supports logging errors/panics to Rollbar, Sentry, Opbeat, and Honeybadger. I’m happy to implement other service integrations as well. Just open feature requests.
  • Supports both synchronous and asynchronous logging, defined on a per-Collector basis. The asynchronous mode is guaranteed non-blocking.
  • Log collection is completely “opt-in”. Log calls that don’t match a registered collector threshold are very cheap/fast – about 16ns/call on a 2015 MacBook Pro. The fast path is handled via atomic operations.
  • Supports dynamic threshold changes on a per-Collector basis. This means you may temporarily enable debug logging to troubleshoot a live issue and disable it again shortly thereafter, without needing to restart your application.
  • Has thorough test coverage.

Take a look and let me know what you think! Right now the API is mostly set, but I’m open to minor changes prior to the 1.0 release. After 1.0, I’ll be following a similar API promise to the Go standard library.




Please make a properlink to the page using the appropriate markdown directive. It will help us better reach your library.

Sure, I just updated the post. I tried to do that when I first posted and the post was rejected with “Cannot link to that host”, but now it appears it is accepted.

1 Like

Thanks, great work!

Thanks! I just added some additional examples and updated the to demonstrate those examples as well. It just occurred to me that I didn’t have any good, concrete end-to-end examples of using the library. Anyway, just an FYI in case you read any of the docs prior to now.

1 Like

Nice job, the introduction and documentation are very well written. Other than the joy of creating something what was the motivation for this log package versus other ones available?

Hi Bryan,

Thank you very much for asking the question. You highlighted a key deficiency in my documentation. I did a terrible job explaining why I wrote cue.

To that point, I’ve added two new sections to the Key Features and Motivation.

Take a look and feel free to ask any follow-up questions. The short answer is that I examined the existing logging libraries closely and couldn’t find one that met my needs. Unfortunately the changes I would have made to the existing libraries would have broken their API, so I ended up writing cue.


Thanks for following up, this information is great. There have been many discussion over logging in the Go community specifically with whether the builtin log is enough. I think it is in terms of what is necessary, however in practice having options to add structured data and delegate where it goes is required. You’ve taken a very pragmatic, but practical approach with designing this library.

Thanks, and my apologies for addressing you as Bryan and not Byron. I completely missed that when I read your question. :smile:

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!



This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.