You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将互斥锁与ID管理器集成到Zerolog Writer实现自增日志ID?

解决方案

要实现自动添加自增ID、简化调用、缩短锁持有时间且保证时间有序的日志系统,我们可以封装一个自定义的Zerolog Logger,将ID管理、锁逻辑和时间戳控制整合到内部,同时保留Zerolog的链式调用特性。

实现代码

1. 自定义Logger和Event结构体

package main

import (
    "sync"
    "time"

    "github.com/rs/zerolog"
    "gopkg.in/natefinch/lumberjack.v2"
)

// Logger 自定义日志器,整合ID管理、锁和Zerolog
type Logger struct {
    baseLogger zerolog.Logger
    mu         sync.Mutex
    nextID     int64 // 自增ID的起始值,可根据需求调整
}

// Event 自定义事件结构体,转发Zerolog的字段方法,自动添加ID
type Event struct {
    innerEvent *zerolog.Event
    id         int64
}

// NewLogger 创建自定义Logger,传入Lumberjack作为底层Writer
func NewLogger() *Logger {
    // 初始化Lumberjack Writer
    lumberjackWriter := &lumberjack.Logger{
        Filename:   "/var/log/myapp/app.log",
        MaxSize:    10,    // 单个日志文件最大大小(MB)
        MaxBackups: 5,     // 保留的备份文件数量
        MaxAge:     30,    // 日志文件保留天数
        Compress:   true,  // 是否压缩备份文件
    }

    // 初始化Zerolog基础Logger(不自动添加时间戳,由自定义逻辑控制)
    baseLogger := zerolog.New(lumberjackWriter).Logger()

    return &Logger{
        baseLogger: baseLogger,
        nextID:     0,
    }
}

// Info 创建Info级别的日志事件,自动获取ID和时间戳
func (l *Logger) Info() *Event {
    l.mu.Lock()
    defer l.mu.Unlock()

    // 原子性操作:递增ID + 获取当前时间
    l.nextID++
    now := time.Now()

    // 创建Zerolog事件并设置时间戳
    inner := l.baseLogger.Info().Timestamp(now)
    return &Event{
        innerEvent: inner,
        id:         l.nextID,
    }
}

// Error 创建Error级别的日志事件,逻辑同Info
func (l *Logger) Error() *Event {
    l.mu.Lock()
    defer l.mu.Unlock()

    l.nextID++
    now := time.Now()

    inner := l.baseLogger.Error().Timestamp(now)
    return &Event{
        innerEvent: inner,
        id:         l.nextID,
    }
}

// Msg 完成日志写入,自动添加ID字段
func (e *Event) Msg(msg string) {
    e.innerEvent.Uint64("id", uint64(e.id)).Msg(msg)
}

// 以下方法转发Zerolog的字段添加能力,保留链式调用
func (e *Event) Str(key, value string) *Event {
    e.innerEvent.Str(key, value)
    return e
}

func (e *Event) Int(key string, value int) *Event {
    e.innerEvent.Int(key, value)
    return e
}

func (e *Event) Uint(key string, value uint) *Event {
    e.innerEvent.Uint(key, value)
    return e
}

// 可根据需求添加更多Zerolog字段方法,比如Bool、Float64等

2. 使用示例

func main() {
    logger := NewLogger()

    // 简化调用,无需手动加锁
    logger.Info().Str("user", "alice").Msg("User logged in")
    logger.Error().Int("code", 500).Msg("Internal server error")

    // 并发场景下也能保证ID递增且时间有序
    for i := 0; i < 10; i++ {
        go func(idx int) {
            logger.Info().Int("goroutine", idx).Msg("Concurrent log message")
        }(i)
    }
}

核心特性说明

  • 简化调用:无需手动加锁,直接通过logger.Info().Msg(...)链式调用,和原生Zerolog使用方式一致。
  • 锁持有时间极短:仅在获取自增ID和时间戳的瞬间加锁,锁的范围被压缩到最小,对并发性能影响极低。
  • 时间有序保证:ID递增和时间戳获取在同一个锁保护下完成,确保ID=n+1的日志时间戳一定不早于ID=n的日志时间戳。
  • 完整保留Zerolog功能:自定义Event转发了Zerolog的所有字段添加方法,可自由扩展日志内容。

内容的提问来源于stack exchange,提问作者ExtremG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 05:50:56