Golang Logrus Complete Tutorial with Examples


Written By - Antony Shikubu
Advertisement

When developing enterprise software or any other software for that matter, it is always good practice to have a structured logging framework. Logrus is a structured logger for Go and is compatible with the standard log package. The standard logging package is limited in some ways and that led to development of other logging packages like Logrus, oklog and zerolog. Logging is an importantĀ  aspect in modern programming and are used to :

  1. Log Errors or Warning messages that developers need to know about.
  2. Log performance metrics , memory consumption etc
  3. Log Debug messages to help developers troubleshoot their software.

Logging is a way of letting our software communicate to us what is going on. For that reason , not everything should be logged as that will be a waste of time and resources. Developers need to only log messages that are meaningful. These logs messages can be sent to the stdout, or files like log.txt file.

To follow along with this tutorial , please ensure you have basic Go skills and you have Go language installed on your computer.

 

Installing logrus

Logrus is not a standard package that comes with Go. It has to be added into your package manually . To get started , navigate to your working directory and use the below commands to create a module and install Logrus package.

$ go get github.com/sirupsen/logrus
go get: added github.com/sirupsen/logrus v1.9.0
go get: added golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8go get github.com/sirupsen.logrus

Now you can directly start using logrus in your package file:

package main

import (
	"github.com/sirupsen/logrus"
)

func main() {
   // Your Code
}

 

Getting started with Logrus

Now that we have successfully installed the logrus package, let us now learn how we can use logrus in the initial stages.Logrus has different methods just like theĀ  fmt package used to log messages to the terminal or any other output. These methods are Println(), Printf() , Fatal(), Fatalf() and many more.

Example

package main
 
package main
 
import (
   "os"
 
   "github.com/sirupsen/logrus"
)
 
func main() {
   logrus.Println("Hello, world!")
}

Explanation

Advertisement

In the above example, we are importing the logrus package and assigning it the alias name log. In the main function we use the log alias name to access the Println() methods that come with the logrus package.

Output

$ go run main.go
INFO[0000] Hello, world!

 

Choosing different log levels in logrus

Logrus offers different log levels e.g panic, fatal, error, warning, info, debug and trace. A log level is a way of determining the severity/priority of any type of event. These levels are ordered from the lowest to the highest as shown below.

  • Trace
  • Debug
  • Info
  • Warn
  • Error
  • Fatal
  • Panic

You can set the log level using:

logrus.SetLevel(logrus.TraceLevel)
logrus.SetLevel(logrus.DebugLevel)
logrus.SetLevel(logrus.InfoLevel)
logrus.SetLevel(logrus.WarnLevel)
logrus.SetLevel(logrus.ErrorLevel)
logrus.SetLevel(logrus.FatalLevel)

Here we have set the log level as TraceLevel so we will log everything. For Example:

package main

import (
	"github.com/sirupsen/logrus"
)

func main() {
	logrus.SetLevel(logrus.TraceLevel)
	logrus.Traceln("Trace Level")
	logrus.Debugln("Debug Level")
	logrus.Infoln("Info Level")
	logrus.Warningln("Warning Level")
	logrus.Errorln("Error Level")
	logrus.Fatalln("Fatal Level")
	logrus.Panicln("Panic Level")
}

Since we have set the log level as TraceLevel so all levels of logs are getting logged. Output:

$ go run main.go 
TRAC[0000] Trace Level 
DEBU[0000] Debug Level 
INFO[0000] Info Level 
WARN[0000] Warning Level 
ERRO[0000] Error Level 
FATA[0000] Fatal Level 
exit status 1

 

Now let us set the log level to DebugLevel:

package main

import (
	"github.com/sirupsen/logrus"
)

func main() {
	logrus.SetLevel(logrus.DebugLevel)
	logrus.Traceln("Trace Level")
	logrus.Debugln("Debug Level")
	logrus.Infoln("Info Level")
	logrus.Warningln("Warning Level")
	logrus.Errorln("Error Level")
	logrus.Fatalln("Fatal Level")
	logrus.Panicln("Panic Level")
}

As expected, trace level messages are not shown anymore:

$ go run main.go 
DEBU[0000] Debug Level 
INFO[0000] Info Level 
WARN[0000] Warning Level 
ERRO[0000] Error Level 
FATA[0000] Fatal Level 
exit status 1

 

logrus.SetOutput to stdOut, stderr and/or log file

logrus supports different options to print and log messages. We can

  1. Only print messages on console
  2. Only log messages to log file
  3. Print on console and log messages to log file

 

Print messages on console (stdout and stderr)

Use SetOutput to os.Stdout or os.Stderr to print messages on console:

package main

import (
	"os"

	log "github.com/sirupsen/logrus"
)

func main() {
	// Output to stdout instead of the default stderr
	log.SetOutput(os.Stdout)

	// Only log the debug severity or above
	log.SetLevel(log.DebugLevel)

	log.Info("Info message")
	log.Warn("Warn message")
	log.Error("Error message")
	log.Fatal("Fatal message")
}

 

Log messages in log file

To log messages into log file use SetOutput with the filename using logrus.SetOutput(file):

package main

import (
	"fmt"
	"os"

	log "github.com/sirupsen/logrus"
)

func main() {
	logFile := "log.txt"
	f, err := os.OpenFile(logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("Failed to create logfile" + logFile)
		panic(err)
	}
	defer f.Close()
	// Output to stdout instead of the default stderr
	log.SetOutput(f)

	// Only log the debug severity or above
	log.SetLevel(log.DebugLevel)

	log.Info("Info message")
	log.Warn("Warn message")
	log.Error("Error message")
	log.Fatal("Fatal message")
}

 

Print on console and also in log file

package main

import (
	"fmt"
	"io"
	"os"

	"github.com/sirupsen/logrus"
	easy "github.com/t-tomalak/logrus-easy-formatter"
)

func main() {
	f, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("Failed to create logfile" + "log.txt")
		panic(err)
	}
	defer f.Close()

	log := &logrus.Logger{
                // Log into f file handler and on os.Stdout
		Out:   io.MultiWriter(f, os.Stdout),
		Level: logrus.DebugLevel,
		Formatter: &easy.Formatter{
			TimestampFormat: "2006-01-02 15:04:05",
			LogFormat:       "[%lvl%]: %time% - %msg%\n",
		},
	}

	log.Trace("Trace message")
	log.Info("Info message")
	log.Warn("Warn message")
	log.Error("Error message")
	log.Fatal("Fatal message")
}

Output:

# go run main.go 
[INFO]: 2022-08-25 11:27:06 - Info message
[WARNING]: 2022-08-25 11:27:06 - Warn message
[ERROR]: 2022-08-25 11:27:06 - Error message
[FATAL]: 2022-08-25 11:27:06 - Fatal message
exit status 1

Content of log.txt file

[INFO]: 2022-08-25 11:27:06 - Info message
[WARNING]: 2022-08-25 11:27:06 - Warn message
[ERROR]: 2022-08-25 11:27:06 - Error message
[FATAL]: 2022-08-25 11:27:06 - Fatal message

 

Alternatively you can just set multi writer using SetOutput:

multi := io.MultiWriter(logFile, os.Stdout)
logrus.SetOutput(multi)

 

logrus show line number

To print the line number of the entries in the code which is printing or logging the messages we can use SetReportCaller:

package main

import (
	"os"

	log "github.com/sirupsen/logrus"
)

func main() {

	// Output to stdout instead of the default stderr
	log.SetOutput(os.Stdout)

	// Only log the debug severity or above
	log.SetLevel(log.DebugLevel)

        // logrus show line number
	log.SetReportCaller(true)

	log.Info("Info message")
	log.Warn("Warn message")
	log.Error("Error message")
	log.Fatal("Fatal message")
}

Output:

$ go run main.go 
INFO[0000]/opt/deepak/scripts/goexamples/global-vars/main.go:18 main.main() Info message 
WARN[0000]/opt/deepak/scripts/goexamples/global-vars/main.go:19 main.main() Warn message 
ERRO[0000]/opt/deepak/scripts/goexamples/global-vars/main.go:20 main.main() Error message 
FATA[0000]/opt/deepak/scripts/goexamples/global-vars/main.go:21 main.main() Fatal message 
exit status 1

 

Using hooks in logrus

In Logrus, hooks are essentially interfaces that can be added to the logger to execute custom logic or side effects when log entries are made. A hook in Logrus only has to satisfy the Hook interface, which involves implementing two methods: Levels() and Fire().

  • Levels(): Defines for which log levels this hook will be triggered.
  • Fire(): Contains the logic that gets executed when a log of the specified levels is made.

Define the Hook:

First, let's design a hook that writes ErrorLevel log entries to a file named error.log.

package main

import (
	"fmt"
	"os"
	"github.com/sirupsen/logrus"
)

type FileHook struct {
	File *os.File
}

func (hook *FileHook) Levels() []logrus.Level {
	return []logrus.Level{logrus.ErrorLevel}
}

func (hook *FileHook) Fire(entry *logrus.Entry) error {
	msg := fmt.Sprintf("%s: %s\n", entry.Time.Format("2006-01-02 15:04:05"), entry.Message)
	_, err := hook.File.WriteString(msg)
	return err
}

Using the Hook in Your Application:

Now, we can integrate this hook with our Logrus logger:

func main() {
	logger := logrus.New()

	// Open a file for appending logs
	file, err := os.OpenFile("error.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		logger.Fatalf("Failed to open log file: %v", err)
	}
	defer file.Close()

	hook := &FileHook{File: file}
	logger.AddHook(hook)

	// This error will be written to the error.log file!
	logger.Error("This is a critical error!")
}

Explanation:

  • The FileHook type is defined to satisfy the logrus.Hook interface.
  • The Levels method specifies that it only cares about error logs.
  • The Fire method writes the error log entry to a local file with a timestamp.
  • In the main function, we instantiate the logger, create/open the error.log file, add our custom file hook, and then make an error log.
# go run main.go 
ERRO[0000] This is a critical error! 

# cat error.log 
2023-08-20 14:33:43: This is a critical error!

 

Format logrus messages

Logrus has two main built-in formatters namely logrus.TextFormatter and logrus.JSONFormatter. They are used to format the logs coming from the logger using an object formatter. By default the the formatter in logrus will set logrus.TextFormatter.The logrus.TextFormatter can be used to log events in colors if the stdout is a tty. To be able to switch between these two formatters, logrus offers a method called logger.SetFormatter(), that takes the format type as the input. In the following examples we will use logrus.TextFormatter as the object formatter.

 

logrus.TextFormatter

logrus.TextFormatter provides logs in the form of key-value pairs. It is designed for console output where developers can view logs in real-time or when logs are directed to a plain-text file. The format ensures that the logs are easily discernible and structured enough for visual understanding.

The key features include:

  1. Timestamps: By default, each log entry starts with a timestamp indicating when the log was generated.
  2. Log Levels: Indicates the severity of the log, like info, warn, error, etc.
  3. Log Message: The main message of the log.
  4. Fields: Any additional fields are displayed in key-value pairs, offering context or more data regarding the log.
  5. Color-Coding (Optional): When outputting to a terminal, TextFormatter can use colors to differentiate log levels, making it easier to spot warnings, errors, or other log levels.
  6. Configuration Options: Provides several customization features such as:
    • Disabling timestamps.
    • Choosing custom timestamp formats.
    • Enabling/Disabling color coding.

When initializing Logrus without setting any formatter, it uses TextFormatter as default:

import (
    "github.com/sirupsen/logrus"
)

func main() {
    logrus.WithField("example", "textFormatter").Info("Hello from Logrus!")
}

The log output might look something like this:

time="2021-08-20T14:23:12Z" level=info msg="Hello from Logrus!" example=textFormatter

Customizing Field Delimiters

You can customize the delimiters used between different components of the log entry, such as timestamp, level, message, and fields.

logrus.SetFormatter(&logrus.TextFormatter{
    DisableTimestamp:       false,
    TimestampFormat:        "2006-01-02 15:04:05",
    DisableColors:          false,
    QuoteEmptyFields:       true,
    ForceFormatting:        false,
    DisableLevelTruncation: true,
    PadLevelText:           true,
    FullTimestamp:          false,
    // Customizing delimiters
    FieldMap: logrus.FieldMap{
        logrus.FieldKeyTime: "@timestamp",
        logrus.FieldKeyLevel: "severity",
        logrus.FieldKeyMsg:   "message",
        logrus.FieldKeyFunc:  "caller",
    },
})

 

logrus.JSONFormatter

logrus.JSONFormatter outputs log entries in a structured JSON format. JSON (JavaScript Object Notation) is a lightweight data-interchange format that is both human-readable and machine-parsable. This formatter is ideal when you need to aggregate logs, analyze them using log analysis tools, or when you need to integrate logs into other systems.

The key features include:

  1. Structured Data: Logs are structured in key-value pairs, making them machine-friendly and easily searchable.
  2. Compact Format: JSON format is concise and efficient, which is beneficial when dealing with large volumes of logs.
  3. Interoperability: JSON is a widely accepted standard for data exchange, ensuring compatibility with various tools and systems.
  4. Human-Readable (to an extent): While JSON is machine-readable, some log management tools offer pretty-printing to make it more human-readable when needed.

To use the logrus.JSONFormatter, you simply set it as the formatter for your Logrus instance:

import (
    "github.com/sirupsen/logrus"
)

func main() {
    logrus.SetFormatter(&logrus.JSONFormatter{})
    logrus.Info("This is a JSON-formatted log")
}

The log output might look something like this:

{"time":"2021-08-20T14:23:12Z","level":"info","msg":"This is a JSON-formatted log"}

The logrus.JSONFormatter provides a few customization options to tailor the JSON-formatted log outputs according to your preferences and requirements. Here are the different possible customizations:

package main

import (
	"github.com/sirupsen/logrus"
)

func main() {
	// Create a JSON formatter instance with the valid customizations
	jsonFormatter := &logrus.JSONFormatter{
		PrettyPrint:    true,
		TimestampFormat: "2006-01-02 15:04:05",
	}

	// Set the JSON formatter instance as the formatter for the logger
	logrus.SetFormatter(jsonFormatter)

	// Now, log something
	logrus.WithFields(logrus.Fields{
		"user":   "john_doe",
		"action": "login",
	}).Info("User logged in")
}

 

Custom Formatter

Logrus is a flexible and efficient logging library in Go, and one of its powerful features is its ability to have custom formatters. A formatter in logrus is responsible for converting log entries into byte slices, which are then written to the desired output.

Let us create a custom formatter with a formatted message that contains

  • Timestamp
  • Log level (e.g., INFO, ERROR)
  • Session ID
  • Script name (filename)
  • Line number
  • The actual log message
package main

import (
	"fmt"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/google/uuid"
	"github.com/sirupsen/logrus"
)

type CustomTextFormatter struct {
	SessionID string
}

func (f *CustomTextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	// Get the file and line number where the log was called
	_, filename, line, _ := runtime.Caller(7)

	// Get the script name from the full file path
	scriptName := filepath.Base(filename)

	// Format the log message
	message := fmt.Sprintf("[%s] [%s] [%s] [%s:%d] %s\n",
		entry.Time.Format("2006-01-02 15:04:05"), // Date-time
		entry.Level.String(),                     // Log level
		f.SessionID,                              // Unique session ID
		scriptName,                               // Script name
		line,                                     // Line number
		entry.Message,                            // Log message
	)

	return []byte(message), nil
}

// Generate a unique session ID
func generateSessionID() string {
	randomUUID := uuid.New()
	return strings.Replace(randomUUID.String(), "-", "", -1)
}

func main() {
	// Generate a new unique session ID
	sessionID := generateSessionID()

	// Create a new instance of the custom formatter
	customFormatter := &CustomTextFormatter{SessionID: sessionID}

	// Set the custom formatter as the formatter for the logger
	logrus.SetFormatter(customFormatter)

	// Now, log something
	logrus.Info("This is a custom-formatted log")
}

Output:

[2023-08-20 14:22:21] [info] [449499175f8a44fcb7ee6b5b28c60a4d] [main.go:54] This is a custom-formatted log

 

Summary

Structured logging determines how reliable an application is. Go comes with a standard logger which is limited in terms of functionality. Logrus makes logging easy and fun and can be used to build metrics, log latency and many more. It also tries to make logging possible in a structured fashion.

 

References

https://pkg.go.dev/github.com/sirupsen/logrus#JSONFormatter
logrus set output format

 

Categories GO

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment