新增日志模块
This commit is contained in:
219
internal/logging/logger.go
Normal file
219
internal/logging/logger.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// Logger 封装 zerolog,提供全局日志能力
|
||||
type Logger struct {
|
||||
zl zerolog.Logger // 底层 zerolog 实例
|
||||
level zerolog.Level // 当前日志级别
|
||||
logFile *os.File // 日志文件句柄(可选)
|
||||
quiet bool // 静默模式:不输出到控制台
|
||||
verbose bool // 详细模式:启用 Debug 级别
|
||||
}
|
||||
|
||||
// Options 创建 Logger 时的配置选项
|
||||
type Options struct {
|
||||
Level string // 日志级别: trace|debug|info|warn|error|fatal (默认 info)
|
||||
LogFile string // 日志文件路径(空则不写文件)
|
||||
Verbose bool // 详细模式,覆盖 Level 为 debug
|
||||
Quiet bool // 静默模式,禁用控制台输出
|
||||
NoColor bool // 禁用彩色输出
|
||||
}
|
||||
|
||||
// 全局 Logger 实例,初始化前使用零值安全地丢弃日志
|
||||
var global = &Logger{zl: zerolog.Nop()}
|
||||
|
||||
// G 返回全局 Logger 实例
|
||||
func G() *Logger {
|
||||
return global
|
||||
}
|
||||
|
||||
// Init 根据选项初始化全局 Logger,应在程序启动时调用一次
|
||||
func Init(opts Options) (*Logger, error) {
|
||||
logger, err := New(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
global = logger
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
// New 创建一个新的 Logger 实例
|
||||
func New(opts Options) (*Logger, error) {
|
||||
// 解析日志级别
|
||||
level := zerolog.InfoLevel
|
||||
if opts.Verbose {
|
||||
level = zerolog.DebugLevel
|
||||
} else if opts.Level != "" {
|
||||
parsed, err := zerolog.ParseLevel(opts.Level)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无效的日志级别 %q: %w", opts.Level, err)
|
||||
}
|
||||
level = parsed
|
||||
}
|
||||
|
||||
// 构建输出目标(Writers)
|
||||
var writers []io.Writer
|
||||
|
||||
// 控制台输出(非静默模式时启用)
|
||||
if !opts.Quiet {
|
||||
console := zerolog.ConsoleWriter{
|
||||
Out: os.Stdout,
|
||||
TimeFormat: "2006-01-02 15:04:05",
|
||||
NoColor: opts.NoColor,
|
||||
}
|
||||
writers = append(writers, console)
|
||||
}
|
||||
|
||||
// 文件输出(指定了日志文件时启用)
|
||||
var logFile *os.File
|
||||
if opts.LogFile != "" {
|
||||
dir := filepath.Dir(opts.LogFile)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return nil, fmt.Errorf("无法创建日志目录 %s: %w", dir, err)
|
||||
}
|
||||
f, err := os.OpenFile(opts.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无法打开日志文件 %s: %w", opts.LogFile, err)
|
||||
}
|
||||
logFile = f
|
||||
// 文件始终使用 JSON 格式,不带颜色
|
||||
writers = append(writers, f)
|
||||
}
|
||||
|
||||
// 若无任何输出目标,丢弃所有日志
|
||||
var writer io.Writer
|
||||
switch len(writers) {
|
||||
case 0:
|
||||
writer = io.Discard
|
||||
case 1:
|
||||
writer = writers[0]
|
||||
default:
|
||||
writer = zerolog.MultiLevelWriter(writers...)
|
||||
}
|
||||
|
||||
zl := zerolog.New(writer).
|
||||
Level(level).
|
||||
With().
|
||||
Timestamp().
|
||||
Logger()
|
||||
|
||||
return &Logger{
|
||||
zl: zl,
|
||||
level: level,
|
||||
logFile: logFile,
|
||||
quiet: opts.Quiet,
|
||||
verbose: opts.Verbose,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close 关闭日志文件句柄(如果有的话),应在程序退出时调用
|
||||
func (l *Logger) Close() {
|
||||
if l.logFile != nil {
|
||||
_ = l.logFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 各级别日志方法 ----
|
||||
|
||||
// Trace 输出 Trace 级别日志
|
||||
func (l *Logger) Trace(msg string) {
|
||||
l.zl.Trace().Msg(msg)
|
||||
}
|
||||
|
||||
// Tracef 输出格式化 Trace 级别日志
|
||||
func (l *Logger) Tracef(format string, args ...any) {
|
||||
l.zl.Trace().Msgf(format, args...)
|
||||
}
|
||||
|
||||
// Debug 输出 Debug 级别日志
|
||||
func (l *Logger) Debug(msg string) {
|
||||
l.zl.Debug().Msg(msg)
|
||||
}
|
||||
|
||||
// Debugf 输出格式化 Debug 级别日志
|
||||
func (l *Logger) Debugf(format string, args ...any) {
|
||||
l.zl.Debug().Msgf(format, args...)
|
||||
}
|
||||
|
||||
// Info 输出 Info 级别日志
|
||||
func (l *Logger) Info(msg string) {
|
||||
l.zl.Info().Msg(msg)
|
||||
}
|
||||
|
||||
// Infof 输出格式化 Info 级别日志
|
||||
func (l *Logger) Infof(format string, args ...any) {
|
||||
l.zl.Info().Msgf(format, args...)
|
||||
}
|
||||
|
||||
// Warn 输出 Warn 级别日志
|
||||
func (l *Logger) Warn(msg string) {
|
||||
l.zl.Warn().Msg(msg)
|
||||
}
|
||||
|
||||
// Warnf 输出格式化 Warn 级别日志
|
||||
func (l *Logger) Warnf(format string, args ...any) {
|
||||
l.zl.Warn().Msgf(format, args...)
|
||||
}
|
||||
|
||||
// Error 输出 Error 级别日志
|
||||
func (l *Logger) Error(msg string) {
|
||||
l.zl.Error().Msg(msg)
|
||||
}
|
||||
|
||||
// Errorf 输出格式化 Error 级别日志
|
||||
func (l *Logger) Errorf(format string, args ...any) {
|
||||
l.zl.Error().Msgf(format, args...)
|
||||
}
|
||||
|
||||
// Fatal 输出 Fatal 级别日志并终止程序
|
||||
func (l *Logger) Fatal(msg string) {
|
||||
l.zl.Fatal().Msg(msg)
|
||||
}
|
||||
|
||||
// Fatalf 输出格式化 Fatal 级别日志并终止程序
|
||||
func (l *Logger) Fatalf(format string, args ...any) {
|
||||
l.zl.Fatal().Msgf(format, args...)
|
||||
}
|
||||
|
||||
// ---- 带字段的结构化日志 ----
|
||||
|
||||
// WithField 附加一个键值对字段,返回可链式调用的事件
|
||||
func (l *Logger) WithField(key string, value any) *zerolog.Event {
|
||||
return l.zl.Info().Any(key, value)
|
||||
}
|
||||
|
||||
// WithError 附加一个错误对象,返回 Error 级别事件
|
||||
func (l *Logger) WithError(err error) *zerolog.Event {
|
||||
return l.zl.Error().Err(err)
|
||||
}
|
||||
|
||||
// WithDuration 附加一个耗时字段,常用于性能日志
|
||||
func (l *Logger) WithDuration(d time.Duration) *zerolog.Event {
|
||||
return l.zl.Info().Dur("duration", d)
|
||||
}
|
||||
|
||||
// ---- 工具方法 ----
|
||||
|
||||
// IsDebug 返回当前是否启用了 Debug 级别
|
||||
func (l *Logger) IsDebug() bool {
|
||||
return l.level <= zerolog.DebugLevel
|
||||
}
|
||||
|
||||
// IsQuiet 返回当前是否处于静默模式
|
||||
func (l *Logger) IsQuiet() bool {
|
||||
return l.quiet
|
||||
}
|
||||
|
||||
// IsVerbose 返回当前是否处于详细模式
|
||||
func (l *Logger) IsVerbose() bool {
|
||||
return l.verbose
|
||||
}
|
||||
1
internal/logging/logger_test.go
Normal file
1
internal/logging/logger_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package logging
|
||||
Reference in New Issue
Block a user