Go Logging (Logrus / Zap / Slog)

Slog Basic

import "log/slog"

slog.Info("user logged in", "user_id", 42, "ip", "192.168.1.1")
slog.Warn("disk space low", "percent_free", 5.2)
slog.Error("connection failed", "err", err, "host", "db.example.com")
slog.Debug("cache hit", "key", "user:42")

slog.Info("request completed",
    "method", "GET",
    "path", "/api/users",
    "status", 200,
    "duration", time.Since(start),
)

Slog Structured

slog.Info("user created",
    slog.String("name", "Alice"),
    slog.Int("id", 42),
    slog.Float64("score", 98.5),
    slog.Bool("active", true),
    slog.Duration("elapsed", time.Since(start)),
    slog.Time("timestamp", time.Now()),
)

slog.LogAttrs(context.Background(), slog.LevelInfo, "order placed",
    slog.String("order_id", "ORD-123"),
    slog.Int("items", 3),
    slog.Float64("total", 99.99),
)

Slog Handlers (Text/JSON)

handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
})
logger := slog.New(handler)
slog.SetDefault(logger)

handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level:     slog.LevelInfo,
    AddSource: true,
})
logger := slog.New(handler)
slog.SetDefault(logger)

Slog Context

ctx := context.WithValue(context.Background(), "request_id", "req-abc123")

handler := slog.NewJSONHandler(os.Stdout, nil)
handler = middleware.ExtractRequestID(handler)
slog.SetDefault(slog.New(handler))

slog.InfoContext(ctx, "processing request")

func WithRequestID(ctx context.Context, reqID string) context.Context {
    return context.WithValue(ctx, "request_id", reqID)
}

Slog Custom Handler

type FilterHandler struct {
    handler slog.Handler
    level   slog.Level
}

func (h *FilterHandler) Enabled(ctx context.Context, level slog.Level) bool {
    return level >= h.level
}

func (h *FilterHandler) Handle(ctx context.Context, r slog.Record) error {
    return h.handler.Handle(ctx, r)
}

func (h *FilterHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
    return &FilterHandler{handler: h.handler.WithAttrs(attrs), level: h.level}
}

func (h *FilterHandler) WithGroup(name string) slog.Handler {
    return &FilterHandler{handler: h.handler.WithGroup(name), level: h.level}
}

Logrus Basic

import log "github.com/sirupsen/logrus"

log.SetFormatter(&log.JSONFormatter{})
log.SetLevel(log.InfoLevel)

log.Info("server started")
log.Warn("config missing, using defaults")
log.Error("failed to connect")

log.WithFields(log.Fields{
    "user_id": 42,
    "action":  "login",
}).Info("user action")

log.SetFormatter(&log.TextFormatter{
    FullTimestamp: true,
    ForceColors:   true,
})

Logrus Hooks

type SlackHook struct{}

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

func (h *SlackHook) Fire(entry *log.Entry) error {
    msg, _ := entry.String()
    return sendToSlack(msg)
}

log.AddHook(&SlackHook{})

hook, _ := lfsHook.NewHook(
    lfsHook.WriterMap{
        log.ErrorLevel: os.Stderr,
        log.InfoLevel:  logFile,
    },
    &log.JSONFormatter{},
)
log.AddHook(hook)

Logrus Fields

entry := log.WithFields(log.Fields{
    "request_id": "req-123",
    "user_id":    42,
    "module":     "auth",
})

entry.Info("attempting login")
entry.Warn("rate limit approaching")
entry.Error("login failed")

log.WithField("service", "api").Info("health check")
log.WithTime(time.Now()).Info("timed event")

log.WithError(err).Error("operation failed")
log.WithError(err).WithField("query", sql).Fatal("db error")

Zap Basic

import "go.uber.org/zap"

logger, _ := zap.NewProduction()
defer logger.Sync()

logger.Info("user created",
    zap.String("name", "Alice"),
    zap.Int("id", 42),
    zap.Error(err),
)

logger.Warn("slow query",
    zap.String("query", "SELECT * FROM users"),
    zap.Duration("duration", 500*time.Millisecond),
)

logger.Error("connection lost",
    zap.String("host", "db.example.com"),
    zap.Int("port", 5432),
)

Zap Structured

logger, _ := zap.NewProduction()

logger.Info("request",
    zap.String("method", "GET"),
    zap.String("path", "/api/users"),
    zap.Int("status", 200),
    zap.Duration("latency", time.Since(start)),
    zap.Any("headers", req.Header),
)

logger.Info("metrics",
    zap.Int64("bytes_written", 2048),
    zap.Float64("cpu_usage", 0.75),
    zap.Bool("healthy", true),
    zap.Strings("tags", []string{"go", "web"}),
    zap.Ints("codes", []int{200, 201, 404}),
)

Zap Encoder Config

cfg := zap.Config{
    Level:       zap.NewAtomicLevelAt(zap.InfoLevel),
    Development: false,
    Encoding:    "json",
    EncoderConfig: zapcore.EncoderConfig{
        TimeKey:        "ts",
        LevelKey:       "level",
        NameKey:        "logger",
        CallerKey:      "caller",
        MessageKey:     "msg",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     zapcore.ISO8601TimeEncoder,
        EncodeDuration: zapcore.StringDurationEncoder,
        EncodeCaller:   zapcore.ShortCallerEncoder,
    },
    OutputPaths:      []string{"stdout", "/var/log/app.log"},
    ErrorOutputPaths: []string{"stderr"},
}
logger, _ := cfg.Build()

Zap Sugar Logger

logger, _ := zap.NewProduction()
sugar := logger.Sugar()
defer sugar.Sync()

sugar.Infow("request received",
    "method", "GET",
    "path", "/api/users",
    "latency", time.Since(start),
)

sugar.Infof("user %s logged in from %s", name, ip)
sugar.Errorw("db error", "err", err, "query", query)

sugar.With("request_id", "abc123").Infow("processing")

Choosing a Library

Slog (stdlib):
  - Built into Go 1.21+
  - Structured logging with typed attributes
  - Pluggable handlers
  - Zero dependencies
  - Best for: new projects, stdlib preference

Zap (uber-go):
  - Fastest structured logger
  - Type-safe fields, no reflection
  - Sugar logger for printf-style
  - Best for: high-performance services

Logrus:
  - Mature, widely adopted
  - Hooks system for extensibility
  - Slower than Zap/Slog
  - Best for: existing codebases, hook needs

Common Patterns

// Request logger middleware (Slog)
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        slog.Info("request",
            "method", r.Method,
            "path", r.URL.Path,
            "duration", time.Since(start),
        )
    })
}

// Error with stack trace (Zap)
logger.Error("panic recovered",
    zap.String("stack", string(debug.Stack())),
    zap.Any("request", reqInfo),
)

// Conditional debug logging
if logger.Core().Enabled(zap.DebugLevel) {
    logger.Debug("large payload", zap.Any("data", largeSlice))
}

Slog 基础

import "log/slog"

slog.Info("用户登录", "user_id", 42, "ip", "192.168.1.1")
slog.Warn("磁盘空间不足", "percent_free", 5.2)
slog.Error("连接失败", "err", err, "host", "db.example.com")
slog.Debug("缓存命中", "key", "user:42")

slog.Info("请求完成",
    "method", "GET",
    "path", "/api/users",
    "status", 200,
    "duration", time.Since(start),
)

Slog 结构化

slog.Info("用户创建",
    slog.String("name", "Alice"),
    slog.Int("id", 42),
    slog.Float64("score", 98.5),
    slog.Bool("active", true),
    slog.Duration("elapsed", time.Since(start)),
    slog.Time("timestamp", time.Now()),
)

slog.LogAttrs(context.Background(), slog.LevelInfo, "下单成功",
    slog.String("order_id", "ORD-123"),
    slog.Int("items", 3),
    slog.Float64("total", 99.99),
)

Slog Handler (Text/JSON)

handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
})
logger := slog.New(handler)
slog.SetDefault(logger)

handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level:     slog.LevelInfo,
    AddSource: true,
})
logger := slog.New(handler)
slog.SetDefault(logger)

Slog Context

ctx := context.WithValue(context.Background(), "request_id", "req-abc123")

handler := slog.NewJSONHandler(os.Stdout, nil)
handler = middleware.ExtractRequestID(handler)
slog.SetDefault(slog.New(handler))

slog.InfoContext(ctx, "处理请求")

func WithRequestID(ctx context.Context, reqID string) context.Context {
    return context.WithValue(ctx, "request_id", reqID)
}

Slog 自定义 Handler

type FilterHandler struct {
    handler slog.Handler
    level   slog.Level
}

func (h *FilterHandler) Enabled(ctx context.Context, level slog.Level) bool {
    return level >= h.level
}

func (h *FilterHandler) Handle(ctx context.Context, r slog.Record) error {
    return h.handler.Handle(ctx, r)
}

func (h *FilterHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
    return &FilterHandler{handler: h.handler.WithAttrs(attrs), level: h.level}
}

func (h *FilterHandler) WithGroup(name string) slog.Handler {
    return &FilterHandler{handler: h.handler.WithGroup(name), level: h.level}
}

Logrus 基础

import log "github.com/sirupsen/logrus"

log.SetFormatter(&log.JSONFormatter{})
log.SetLevel(log.InfoLevel)

log.Info("服务启动")
log.Warn("配置缺失,使用默认值")
log.Error("连接失败")

log.WithFields(log.Fields{
    "user_id": 42,
    "action":  "login",
}).Info("用户操作")

log.SetFormatter(&log.TextFormatter{
    FullTimestamp: true,
    ForceColors:   true,
})

Logrus 钩子

type SlackHook struct{}

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

func (h *SlackHook) Fire(entry *log.Entry) error {
    msg, _ := entry.String()
    return sendToSlack(msg)
}

log.AddHook(&SlackHook{})

hook, _ := lfsHook.NewHook(
    lfsHook.WriterMap{
        log.ErrorLevel: os.Stderr,
        log.InfoLevel:  logFile,
    },
    &log.JSONFormatter{},
)
log.AddHook(hook)

Logrus 字段

entry := log.WithFields(log.Fields{
    "request_id": "req-123",
    "user_id":    42,
    "module":     "auth",
})

entry.Info("尝试登录")
entry.Warn("接近速率限制")
entry.Error("登录失败")

log.WithField("service", "api").Info("健康检查")
log.WithTime(time.Now()).Info("定时事件")

log.WithError(err).Error("操作失败")
log.WithError(err).WithField("query", sql).Fatal("数据库错误")

Zap 基础

import "go.uber.org/zap"

logger, _ := zap.NewProduction()
defer logger.Sync()

logger.Info("用户创建",
    zap.String("name", "Alice"),
    zap.Int("id", 42),
    zap.Error(err),
)

logger.Warn("慢查询",
    zap.String("query", "SELECT * FROM users"),
    zap.Duration("duration", 500*time.Millisecond),
)

logger.Error("连接丢失",
    zap.String("host", "db.example.com"),
    zap.Int("port", 5432),
)

Zap 结构化

logger, _ := zap.NewProduction()

logger.Info("请求",
    zap.String("method", "GET"),
    zap.String("path", "/api/users"),
    zap.Int("status", 200),
    zap.Duration("latency", time.Since(start)),
    zap.Any("headers", req.Header),
)

logger.Info("指标",
    zap.Int64("bytes_written", 2048),
    zap.Float64("cpu_usage", 0.75),
    zap.Bool("healthy", true),
    zap.Strings("tags", []string{"go", "web"}),
    zap.Ints("codes", []int{200, 201, 404}),
)

Zap 编码器配置

cfg := zap.Config{
    Level:       zap.NewAtomicLevelAt(zap.InfoLevel),
    Development: false,
    Encoding:    "json",
    EncoderConfig: zapcore.EncoderConfig{
        TimeKey:        "ts",
        LevelKey:       "level",
        NameKey:        "logger",
        CallerKey:      "caller",
        MessageKey:     "msg",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     zapcore.ISO8601TimeEncoder,
        EncodeDuration: zapcore.StringDurationEncoder,
        EncodeCaller:   zapcore.ShortCallerEncoder,
    },
    OutputPaths:      []string{"stdout", "/var/log/app.log"},
    ErrorOutputPaths: []string{"stderr"},
}
logger, _ := cfg.Build()

Zap Sugar Logger

logger, _ := zap.NewProduction()
sugar := logger.Sugar()
defer sugar.Sync()

sugar.Infow("收到请求",
    "method", "GET",
    "path", "/api/users",
    "latency", time.Since(start),
)

sugar.Infof("用户 %s 从 %s 登录", name, ip)
sugar.Errorw("数据库错误", "err", err, "query", query)

sugar.With("request_id", "abc123").Infow("处理中")

如何选择

Slog(标准库):
  - Go 1.21+ 内置
  - 类型化属性的结构化日志
  - 可插拔 Handler
  - 零依赖
  - 适用于:新项目、偏好标准库

Zap (uber-go):
  - 最快的结构化日志库
  - 类型安全字段,无反射
  - Sugar logger 支持 printf 风格
  - 适用于:高性能服务

Logrus:
  - 成熟,广泛使用
  - 钩子系统可扩展
  - 比 Zap/Slog 慢
  - 适用于:现有代码库、需要钩子

常用模式

// 请求日志中间件(Slog)
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        slog.Info("请求",
            "method", r.Method,
            "path", r.URL.Path,
            "duration", time.Since(start),
        )
    })
}

// 带堆栈跟踪的错误(Zap)
logger.Error("panic 恢复",
    zap.String("stack", string(debug.Stack())),
    zap.Any("request", reqInfo),
)

// 条件调试日志
if logger.Core().Enabled(zap.DebugLevel) {
    logger.Debug("大量数据", zap.Any("data", largeSlice))
}