Logrus: Structured Logger for Go

repository·master·Indexed 12 days ago

https://github.com/sirupsen/logrus

A structured logger for Go that is API-compatible with the standard library's `log` package. Logrus facilitates machine-readable logging via JSON and logfmt through the use of fields and hooks. It supports seven logging levels, custom formatters like `JSONFormatter` and `TextFormatter`, and provides extensibility through hooks for routing logs to destinations such as Syslog or custom `io.Writer` implementations.

Tokens
11.8K
Snippets
48
Records
54
Agent score
98%

What's inside Logrus

  1. Manage thread safety and locking

    master
    By default, the Logger is protected by a mutex to ensure thread-safe concurrent writes. If you are certain that locking is unnecessary (e.g., you have no hooks and are writing to a thread-safe io.Writer like an os.File opened with O_APPEND), you can call logger.SetNoLock() to disable locking and improve performance.
  2. Use structured logging with Fields

    master

    Instead of using printf-style formatting for long error messages, Logrus encourages using WithFields with a logrus.Fields map (which is a map[string]any). This makes logs more discoverable and easier to parse by machines.

    // Avoid this:
    logrus.Fatalf("Failed to send event %s to topic %s with key %d", event, topic, key)
    
    // Do this:
    logrus.WithFields(logrus.Fields{
      "event": event,
      "topic": topic,
      "key": key,
    }).Fatal("Failed to send event")
  3. Reuse log context with logrus.Entry

    master

    A common pattern is to create a logrus.Entry by calling WithFields once and then reusing that entry for multiple subsequent log statements. This ensures a set of 'default' or 'contextual' fields are attached to every log call made with that entry.

    // Create an entry with common fields
    contextLogger := log.WithFields(log.Fields{
      "common": "this is a common field",
      "other": "I also should be logged always",
    })
    
    // These will both include the fields above
    contextLogger.Info("I'll be logged with common and other field")
    contextLogger.Info("Me too")
  4. Quickstart: Use the Logrus package-level logger

    master

    The simplest way to use Logrus is via the package-level exported logger. It is completely API-compatible with the Go standard library log package, allowing you to replace log imports with log "github.com/sirupsen/logrus" to gain structured logging capabilities.

    package main
    
    import "github.com/sirupsen/logrus"
    
    func main() {
      logrus.WithFields(logrus.Fields{
        "animal": "walrus",
      }).Info("A walrus appears")
    }
  5. Test log output with the test hook

    master

    Logrus provides a test hook to facilitate asserting that specific log messages were emitted during tests. You can use test.NewLocal(logger) to wrap an existing logger or test.NewNullLogger() to create a logger that only records messages in memory without printing them.

    import(
      "testing"
      "github.com/sirupsen/logrus"
      "github.com/sirupsen/logrus/hooks/test"
      "github.com/stretchr/testify/assert"
    )
    
    func TestSomething(t *testing.T) {
      logger, hook := test.NewNullLogger()
      logger.Error("Helloerror")
    
      assert.Equal(t, 1, len(hook.Entries))
      assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
      assert.Equal(t, "Helloerror", hook.LastEntry().Message)
    }
  6. Configure Logrus with custom formatters and levels

    master

    You can customize the global logger using package-level functions. Common configurations include setting the output destination (e.g., os.Stdout), changing the formatter (e.g., JSONFormatter), and setting the minimum logging severity level.

    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
      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")
    }
  7. Use Writer Hooks to route logs to different io.Writer destinations

    master

    The logrus/hooks/writer package allows you to route logs of specific levels to any object implementing the io.Writer interface. This is useful for separating log streams, such as sending high-severity logs (Error, Warn, etc.) to os.Stderr while sending routine execution logs (Info, Debug) to os.Stdout.

    package main
    
    import (
    	"io/ioutil"
    	"os"
    
    	log "github.com/sirupsen/logrus"
    	"github.com/sirupsen/logrus/hooks/writer"
    )
    
    func main() {
    	log.SetOutput(ioutil.Discard) // Send all logs to nowhere by default
    
    	log.AddHook(&writer.Hook{
    		Writer: os.Stderr,
    		LogLevels: []log.Level{
    			log.PanicLevel,
    			log.FatalLevel,
    			log.ErrorLevel,
    			log.WarnLevel,
    		},
    	})
    
    	log.AddHook(&writer.Hook{
    		Writer: os.Stdout,
    		LogLevels: []log.Level{
    			log.InfoLevel,
    			log.DebugLevel,
    		},
    	})
    
    	log.Info("This will go to stdout")
    	log.Warn("This will go to stderr")
    }
  8. Filter syslog log levels using a custom hook

    master

    By default, NewSyslogHook() sends all log levels to syslog. If you want the syslog hook to only trigger for specific levels (different from your main logger's level), you must create a custom hook type that embeds *lsyslog.SyslogHook and overrides the Levels() method to return only the desired logrus.Level values.

    package main
    
    import (
    	"log/syslog"
    
    	log "github.com/sirupsen/logrus"
    	lsyslog "github.com/sirupsen/logrus/hooks/syslog"
    )
    
    type customHook struct {
    	*lsyslog.SyslogHook
    }
    
    // Levels overrides the default behavior to only send specific levels to syslog
    func (h *customHook) Levels() []log.Level {
    	return []log.Level{log.WarnLevel}
    }
    
    func main() {
    	log.SetLevel(log.DebugLevel)
    
    	hook, err := lsyslog.NewSyslogHook("tcp", "localhost:5140", syslog.LOG_WARNING, "myTag")
    	if err != nil {
    		panic(err)
    	}
    
    	// Wrap the syslog hook in the custom hook to apply level filtering
    	log.AddHook(&customHook{hook})
    
    	// ...
    }
  9. What is an Entry and how to use it

    master

    An Entry represents a single log event. It can be an intermediate entry (created by adding fields or context) or a final entry that is emitted when a level method (like Info or Error) is called.

    Key Characteristics:

    • Ownership: Every Entry belongs to a Logger. A nil Logger will cause a panic during logging.
    • Immutability/Safety: Entries are safe to reuse for adding fields. Each log operation operates on a copy of the Entry's data to avoid mutation during formatting. You can pass entries around to avoid duplicating fields.
    • Fields: User-defined fields are stored in the Data field.
    // Creating an entry via a logger (standard usage)
    entry := logger.WithField("user_id", 123)
    entry.Info("user logged in")
  10. Customize default field names using FieldMap

    master

    The FieldMap type allows you to rename the standard Logrus keys (like time, level, and message) to match specific requirements, such as those used by the Elastic Stack or other log aggregators.

    To use it, initialize a JSONFormatter with a FieldMap containing your desired mappings. The resolve method ensures that if a key is not provided in the map, the original default key name is used.

    formatter := &logrus.JSONFormatter{
    	FieldMap: logrus.FieldMap{
    		logrus.FieldKeyTime:  "@timestamp",
    		logrus.FieldKeyLevel: "@level",
    		logrus.FieldKeyMsg:   "@message",
    		logrus.FieldKeyFunc:  "@caller",
    	},
    }
  11. Use TextFormatter for human-readable console output

    master

    The TextFormatter produces logfmt-like output consisting of key=value pairs separated by spaces. It is designed for human readability in consoles.

    Key characteristics:

    • Output Format: Keys are written as-is (unquoted/unescaped). Values may be quoted depending on configuration.
    • Colors: Supports ANSI escape sequences for colored output when a TTY is detected. Colors can be forced on or disabled entirely.
    • Structured vs. Plain: For fully escaped, machine-readable structured output, use JSONFormatter instead. TextFormatter is optimized for human eyes.
    formatter := &logrus.TextFormatter{
        ForceColors: true,
    }
    logger.SetFormatter(formatter)
  12. Quickstart: Use the package-level logger

    master

    The simplest way to use Logrus is via the package-level exported logger. It provides structured logging capabilities that are API compatible with the standard library log package, allowing you to attach key-value pairs (fields) to your log entries.

    package main
    
    import (
    	log "github.com/sirupsen/logrus"
    )
    
    func main() {
    	log.WithFields(log.Fields{
    		"animal": "walrus",
    		"number": 1,
    		"size":   10,
    	}).Info("A walrus appears")
    }