03 Logrus Formatter and Output in Golang
This time we will learn about and discuss how a Logger has a format and an output. By default, a Logger always has a predefined format, whether that is JSON, a file, or a custom format of your own.
Formatter in Logrus
When Logrus sends data to the output, the format it uses is determined by a Formatter object. By default, Logrus comes with two formatters:
- TextFormatter, which is the one most commonly used by default.
- JSONFormatter, which is used to format messages into JSON data.
If we want to change the formatter, we can call the function below.
1logger.SetFormatter(/* formatter name */)In our previous testing, when the code was run with the default formatter, the output was written like this.
1time="2024-04-30T16:35:08+07:00" level=info msg="hello world"
2time="2024-04-30T16:35:08+07:00" level=warning msg="hello world warn"
3time="2024-04-30T16:35:08+07:00" level=info msg="log info sending to file application"
4time="2024-04-30T16:35:08+07:00" level=error msg="error test"Now, let’s try changing our Logger’s formatter to JSONFormatter. First, we create a file called logger_formatter.go and write a function with the contents below.
1func TestFormatting(t *testing.T) {
2 logger := logrus.New()
3 logger.SetFormatter(&logrus.JSONFormatter{})
4
5 logger.Info("hello world")
6 logger.Warn("hello world warn")
7 logger.Info("log info sending to file application")
8 logger.Error("error test")
9}When this unit test function is run, it will produce logger output displayed in JSON form, as shown below.
1{"level":"info","msg":"hello world","time":"2024-04-30T16:42:18+07:00"}
2{"level":"warning","msg":"hello world warn","time":"2024-04-30T16:42:18+07:00"}
3{"level":"info","msg":"log info sending to file application","time":"2024-04-30T16:42:18+07:00"}
4{"level":"error","msg":"error test","time":"2024-04-30T16:42:18+07:00"}By using this JSON format, if we want to store the logger data into a JSON-based database such as MongoDB, it becomes much easier to save.
Output from Logrus
The default output of Logrus is the console. So when we look at or run it during initialization without any additional configuration, Logrus will send the logs to our console, or what we often call the terminal.
Sometimes, once we already have a service or application running on a production server, we need to change the destination of the log output, for example to a File or a Database. So, Logrus should be configurable so we can change it to whatever output we want.
If we look at the Logrus library, the output of the Logger is an io.Writer, which means we can actually create our own output by following the io.Writer contract.
The default output of Logrus is os.Stderr, as we can see in the code here.
Now, this time we will try to save the Logrus output into a file. How do we do that? First, we create a file named output_test.go and fill it with the code below.
1func TestOutput(t *testing.T) {
2 logger := logrus.New()
3
4 // initiation create file for logger
5 file, err := os.OpenFile("application.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
6 if err != nil {
7 log.Fatal(err)
8 }
9
10 // set output file into logrus
11 logger.SetOutput(file)
12
13 logger.Info("hello world")
14 logger.Warn("hello world warn")
15 logger.Info("log info sending to file application")
16 logger.Error("error test")
17}In the function above we have an initialization step to create a file, which we commonly do using os.OpenFile. This method needs to be given:
- the name of the file to be created
- a flag, used to ensure the file we modify is allowed to be appended to; if the file does not exist yet it will be created, and for example it can only be written to.
- the access mode, which here is just like Linux permissions where we have the Read (R), Write (W), and Execute (X) attributes.
This access mode uses numbers to set the attributes of a file, and we only type 1 digit per segment. In total there are 3 digits to set the permissions for owner, group, and other. These attributes or permissions are:
- Read (R) has a value of 4
- Write (W) has a value of 2
- Executable (X) has a value of 1
For example, suppose we have access with the code 0755, then the permissions look like this:
| Directory | Owner | Group | Other |
|---|---|---|---|
| - - - | R W X | R - X | R - X |
| 0+0+0 | 4+2+1 | 4+0+1 | 4+0+1 |
| 0 | 7 | 5 | 5 |
| Directory | Owner | Group | Other |
|---|---|---|---|
| - - - | R W - | R W - | R W - |
| 0+0+0 | 4+2+0 | 4+2+0 | 4+2+0 |
| 0 | 6 | 6 | 6 |
So, the meaning of the code 0666 is as follows:
- Owner has read and write access to the file, but cannot execute it.
- Group has access to the file for reading and writing only, and cannot execute it.
- Other has access to the file for reading and writing only, but cannot execute it.
Why do we use the code 0666? Because when the application runs it has access as a group, so we set it like this.
In the next part of the code, we set this as the output of Logrus.
1// set output file into logrus
2logger.SetOutput(file)When we run the function code above, a file named application.log will be created, and it will contain the logs we set as they run.
1time="2024-04-30T16:35:08+07:00" level=info msg="hello world"
2time="2024-04-30T16:35:08+07:00" level=warning msg="hello world warn"
3time="2024-04-30T16:35:08+07:00" level=info msg="log info sending to file application"
4time="2024-04-30T16:35:08+07:00" level=error msg="error test"