Golang Log: How to Log in Go

0
1325
Golang Log Example | Log Package In Go

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.

  1. errors
  2. fmt
  3. 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.

The logger writes to standard error and prints the date and time of each logged message which is quite helpful in general case scenarios.

Every log message has an output on a separate line: if the message being printed does not end in the new line, the logger will add one line.

The Fatal functions call os.Exit(1) after writing the log message.

The Panic functions call panic after writing a log message.

How to store the logged messages in files in Go

In the above code, we printed the log message in the console. Let’s store the message in a file because, in a real-time application, all the logs are saved in the file.

See the following code.

// hello.go

package main

import (
	"errors"
	"fmt"
	"log"
	"os"
)

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, exception := division(x, y)
	file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)

	if err != nil {
		log.Fatal(err)
	}

	defer file.Close()

	log.SetOutput(file)
	log.Print(exception)
	fmt.Println(res)
}

Output

➜  hello go run hello.go
0
➜  hello

Also, you will find the info.log created in your directory. Open the file, and you will see something like the following printed.

2019/11/28 19:29:33 can't divide by zero

The os interface in Go is intended to be uniform across all operating systems. Therefore, features not generally available appear in the system-specific package syscall.

If the open fails on file, the error string will be self-explanatory.

What we are doing in the above code is creating one log file and printing our log message inside that file.

After printing the log message, we close the file, and that is it.

GitHub package logrus for formatted logs

The logrus is a logging package designed for structured logging that is well-suited for logging in JSON. The JSON format allows machines to parse your Golang logs quickly.

And since JSON is a well-defined standard, it makes it straightforward to add context by including new fields—a parser should be able to pick them up automatically.

Using the logrus package, you can define standard fields to add to your JSON logs using the function WithFields, as shown below.

You can then make calls to the logger at different levels, such as Info(), Warn(), and Error().

The logrus library will automatically write the log as JSON and insert the standard fields, along with any fields you’ve defined on the fly.

Now, to use the package, first, you need to install it. Type the following command.
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

Golang Log Example

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.

  1. You can make calls to the logger from within your primary application process, not within goroutines.
  2. 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.
  3. You can standardize your logs with a set of predefined messages.
  4. You can send your logs to the central platform to analyze and aggregate them.
  5. 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.

Leave A Reply

Please enter your comment!
Please enter your name here

This site uses Akismet to reduce spam. Learn how your comment data is processed.