zerolog 封装:打造更强大、更灵活的日志系统
在现代软件开发中,一个强大而灵活的日志系统是不可或缺的。本文将详细介绍如何封装 github.com/rs/zerolog
库,以创建一个更加易用、可配置且功能丰富的日志系统。
为什么要封装 zerolog?
虽然 zerolog 是一个高性能的日志库,但直接使用它可能会面临以下挑战:
- 配置复杂,需要编写大量样板代码
- 缺乏开箱即用的全局配置
- 不易实现额外功能,如 HTTP 日志转发
通过封装,我们可以解决这些问题,同时保留 zerolog 的高性能特性。
封装的主要特性
我们的封装库 zlog
提供了以下主要特性:
- 全局单例 logger,易于在整个应用中使用
- 支持通过结构体进行灵活配置
- 支持日志轮转
- 内置 HTTP 日志转发功能
- 支持动态修改日志级别
- 提供了便捷的日志级别检查方法
核心代码结构
让我们深入了解 zlog
的核心代码结构:
package zlog
import (
"io"
"os"
"strings"
"sync"
"time"
"github.com/rs/zerolog"
"gopkg.in/natefinch/lumberjack.v2"
)
// LogConfig 定义了日志的配置结构
type LogConfig struct {
Level string `yaml:"Level"`
Pattern string `yaml:"Pattern"`
Output string `yaml:"Output"`
LogRotate LogRotate `yaml:"LogRotate"`
HTTP HTTPConfig `yaml:"HTTP"`
}
// LogRotate 定义了日志轮转的配置
type LogRotate struct {
Filename string `yaml:"Filename"`
MaxSize int `yaml:"MaxSize"`
MaxBackups int `yaml:"MaxBackups"`
MaxAge int `yaml:"MaxAge"`
Compress bool `yaml:"Compress"`
}
// HTTPConfig 定义了 HTTP 日志的配置
type HTTPConfig struct {
Enabled bool `yaml:"Enabled"`
URL string `yaml:"URL"`
WarnOnHttpErr bool `yaml:"WarnOnHttpErr"`
}
// Logger 接口定义了日志的方法
type Logger interface {
Trace(message string, args ...interface{})
Debug(message string, args ...interface{})
Info(message string, args ...interface{})
Warn(message string, args ...interface{})
Error(message string, args ...interface{})
Fatal(message string, args ...interface{})
Panic(message string, args ...interface{})
SetConfig(config LogConfig)
GetConfig() LogConfig
EnableHTTPLogging(url string, warnOnHttpErr bool)
IsDebugEnabled() bool
// ... 其他方法
}
// loggerImpl 是 Logger 接口的具体实现
type loggerImpl struct {
zl zerolog.Logger
config LogConfig
isTraceEnabled bool
isDebugEnabled bool
isInfoEnabled bool
isWarnEnabled bool
isErrorEnabled bool
isFatalEnabled bool
isPanicEnabled bool
output io.Writer
httpWriter *LogHTTPWriter
}
var (
globalLogger *loggerImpl
once sync.Once
)
// GetLogger 返回全局单例 logger
func GetLogger() Logger {
once.Do(func() {
globalLogger = &loggerImpl{
config: DefaultConfig,
}
globalLogger.init()
})
return globalLogger
}
// init 初始化 logger
func (l *loggerImpl) init() {
// 设置日志级别
level, _ := zerolog.ParseLevel(strings.ToLower(l.config.Level))
zerolog.SetGlobalLevel(level)
// 更新启用的日志级别标志
l.updateEnabledFlags(level)
// 配置输出
if l.config.Output == "stdout" {
l.output = os.Stdout
} else {
l.output = &lumberjack.Logger{
Filename: l.config.LogRotate.Filename,
MaxSize: l.config.LogRotate.MaxSize,
MaxBackups: l.config.LogRotate.MaxBackups,
MaxAge: l.config.LogRotate.MaxAge,
Compress: l.config.LogRotate.Compress,
}
}
// 配置 HTTP 日志
if l.config.HTTP.Enabled {
l.httpWriter = NewLogHTTPWriter(l.config.HTTP.URL, l.config.HTTP.WarnOnHttpErr)
l.output = io.MultiWriter(l.output, l.httpWriter)
}
// 创建 zerolog.Logger
logContext := zerolog.New(l.output).With().Timestamp()
if l.config.Pattern == "development" {
l.output = zerolog.ConsoleWriter{Out: l.output, TimeFormat: time.RFC3339}
logContext = zerolog.New(l.output).With().Timestamp().Caller()
}
l.zl = logContext.Logger()
}
// SetConfig 设置新的日志配置
func (l *loggerImpl) SetConfig(config LogConfig) {
l.config = config
l.init()
}
// EnableHTTPLogging 启用 HTTP 日志
func (l *loggerImpl) EnableHTTPLogging(url string, warnOnHttpErr bool) {
l.config.HTTP.Enabled = true
l.config.HTTP.URL = url
l.config.HTTP.WarnOnHttpErr = warnOnHttpErr
l.init()
}
// Debug 输出 Debug 级别的日志
func (l *loggerImpl) Debug(message string, args ...interface{}) {
if l.isDebugEnabled {
l.log(zerolog.DebugLevel, message, args...)
}
}
// log 是内部日志方法
func (l *loggerImpl) log(level zerolog.Level, message string, args ...interface{}) {
if len(args) > 0 {
l.zl.WithLevel(level).Msgf(message, args...)
} else {
l.zl.WithLevel(level).Msg(message)
}
}
// IsDebugEnabled 检查 Debug 级别是否启用
func (l *loggerImpl) IsDebugEnabled() bool {
return l.isDebugEnabled
}
// LogHTTPWriter 实现了将日志写入 HTTP 服务器的功能
type LogHTTPWriter struct {
serverURL string
warnOnHttpErr bool
errorLogger zerolog.Logger
}
func NewLogHTTPWriter(serverURL string, warnOnHttpErr bool) *LogHTTPWriter {
return &LogHTTPWriter{
serverURL: serverURL,
warnOnHttpErr: warnOnHttpErr,
errorLogger: zerolog.New(os.Stderr).With().Caller().Timestamp().Logger(),
}
}
func (w *LogHTTPWriter) Write(p []byte) (n int, err error) {
// 实现 HTTP 写入逻辑
// ...
return len(p), nil
}
如何使用
使用 zlog
非常简单:
package main
import (
"your_package_path/zlog"
)
func main() {
logger := zlog.GetLogger()
// 使用默认配置
logger.Info("这是一条信息日志")
// 启用 HTTP 日志
logger.EnableHTTPLogging("http://log-server.com/logs", true)
// 修改配置
newConfig := zlog.LogConfig{
Level: "debug",
Pattern: "development",
Output: "file",
LogRotate: zlog.LogRotate{
Filename: "app.log",
MaxSize: 50, // MB
},
}
logger.SetConfig(newConfig)
// 使用新配置
logger.Debug("这是一条调试日志")
// 使用日志级别检查
if logger.IsDebugEnabled() {
logger.Debug("这条日志只在 Debug 级别启用时输出")
}
}
高级特性:HTTP 日志转发
我们的封装库支持将日志实时转发到 HTTP 服务器,这对于分布式系统的日志收集非常有用。LogHTTPWriter
结构体实现了这一功能:
type LogHTTPWriter struct {
serverURL string
warnOnHttpErr bool
errorLogger zerolog.Logger
}
func (w *LogHTTPWriter) Write(p []byte) (n int, err error) {
// 将日志发送到 HTTP 服务器
// 实现省略...
return len(p), nil
}
性能考虑
尽管我们添加了额外的功能,但我们仍然保持了 zerolog 的高性能特性:
- 使用
sync.Once
确保全局 logger 只被初始化一次 - 提供了日志级别检查方法,避免不必要的字符串格式化
- 使用
io.MultiWriter
实现多目标日志输出,无需额外的 goroutine
通过封装 zerolog,我们创建了一个既易用又灵活的日志系统。它保留了 zerolog 的高性能特性,同时提供了更多的功能和更好的可配置性。无论是在小型项目还是大型分布式系统中,这个日志系统都能满足各种需求。高性能,灵活强大的zlog日志处理库在我们的SagooIoT V3 版企业级物联网平台系统中全面使用。
希望这个封装库能够帮助你更轻松地处理日志,让你专注于业务逻辑的开发。如果你有任何问题或建议,欢迎在评论区留言!