Files
DevPack/internal/logging/logger.go
2026-03-04 14:10:45 +08:00

220 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}