360 lines
8.2 KiB
Go
360 lines
8.2 KiB
Go
package logger
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"os"
|
||
"path/filepath"
|
||
"sync"
|
||
|
||
"git.toowon.com/jimmy/go-common/config"
|
||
)
|
||
|
||
// logMessage 异步日志消息结构
|
||
type logMessage struct {
|
||
level string // debug, info, warn, error
|
||
format string
|
||
args []interface{}
|
||
fields map[string]interface{} // 用于带字段的日志
|
||
}
|
||
|
||
// Logger 日志记录器
|
||
type Logger struct {
|
||
infoLog *log.Logger
|
||
errorLog *log.Logger
|
||
warnLog *log.Logger
|
||
debugLog *log.Logger
|
||
config *config.LoggerConfig
|
||
|
||
// 异步相关字段
|
||
async bool // 是否异步模式
|
||
logChan chan *logMessage // 异步日志channel
|
||
done chan struct{} // 用于优雅关闭
|
||
wg sync.WaitGroup // 等待所有日志写入完成
|
||
closed bool // 是否已关闭
|
||
closeMux sync.RWMutex // 保护closed字段
|
||
}
|
||
|
||
// NewLogger 创建日志记录器
|
||
func NewLogger(cfg *config.LoggerConfig) (*Logger, error) {
|
||
if cfg == nil {
|
||
// 使用默认配置
|
||
cfg = &config.LoggerConfig{
|
||
Level: "info",
|
||
Output: "stdout",
|
||
FilePath: "",
|
||
Async: false, // 默认同步
|
||
BufferSize: 1000, // 默认缓冲区大小
|
||
}
|
||
}
|
||
|
||
// 设置默认值
|
||
if cfg.Level == "" {
|
||
cfg.Level = "info"
|
||
}
|
||
if cfg.Output == "" {
|
||
cfg.Output = "stdout"
|
||
}
|
||
if cfg.BufferSize <= 0 {
|
||
cfg.BufferSize = 1000 // 默认缓冲区大小
|
||
}
|
||
|
||
// 创建输出目标
|
||
var writers []io.Writer
|
||
switch cfg.Output {
|
||
case "stdout":
|
||
writers = append(writers, os.Stdout)
|
||
case "stderr":
|
||
writers = append(writers, os.Stderr)
|
||
case "file":
|
||
if cfg.FilePath == "" {
|
||
return nil, fmt.Errorf("file path is required when output is file")
|
||
}
|
||
// 确保目录存在
|
||
dir := filepath.Dir(cfg.FilePath)
|
||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||
return nil, fmt.Errorf("failed to create log directory: %w", err)
|
||
}
|
||
file, err := os.OpenFile(cfg.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to open log file: %w", err)
|
||
}
|
||
writers = append(writers, file)
|
||
case "both":
|
||
writers = append(writers, os.Stdout)
|
||
if cfg.FilePath == "" {
|
||
return nil, fmt.Errorf("file path is required when output is both")
|
||
}
|
||
dir := filepath.Dir(cfg.FilePath)
|
||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||
return nil, fmt.Errorf("failed to create log directory: %w", err)
|
||
}
|
||
file, err := os.OpenFile(cfg.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to open log file: %w", err)
|
||
}
|
||
writers = append(writers, file)
|
||
default:
|
||
return nil, fmt.Errorf("invalid output type: %s", cfg.Output)
|
||
}
|
||
|
||
// 创建多写入器
|
||
multiWriter := io.MultiWriter(writers...)
|
||
|
||
// 创建日志前缀
|
||
prefix := ""
|
||
if cfg.Prefix != "" {
|
||
prefix = cfg.Prefix + " "
|
||
}
|
||
|
||
// 创建日志记录器
|
||
logger := &Logger{
|
||
config: cfg,
|
||
async: cfg.Async,
|
||
}
|
||
|
||
// 根据日志级别创建不同的logger
|
||
flags := log.LstdFlags
|
||
if cfg.DisableTimestamp {
|
||
flags = 0
|
||
}
|
||
|
||
if cfg.Level == "debug" || cfg.Level == "info" || cfg.Level == "warn" || cfg.Level == "error" {
|
||
logger.infoLog = log.New(multiWriter, prefix+"[INFO] ", flags)
|
||
logger.warnLog = log.New(multiWriter, prefix+"[WARN] ", flags)
|
||
logger.errorLog = log.New(multiWriter, prefix+"[ERROR] ", flags)
|
||
if cfg.Level == "debug" {
|
||
logger.debugLog = log.New(multiWriter, prefix+"[DEBUG] ", flags)
|
||
}
|
||
}
|
||
|
||
// 如果启用异步模式,启动goroutine处理日志
|
||
if cfg.Async {
|
||
logger.logChan = make(chan *logMessage, cfg.BufferSize)
|
||
logger.done = make(chan struct{})
|
||
logger.wg.Add(1)
|
||
go logger.processLogs()
|
||
}
|
||
|
||
return logger, nil
|
||
}
|
||
|
||
// processLogs 异步处理日志(goroutine)
|
||
func (l *Logger) processLogs() {
|
||
defer l.wg.Done()
|
||
|
||
for {
|
||
select {
|
||
case msg := <-l.logChan:
|
||
if msg == nil {
|
||
// channel已关闭,退出
|
||
return
|
||
}
|
||
l.writeLog(msg)
|
||
case <-l.done:
|
||
// 收到关闭信号,处理完剩余日志后退出
|
||
for {
|
||
select {
|
||
case msg := <-l.logChan:
|
||
if msg == nil {
|
||
return
|
||
}
|
||
l.writeLog(msg)
|
||
default:
|
||
return
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// writeLog 实际写入日志(内部方法)
|
||
func (l *Logger) writeLog(msg *logMessage) {
|
||
var logger *log.Logger
|
||
switch msg.level {
|
||
case "debug":
|
||
logger = l.debugLog
|
||
case "info":
|
||
logger = l.infoLog
|
||
case "warn":
|
||
logger = l.warnLog
|
||
case "error":
|
||
logger = l.errorLog
|
||
default:
|
||
return
|
||
}
|
||
|
||
if logger == nil {
|
||
return
|
||
}
|
||
|
||
// 如果有字段,先格式化字段
|
||
format := msg.format
|
||
if len(msg.fields) > 0 {
|
||
fieldStr := formatFields(msg.fields)
|
||
format = fieldStr + format
|
||
}
|
||
|
||
// 写入日志
|
||
logger.Printf(format, msg.args...)
|
||
}
|
||
|
||
// isClosed 检查logger是否已关闭
|
||
func (l *Logger) isClosed() bool {
|
||
l.closeMux.RLock()
|
||
defer l.closeMux.RUnlock()
|
||
return l.closed
|
||
}
|
||
|
||
// setClosed 设置logger为已关闭状态
|
||
func (l *Logger) setClosed() {
|
||
l.closeMux.Lock()
|
||
defer l.closeMux.Unlock()
|
||
l.closed = true
|
||
}
|
||
|
||
// log 内部日志方法,根据模式选择同步或异步
|
||
func (l *Logger) log(level string, format string, args []interface{}, fields map[string]interface{}) {
|
||
// 如果已关闭,直接返回
|
||
if l.isClosed() {
|
||
return
|
||
}
|
||
|
||
// 如果是异步模式,发送到channel
|
||
if l.async {
|
||
// 检查channel是否已关闭
|
||
select {
|
||
case l.logChan <- &logMessage{
|
||
level: level,
|
||
format: format,
|
||
args: args,
|
||
fields: fields,
|
||
}:
|
||
// 成功发送
|
||
default:
|
||
// channel已满或已关闭,同步写入(降级处理)
|
||
l.writeLog(&logMessage{
|
||
level: level,
|
||
format: format,
|
||
args: args,
|
||
fields: fields,
|
||
})
|
||
}
|
||
} else {
|
||
// 同步模式,直接写入
|
||
l.writeLog(&logMessage{
|
||
level: level,
|
||
format: format,
|
||
args: args,
|
||
fields: fields,
|
||
})
|
||
}
|
||
}
|
||
|
||
// Debug 记录调试日志
|
||
func (l *Logger) Debug(format string, v ...interface{}) {
|
||
l.log("debug", format, v, nil)
|
||
}
|
||
|
||
// Info 记录信息日志
|
||
func (l *Logger) Info(format string, v ...interface{}) {
|
||
l.log("info", format, v, nil)
|
||
}
|
||
|
||
// Warn 记录警告日志
|
||
func (l *Logger) Warn(format string, v ...interface{}) {
|
||
l.log("warn", format, v, nil)
|
||
}
|
||
|
||
// Error 记录错误日志
|
||
func (l *Logger) Error(format string, v ...interface{}) {
|
||
l.log("error", format, v, nil)
|
||
}
|
||
|
||
// Fatal 记录致命错误日志并退出程序(始终同步)
|
||
func (l *Logger) Fatal(format string, v ...interface{}) {
|
||
// Fatal必须同步执行,确保日志写入后再退出
|
||
if l.errorLog != nil {
|
||
l.errorLog.Fatalf(format, v...)
|
||
} else {
|
||
log.Fatalf(format, v...)
|
||
}
|
||
}
|
||
|
||
// Panic 记录恐慌日志并触发panic(始终同步)
|
||
func (l *Logger) Panic(format string, v ...interface{}) {
|
||
// Panic必须同步执行,确保日志写入后再panic
|
||
if l.errorLog != nil {
|
||
l.errorLog.Panicf(format, v...)
|
||
} else {
|
||
log.Panicf(format, v...)
|
||
}
|
||
}
|
||
|
||
// WithFields 创建带字段的日志记录器(简化版,返回格式化字符串)
|
||
func (l *Logger) WithFields(fields map[string]interface{}) *Logger {
|
||
// 返回自身,实际使用时可以在format中包含fields
|
||
return l
|
||
}
|
||
|
||
// formatFields 格式化字段
|
||
func formatFields(fields map[string]interface{}) string {
|
||
if len(fields) == 0 {
|
||
return ""
|
||
}
|
||
result := ""
|
||
for k, v := range fields {
|
||
result += fmt.Sprintf("%s=%v ", k, v)
|
||
}
|
||
return result
|
||
}
|
||
|
||
// Debugf 记录调试日志(带字段)
|
||
func (l *Logger) Debugf(fields map[string]interface{}, format string, v ...interface{}) {
|
||
l.log("debug", format, v, fields)
|
||
}
|
||
|
||
// Infof 记录信息日志(带字段)
|
||
func (l *Logger) Infof(fields map[string]interface{}, format string, v ...interface{}) {
|
||
l.log("info", format, v, fields)
|
||
}
|
||
|
||
// Warnf 记录警告日志(带字段)
|
||
func (l *Logger) Warnf(fields map[string]interface{}, format string, v ...interface{}) {
|
||
l.log("warn", format, v, fields)
|
||
}
|
||
|
||
// Errorf 记录错误日志(带字段)
|
||
func (l *Logger) Errorf(fields map[string]interface{}, format string, v ...interface{}) {
|
||
l.log("error", format, v, fields)
|
||
}
|
||
|
||
// Close 优雅关闭logger(仅异步模式需要)
|
||
// 等待所有日志写入完成后再返回
|
||
func (l *Logger) Close() error {
|
||
if !l.async {
|
||
// 同步模式不需要关闭
|
||
return nil
|
||
}
|
||
|
||
// 检查是否已关闭
|
||
if l.isClosed() {
|
||
return nil
|
||
}
|
||
|
||
// 设置关闭状态
|
||
l.setClosed()
|
||
|
||
// 发送关闭信号
|
||
close(l.done)
|
||
|
||
// 关闭channel(会触发processLogs退出)
|
||
close(l.logChan)
|
||
|
||
// 等待所有日志写入完成
|
||
l.wg.Wait()
|
||
|
||
return nil
|
||
}
|