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