In the computing paradigm, a log file is a file that records either events that occur in the operating system or other software runs or messages between the different users of communication software. Logging is an act of keeping a log. In the simplest case, messages are written to a single log file.
Golang Log
To log files in Golang, use the built-in logging library. The package log in Golang implements the simple logging package. It defines a type, Logger, with methods for formatting output.
Golang Log will be helpful in critical scenarios in real-time applications.
It also has the predefined ‘standard’ Logger accessible through helper functions Print[f|ln], Fatal[f|ln], and Panic[f|ln], which are easier to use than creating the Logger manually.
Golang gives you a wealth of options when choosing a logging package, and we’ll explore several of these below.
Large corporations that depend on distributed systems often write their applications in Go to take advantage of the concurrency features like channels and goroutines (e.g., Heroku, Basecamp).
If you are responsible for building or supporting the Go applications, a well-considered logging strategy can help you to understand user behavior, localize errors, and monitor the performance of your applications.
How to use log in Golang
Golang has a built-in logging library called log, which comes with the default logger that writes to standard error and adds the timestamp without the need for configuration.
You can use these rough-and-ready logs for local development in which you need to get fast feedback from your code may be more important than generating rich, structured logs.
To generate rich and structured logs, third-party packages will be more helpful.
For example, you can define the division function that returns the error to the caller rather than exiting the program when you attempt to divide by zero.
See the following code.
// hello.go package main import ( "errors" "fmt" "log" ) func division(x float32, y float32) (float32, error) { if y == 0 { return 0, errors.New("can't divide by zero") } return x / y, nil } func main() { var x float32 = 11 var y float32 res, err := division(x, y) if err != nil { log.Print(err) } fmt.Println(res) }
Output
➜ hello go run hello.go 2019/11/28 18:50:19 can't divide by zero 0 ➜ hello
In the above program, we have imported three packages.
- errors
- fmt
- log
Then we have defined a function division(), which accepts two parameters.
We are checking the dividing by 0 error; if it occurs, we log that error in the console.
If we meet the divide by 0 conditions, one error will be generated, and then we log that error in the console and print the return value.
In the above program, we got the divide by 0 conditions; that is why we got the output log.
go get github.com/Sirupsen/logrus
Now, you can import the package into your file. See the following code.
// hello.go package main import ( log "github.com/sirupsen/logrus" ) func main() { log.WithFields(log.Fields{ "Best Song": "Sunflower", }).Info("One of the best song") }
Output
Note that it’s utterly api-compatible with the stdlib logger so that you can replace your log imports everywhere with log “github.com/sirupsen/logrus“, and you’ll now have the flexibility of Logrus. You can customize it all you want:
// hello.go package main import ( "os" log "github.com/sirupsen/logrus" ) func init() { // Log as JSON instead of the default ASCII formatter. log.SetFormatter(&log.JSONFormatter{}) // Output to stdout instead of the default stderr // Can be any io.Writer, see below for File example log.SetOutput(os.Stdout) // Only log the warning severity or above. log.SetLevel(log.WarnLevel) } func main() { log.WithFields(log.Fields{ "animal": "walrus", "size": 10, }).Info("A group of walrus emerges from the ocean") log.WithFields(log.Fields{ "omg": true, "number": 122, }).Warn("The group's number increased tremendously!") log.WithFields(log.Fields{ "omg": true, "number": 100, }).Fatal("The ice breaks!") // A common pattern is to re-use fields between logging statements by re-using // the logrus.Entry returned from WithFields() contextLogger := log.WithFields(log.Fields{ "common": "this is a common field", "other": "I also should be logged always", }) contextLogger.Info("I'll be logged with common and other field") contextLogger.Info("Me too") }
Output
➜ hello go run hello.go {"level":"warning","msg":"The group's number increased tremendously!","number":122,"omg":true,"time":"2019-11-28T19:44:30+05:30"} {"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,"time":"2019-11-28T19:44:30+05:30"} exit status 1 ➜ hello
Best practices for writing and storing Golang logs
The first thing while writing a log is to find a perfect library. Then after you have chosen the logging library, you’ll also want to plan for where in your code to make calls to the logger, how to store your logs, how to make them available at any given time, and how to analyze them.
Let’s see some of the best practices.
- You can make calls to the logger from within your primary application process, not within goroutines.
- It is considered good practice to write logs from your application to a local file, even if you will ship them to a central platform later.
- You can standardize your logs with a set of predefined messages.
- You can send your logs to the central platform to analyze and aggregate them.
- Use the HTTP headers and unique IDs to log user behavior across microservices.
Conclusion
This post has gone through the benefits and tradeoffs of several Go logging libraries.
We have also recommended ways to ensure that your logs are available and accessible when you need them and the information they contain is handy, consistent, and easy to analyze.
That’s it.