Think Differently About What to Log in Go: Best Practices Examined
Introduction
Go is a young programming language, having just celebrated its sixth birthday on November 10, 2015. Even while continuing to experience the natural growing pains of its youth, Go has already established itself as a solid and increasingly popular tool for creating small, efficient, scalable microservices and numerous other applications. Its logo is an unassuming gopher, an easily recognizable symbol created by Renée French of Plan9 renown1. Go’s choice of a short, common English word for its name makes it ripe for puns and vague online searches, so it is commonly referred to as Golang when specificity is required.
Both learning and departing from the more established programming predecessors such as C, Prolog, and Smalltalk (1972), C++ (1983), Python (~1989), Java (1995), and Ruby (~1998), Go is willing to challenge everything and start from scratch where beneficial. This leads to new features and frustrations alike. There’s a strong sense of community and a clear (and progressive) approach to doing things The Go Way. It is worth wrapping your head around Go’s idiomatic styling and tooling to ensure working code that conforms to established standards. It’s a really well designed language that is proven in the wild, and its patterns are worth understanding in depth. Oh, and there’s the small matter of pedigree: Go is a Google project, a language designed and created to solve problems in their more-than-slightly successful company.
Crafting a useful application includes more than just writing working code: to successfully maintain a working system, we need to give ourselves and our team feedback on what’s happening when our code runs (and when it stops running) in the form of logs.
Go has built-in logging, and there are numerous logging libraries vying for dominance or at least a little love. In this blog post, we’ll discuss what you should be logging in your Go programs and take an introductory look at how one might go about the task.
Where Go Logging Meets Controversy
Coming to Go from another major programming language, developers may be looking for familiar ground in the form of feature parity in a logging library. There have been recent discussions amongst members of the Go community regarding best practices in this area, with arguments predominately being made for lean simplicity over familiarity or traditional features. Because Go is willing to break away from the pack and take bold steps (forward or backward, depending on who you ask), many of the most outspoken proponents of the language embrace and extend its maverick vibe. Don’t let this scare you away! There are clear, accepted standards for how to do things in Go, but it is also a living and breathing tool which you can make your own. Whether or not your solution is a textbook-quality example of idiomatic Go, or even an outright hack, you can achieve your goals with a little research and creativity. Using an existing language feature or library, writing your own, or coaxing your favorite features into existence, you have all the power and flexibility at your disposal with Go.
While this blog post provides a catalyst to the thinking process, nothing beats connecting directly with other developers and comparing notes on how and why we do what we do in solving problems together. Many aspects of such problem solving are conceptual challenges larger than the programming language being used. Don’t hesitate to broaden your reading list and conversations to benefit from the communities, resources and ideas available in other languages as well!
Environment and Configuration
We’re running go version go1.5.1 darwin/amd64 on OSX El Capitan 10.11.1 on a MacBook Air for localhost development and go version go1.5.1 linux/amd64 on Ubuntu 15.10 “Wily Werewolf” on a DigitalOcean Droplet for production. Go is designed to run on Mac, Unix, Linux, Windows, and others and has native and cross-compile functionality.
Golang Logging: Example Code
So, let’s dive into the code! You may download a copy of our example code located on GitHub.com with the `go get` command:
> go get -u github.com/DavidLiedle/loggly-blog-go-logthis |
(The > represents your shell.) Once you’ve run this command, you have a working copy of the GitHub project copied to your local Go working directory. For example, on our development Mac, this is downloaded to ~/go/src/github.com/DavidLiedle/loggly-blog-go-logthis, and it can be opened, edited, and used as any normal git repository from that location.
We can go a step further and `go install` the main script such that it is available to run as a statically linked binary executable on the command line from anywhere on our system:
> go install github.com/DavidLiedle/loggly-blog-go-logthis |
This will create the statically linked executable binary called loggly-blog-go-loggly (yes, it names the executable as the name of the project, regardless of what the filename of the main executable within the project may be; in our case, that file is named logthis.go and it has package main specified in the code). This means that you can simply type `loggly-blog-go-logthis` (without the backticks) on your terminal and, if your Go environment is installed and configured properly and you have your paths set up right, the example code will run! On our system, the executable is placed in ~/go/bin and automatically gains the x permission to execute.
The code demonstrates the usage of Google’s glog package for logging. Go has its own built-in log packageas well: https://golang.org/pkg/log/
What Levels Should You Log in Go?
We log things because we need information: things happen, and we want to know what those things are. Sometimes those are things that we expected to happen. Sometimes they are not. Because different kinds of things happen, we want to be able to categorize them by kind.
Dave Cheney recently wrote an article expressing his views on what should (and should not) be logged: https://dave.cheney.net/2015/11/05/lets-talk-about-logging
He argues that successful logging packages should have fewer logging levels and fewer features in favor of simpler, more effective communication between a developer and the reader of the logs from the future.
On the other hand, simplicity and limited logging levels are already available even in complex logging packages which attempt to mirror features and levels from other languages, because developers can limit the features and levels they implement.
Google’s glog package offers leveled logging: INFO, WARNING, ERROR, FATAL
This doesn’t include DEBUG, one of the two main levels recommended in Mr. Cheney’s post. Google’s glog library has its own built-in level system with the V function, allowing you to customize what gets logged. As you can see in our example code, we set that on the command line as we run our code: `go run logthis.go -logtostderr=true -v=2`
Go Is Exceptionally Different
Go does not have exceptions, which can throw people off. It does have its own way of handling problems that arise in our code. If you want some more context, see why Dave Cheney thinks Go gets exceptions right.
He compares the Go approach to C++, which holds the philosophy of “not-my-problem” in its handling of exceptions. Java added annotations to patch things up a bit, but the solution was still very broad and led to unclear code.
Rather than reaching for the comfortable, familiar throwing of exceptions found in C++, Java, and others, Go encourages the return of an error. Because Go has multi-value returns, you can get back the function return you’re looking for as well as the built-in error type. For example:
f, err := os.Open(“config.json”) if err != nil { log.Fatal(err) } // do something with the open config *File f |
This code snippet shows the use of the built-in Fatal() func of the log package. The Fatal functions call os.Exit(1) after execution, whereas the Panic functions call panic() after they run.
You don’t have to panic when something goes wrong in your code. Go’s pattern of returning and handling errors brings the responsibility back into your hands and avoids the confusion of throwing exceptions altogether.
Once you see log errors coming through, you’ll be alerted to areas of your code that need attention. In our short example above, we might see that the config.json file is missing and decide to add a message to our user instructing them in the configuration of that file.
There’s More to Logging than Levels
Although logging levels hold a large share of the conversation about Go logging, it’s important to keep other issues and other users in mind when designing your logging strategy. For example, you might need traces for debugging or performance metrics to find the bottlenecks that are slowing down your app. You might need to include user IDs for your customer support team, version numbers of split testing, environment names to facilitate log filtering, and more.
Ultimately, you and your team must decide on the data that you find useful to solve problems and track usage in your code. With unique challenges and strengths in the Go ecosystem, it’s worth having a round table discussion with your team to decide how you’ll address all of these considerations.
Getting Involved
As an open source project and community, there are many opportunities to learn, discuss, and be heard. Go’s organizational structure and relatively simple syntax make it easy to “pop the hood” and see how things work, as well as extending functionality and implementing new features and approaches to suit your needs.
You can view, edit, and share Go code snippets right in your browser: https://play.golang.org/p/euc9DDZUgQ
Join the IRC conversation in #go-nuts on freenode, or just view the discussion as an observer.
You may be interested in coming to Denver, Colorado for GopherCon 2016 in July.
Summary
We’ve examined the Go language’s position in the development community and how it fits in history, and discussed some of the surrounding factors which give perspective on Go’s approach to problem-solving. We’ve looked at an example of logging at the info level with Google’s glog package and shown how to use Go’s built-in logging package to log a fatal error which alerts us to give more attention to a config file’s handling in our code. With this perspective, we’ve looked at current and ongoing discussions regarding what we should log in our Go applications, and examined some example code putting it into practice. You’ve got the idea; now Go!
1GoLang Gopher Logo–Creative Commons Attributions 3.0: Renée French
Additional Reading: Logging to Loggly from Go with Logrus and Logrusly »
The Loggly and SolarWinds trademarks, service marks, and logos are the exclusive property of SolarWinds Worldwide, LLC or its affiliates. All other trademarks are the property of their respective owners.
David Liedle