Logrus

One of veteran Go logging frameworks. Maintains relevance in 2025 due to rich feature set and community support. However, no longer viable option for high-performance applications, but effective when prioritizing feature richness.

Logging LibraryGoGolangStructured LoggingHooksJSONText

Library

Logrus

Overview

Logrus is a structured logging library for Go that provides a pluggable architecture and simple API. It supports multiple output formats (JSON, text), structured fields, log level control, and hook functionality for external system integration, catering to environments from development to production. As of 2025, while it has transitioned to maintenance mode, it remains a proven logging solution in the Go ecosystem, continuously utilized in existing projects due to its rich features and stability.

Details

Logrus 2025 edition maintains its established position as a pioneer in structured logging for Go. It enables structured data addition through WithFields(), machine-readable output via JSON formatter, and external service integration with custom hook functionality for services like Syslog, Airbrake, and Slack. Supporting multiple log levels (Trace, Debug, Info, Warn, Error, Fatal, Panic) with runtime dynamic level changes. Enhances development experience through test hooks, color formatters, and custom formatter creation, with proven reliability in large-scale Go applications.

Key Features

  • Structured Logging: Efficient management of structured data through WithFields()
  • Pluggable Formatters: Support for JSON, text, and custom formatters
  • Hook Functionality: Plugin extension capabilities for external service integration
  • 7-Level Log System: Detailed level control from Trace to Panic
  • Test Support: Log verification capabilities through test hooks
  • Rich Ecosystem: Diverse hooks for Syslog, Airbrake, Slack, and more

Pros and Cons

Pros

  • Mature design as a pioneer in Go structured logging
  • High extensibility through rich formatters and hook functionality
  • Low learning curve with simple and intuitive API
  • High compatibility with monitoring and analysis tools through JSON output
  • Efficient management of context-specific logs through WithFields()
  • Easy log verification through test hook functionality
  • Active community and comprehensive documentation

Cons

  • No new feature additions expected due to current maintenance mode
  • Performance inferior compared to Zerolog and Zap
  • Not recommended for new projects
  • Potential performance bottleneck in high-load environments
  • Relatively high memory allocations
  • Some hook features increase external dependencies

Reference Pages

Usage Examples

Installation and Basic Setup

# Add logrus to Go module
go get github.com/sirupsen/logrus

# For hook functionality (optional)
go get github.com/sirupsen/logrus/hooks/syslog
go get github.com/sirupsen/logrus/hooks/writer
package main

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

func main() {
	// Basic log output
	log.Info("Application started")
	log.Debug("Debug information")
	log.Warn("Warning message")
	log.Error("Error occurred")
	
	// Structured logging (recommended)
	log.WithFields(log.Fields{
		"user_id": 12345,
		"action":  "login",
		"ip":      "192.168.1.100",
	}).Info("User login")
	
	// Combination of objects and messages
	log.WithFields(log.Fields{
		"event": "database_connection",
		"host":  "db-server-01",
		"port":  5432,
	}).Error("Database connection failed")
	
	// Reusing context logger
	requestLogger := log.WithFields(log.Fields{
		"request_id": "req-123456",
		"user_ip":    "10.0.1.50",
	})
	
	requestLogger.Info("Request processing started")
	requestLogger.Warn("Response time exceeded threshold")
	requestLogger.Info("Request processing completed")
}

Log Levels and Formatter Configuration

package main

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

func init() {
	// Environment-based formatter configuration
	if os.Getenv("ENVIRONMENT") == "production" {
		// Production: JSON format (machine-readable)
		log.SetFormatter(&log.JSONFormatter{
			TimestampFormat: "2006-01-02T15:04:05.000Z07:00",
			FieldMap: log.FieldMap{
				log.FieldKeyTime:  "timestamp",
				log.FieldKeyLevel: "severity",
				log.FieldKeyMsg:   "message",
			},
		})
	} else {
		// Development: Text format (human-readable)
		log.SetFormatter(&log.TextFormatter{
			DisableColors:   false,
			FullTimestamp:   true,
			TimestampFormat: "2006-01-02 15:04:05",
		})
	}
	
	// Output destination (default is stderr)
	log.SetOutput(os.Stdout)
	
	// Log level configuration
	if os.Getenv("DEBUG") == "true" {
		log.SetLevel(log.DebugLevel)
	} else {
		log.SetLevel(log.InfoLevel)
	}
	
	// Enable caller information (for debugging, impacts performance)
	log.SetReportCaller(true)
}

func main() {
	// Usage examples for all log levels
	log.Trace("Very detailed debug information")
	log.Debug("Debug information")
	log.Info("Information message")
	log.Warn("Warning message")
	log.Error("Error message")
	// log.Fatal("Fatal error (calls os.Exit(1))")
	// log.Panic("Panic (calls panic())")
	
	// Dynamic log level change
	log.SetLevel(log.WarnLevel)
	log.Info("This info message will not be output")
	log.Warn("This warning message will be output")
}

Hook Functionality and External System Integration

package main

import (
	"io"
	"log/syslog"
	"os"
	
	log "github.com/sirupsen/logrus"
	lSyslog "github.com/sirupsen/logrus/hooks/syslog"
	"github.com/sirupsen/logrus/hooks/writer"
)

func init() {
	// Disable output (controlled by hooks)
	log.SetOutput(io.Discard)
	
	// Writer Hook: Level-based output destination
	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.TraceLevel,
		},
	})
	
	// Syslog Hook (Local)
	hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "myapp")
	if err == nil {
		log.AddHook(hook)
	}
	
	// Syslog Hook (Remote)
	remoteHook, err := lSyslog.NewSyslogHook("udp", "log-server:514", syslog.LOG_INFO, "myapp")
	if err == nil {
		log.AddHook(remoteHook)
	}
}

// Custom hook example: Slack notification
type SlackHook struct {
	WebhookURL string
	Username   string
	Channel    string
}

func (hook *SlackHook) Levels() []log.Level {
	return []log.Level{
		log.ErrorLevel,
		log.FatalLevel,
		log.PanicLevel,
	}
}

func (hook *SlackHook) Fire(entry *log.Entry) error {
	// Slack notification implementation
	message := fmt.Sprintf("🚨 *%s*: %s", entry.Level.String(), entry.Message)
	
	// Add structured fields
	if len(entry.Data) > 0 {
		fields := make([]string, 0, len(entry.Data))
		for key, value := range entry.Data {
			fields = append(fields, fmt.Sprintf("*%s*: %v", key, value))
		}
		message += "\n" + strings.Join(fields, "\n")
	}
	
	// Send request to Slack API (implementation omitted)
	return hook.sendToSlack(message)
}

func (hook *SlackHook) sendToSlack(message string) error {
	// Slack webhook API implementation
	// This part would send actual HTTP request
	return nil
}

func main() {
	// Add custom hook
	slackHook := &SlackHook{
		WebhookURL: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
		Username:   "logrus-bot",
		Channel:    "#alerts",
	}
	log.AddHook(slackHook)
	
	// Normal log (stdout)
	log.Info("This will be displayed on stdout")
	
	// Error log (stderr + Syslog + Slack)
	log.WithFields(log.Fields{
		"error_code": "DB_001",
		"connection": "primary",
		"retry_count": 3,
	}).Error("Database connection error")
}

Advanced Configuration and Custom Formatters

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"strings"
	
	log "github.com/sirupsen/logrus"
)

// Custom formatter example
type CustomFormatter struct {
	TimestampFormat string
	LogFormat       string
}

func (f *CustomFormatter) Format(entry *log.Entry) ([]byte, error) {
	output := f.LogFormat
	
	// Timestamp
	if f.TimestampFormat == "" {
		f.TimestampFormat = "2006-01-02 15:04:05"
	}
	output = strings.Replace(output, "%time%", entry.Time.Format(f.TimestampFormat), 1)
	
	// Log level
	output = strings.Replace(output, "%lvl%", strings.ToUpper(entry.Level.String()), 1)
	
	// Message
	output = strings.Replace(output, "%msg%", entry.Message, 1)
	
	// Fields
	var fieldsStr string
	if len(entry.Data) > 0 {
		fields := make([]string, 0, len(entry.Data))
		for key, value := range entry.Data {
			fields = append(fields, fmt.Sprintf("%s=%v", key, value))
		}
		fieldsStr = "[" + strings.Join(fields, " ") + "]"
	}
	output = strings.Replace(output, "%fields%", fieldsStr, 1)
	
	return []byte(output + "\n"), nil
}

// Application-specific logger
type ApplicationLogger struct {
	*log.Logger
	serviceName string
	version     string
}

func NewApplicationLogger(serviceName, version string) *ApplicationLogger {
	logger := log.New()
	
	// Environment-based configuration
	env := os.Getenv("ENVIRONMENT")
	if env == "production" {
		logger.SetLevel(log.InfoLevel)
		logger.SetFormatter(&log.JSONFormatter{
			TimestampFormat: "2006-01-02T15:04:05.000Z07:00",
		})
	} else if env == "development" {
		logger.SetLevel(log.DebugLevel)
		logger.SetFormatter(&CustomFormatter{
			LogFormat: "%time% [%lvl%] %msg% %fields%",
		})
	} else {
		logger.SetLevel(log.WarnLevel)
		logger.SetFormatter(&log.TextFormatter{
			DisableColors: true,
			FullTimestamp: true,
		})
	}
	
	return &ApplicationLogger{
		Logger:      logger,
		serviceName: serviceName,
		version:     version,
	}
}

func (al *ApplicationLogger) WithContext(fields map[string]interface{}) *log.Entry {
	return al.WithFields(log.Fields{
		"service": al.serviceName,
		"version": al.version,
	}).WithFields(log.Fields(fields))
}

func (al *ApplicationLogger) LogRequest(method, path string, statusCode int, duration float64) {
	entry := al.WithContext(map[string]interface{}{
		"component": "http",
		"method":    method,
		"path":      path,
		"status":    statusCode,
		"duration":  fmt.Sprintf("%.2fms", duration),
	})
	
	if statusCode >= 500 {
		entry.Error("HTTP request error")
	} else if statusCode >= 400 {
		entry.Warn("HTTP request warning")
	} else {
		entry.Info("HTTP request completed")
	}
}

func (al *ApplicationLogger) LogDatabaseOperation(operation, table string, duration float64, err error) {
	entry := al.WithContext(map[string]interface{}{
		"component": "database",
		"operation": operation,
		"table":     table,
		"duration":  fmt.Sprintf("%.2fms", duration),
	})
	
	if err != nil {
		entry.WithError(err).Error("Database operation error")
	} else {
		entry.Info("Database operation completed")
	}
}

func main() {
	// Initialize application logger
	appLogger := NewApplicationLogger("user-service", "v1.2.3")
	
	// HTTP request logs
	appLogger.LogRequest("GET", "/api/users/123", 200, 45.67)
	appLogger.LogRequest("POST", "/api/users", 400, 12.34)
	appLogger.LogRequest("GET", "/api/users/999", 500, 1234.56)
	
	// Database operation logs
	appLogger.LogDatabaseOperation("SELECT", "users", 23.45, nil)
	appLogger.LogDatabaseOperation("INSERT", "users", 67.89, fmt.Errorf("duplicate key"))
	
	// Business logic logs
	userLogger := appLogger.WithContext(map[string]interface{}{
		"component": "business",
		"user_id":   12345,
	})
	
	userLogger.Info("User authentication started")
	userLogger.Debug("Auth token validation")
	userLogger.Info("Authentication successful")
	
	// Error handling example
	if err := performCriticalOperation(); err != nil {
		appLogger.WithContext(map[string]interface{}{
			"component": "critical",
			"operation": "payment_process",
		}).WithError(err).Fatal("Critical error occurred")
	}
}

func performCriticalOperation() error {
	// Critical operation simulation
	return nil
}

Test Support and Log Verification

package main

import (
	"testing"
	
	log "github.com/sirupsen/logrus"
	"github.com/sirupsen/logrus/hooks/test"
	"github.com/stretchr/testify/assert"
)

// Example function to test
func ProcessUser(userID int, action string) error {
	logger := log.WithFields(log.Fields{
		"user_id": userID,
		"action":  action,
	})
	
	logger.Info("User processing started")
	
	if userID <= 0 {
		logger.Error("Invalid user ID")
		return fmt.Errorf("invalid user ID: %d", userID)
	}
	
	if action == "delete" {
		logger.Warn("User deletion operation")
	}
	
	logger.Info("User processing completed")
	return nil
}

func TestProcessUser(t *testing.T) {
	// Initialize test logger and hook
	logger, hook := test.NewNullLogger()
	log.SetOutput(logger.Out)
	
	tests := []struct {
		name           string
		userID         int
		action         string
		expectError    bool
		expectedLogs   int
		expectedLevels []log.Level
		expectedMsgs   []string
	}{
		{
			name:           "Normal user processing",
			userID:         123,
			action:         "update",
			expectError:    false,
			expectedLogs:   2,
			expectedLevels: []log.Level{log.InfoLevel, log.InfoLevel},
			expectedMsgs:   []string{"User processing started", "User processing completed"},
		},
		{
			name:           "Invalid user ID",
			userID:         -1,
			action:         "create",
			expectError:    true,
			expectedLogs:   2,
			expectedLevels: []log.Level{log.InfoLevel, log.ErrorLevel},
			expectedMsgs:   []string{"User processing started", "Invalid user ID"},
		},
		{
			name:           "Delete operation warning",
			userID:         456,
			action:         "delete",
			expectError:    false,
			expectedLogs:   3,
			expectedLevels: []log.Level{log.InfoLevel, log.WarnLevel, log.InfoLevel},
			expectedMsgs:   []string{"User processing started", "User deletion operation", "User processing completed"},
		},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Reset hook
			hook.Reset()
			
			// Execute test
			err := ProcessUser(tt.userID, tt.action)
			
			// Verify error
			if tt.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
			
			// Verify log entry count
			assert.Equal(t, tt.expectedLogs, len(hook.Entries))
			
			// Verify each log entry
			for i, entry := range hook.Entries {
				if i < len(tt.expectedLevels) {
					assert.Equal(t, tt.expectedLevels[i], entry.Level)
				}
				if i < len(tt.expectedMsgs) {
					assert.Equal(t, tt.expectedMsgs[i], entry.Message)
				}
				
				// Verify fields
				assert.Equal(t, tt.userID, entry.Data["user_id"])
				assert.Equal(t, tt.action, entry.Data["action"])
			}
		})
	}
}

// Performance test example
func BenchmarkLogrus(b *testing.B) {
	// Benchmark configuration
	log.SetFormatter(&log.JSONFormatter{})
	log.SetLevel(log.InfoLevel)
	
	b.ResetTimer()
	
	for i := 0; i < b.N; i++ {
		log.WithFields(log.Fields{
			"iteration": i,
			"user_id":   12345,
			"action":    "benchmark",
		}).Info("Benchmark test")
	}
}

func BenchmarkLogrusWithCaller(b *testing.B) {
	// Enable caller information (measure performance impact)
	log.SetReportCaller(true)
	log.SetFormatter(&log.JSONFormatter{})
	log.SetLevel(log.InfoLevel)
	
	b.ResetTimer()
	
	for i := 0; i < b.N; i++ {
		log.WithFields(log.Fields{
			"iteration": i,
			"user_id":   12345,
			"action":    "benchmark_caller",
		}).Info("Benchmark with caller information")
	}
}