The one with loggers in Golang

Photo by Yusuf Onuk on Unsplash

The one with loggers in Golang

A simple guide to write rotating loggers in Golang.

Table of contents

The Whats?

A logger is a simple tool that helps in keeping a record of set of events during a particular period of time. The set of records are hence called the logs and the activity is called logging. The records which we collect over a period of time will be stored in a file which we fondly call a log file.

I prefer dividing my logs into below categories:-

  • DEBUG :- to collect information which can be helpful while debugging a problem.
  • INFO :- to collect generic information about the flow of execution.
  • ERROR :- to collect and inform about the errors occurred in flow of execution.

Funny thing which I learned while using Golang was absence of Warning logs and Warnings in general. The reason is pretty simple,

if it’s worth complaining about, it’s worth fixing in the code.

The other reason being, if go compilers generate warnings, it will pollute the logs hiding the real errors. For the above reasons, I prefer not recording the warnings in my log files.

The Hows?

Golang uses log package to define a type, Logger with methods of formatting output. In our case the logger is built on top of log object hence, we will start with importing the same and defining variables.

package logger
import (
“log”
“fmt”
“os”
“gitHub.com/natefinch/lumberjack”
)
var (
       InfoLogger   *log.Logger
       ErrorLogger *log.Logger
)

We have imported log along with few other packages, fmt will be used to let the user know about the errors, if any while creating the logger. os will be used to open the files. We will talk about the third one in later parts. InfoLogger and ErrorLogger here are two exported variables with a pointer to Logger struct. Next step will be initiating our logger. I prefer to put this inside some sort of function which we can later call from out init function.


func InitLogger() {
    infoLogger, err := os.OpenFile(“path_to_the_log_file.log”, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil {
        fmt.Printf("error opening info log file: %v", err)
        os.Exit(1)
    }
    InfoLogger = log.New(infoLogger, "INFO: ", log.Ldate|log.Ltime|log.LstdFlags | log.Lshortfile)
    InfoLogger.SetOutput(&lumberjack.Logger{
        Filename: path_to_the_log_file.log,
        MaxSize:  100, //megabytes
        MaxBackups: 5,
        MaxAge:30 //days
        Compress: true,
    })

We started with opening/creating the path_to_the_log_file.log file. If we get an error here in the file operation we are printing it and exiting out of the system. We then initialise the defined logger, using log.New function. Here we start with giving the output destination, the INFO prefix to our log statements and the log flags. For more on log constants, we can refer Log Constants


const (
    Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23
    Ltime                         // the time in the local time zone: 01:23:23
    Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.
    Llongfile                     // full file name and line number: /a/b/c/d.go:23
    Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile
    LUTC                          // if Ldate or Ltime is set, use UTC rather than the local time zone
    Lmsgprefix                    // move the "prefix" from the beginning of the line to before the message
    LstdFlags     = Ldate | Ltime // initial values for the standard logger
)

For log rotation, I prefer using this efficient package provided by natefinch called lumberjack. It goes well with standard library’s log package. We can pass it to setOutput function. Here we are passing the file name of our log file. MaxSize Is the maximum size of the log file in megabytes before it is rotated, the default value for this is 100. MaxAge is the maximum number of days before the file is rotated. MaxBackups is the number of backups we would like to keep old files. And Compress is used to gzip the file once rotated. If maxBackups and MaxAge are both set to 0, no old files will be deleted.

Once initialised and imported, we can use our logger through out the files and functions using:-

func main()
{
InfoLogger.Println(“this is an info log”)
}

And we should be seeing something similar to below message in our given log file:-

INFO: 2022/03/27 23:52:17 main.go:10: this is an info log

The prefix INFO comes first, followed by date and time, file name and the line number where the log message was written and the log message. We may see the order being different in few case.

I am still experimenting with multiple logging frameworks and packages to help me write better logs and learn more on this. Open to suggestions and queries.

Cheers, Pratik

Did you find this article valuable?

Support Pratik Anurag by becoming a sponsor. Any amount is appreciated!