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.
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")
}
}