Files
go-common/logger/logger.go

464 lines
11 KiB
Go
Raw Permalink 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 logger
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"sync"
"git.toowon.com/jimmy/go-common/config"
)
var (
// defaultLogger 全局默认日志记录器
// 用于中间件和其他需要快速日志记录的场景
defaultLogger *Logger
defaultMux sync.RWMutex
)
func init() {
// 初始化默认logger同步模式输出到stdout
var err error
defaultLogger, err = NewLogger(nil)
if err != nil {
// 如果初始化失败使用nil后续会降级到标准输出
defaultLogger = nil
}
}
// 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
}
// ========== 全局默认Logger相关方法 ==========
// SetDefaultLogger 设置全局默认logger
// 用于在应用启动时统一配置logger
func SetDefaultLogger(log *Logger) {
defaultMux.Lock()
defer defaultMux.Unlock()
// 如果之前有logger先关闭它
if defaultLogger != nil {
defaultLogger.Close()
}
defaultLogger = log
}
// GetDefaultLogger 获取全局默认logger
func GetDefaultLogger() *Logger {
defaultMux.RLock()
defer defaultMux.RUnlock()
return defaultLogger
}
// Default 全局日志方法 - Debug
func Default() *Logger {
return GetDefaultLogger()
}
// ========== 全局便捷日志方法 ==========
// 以下方法使用全局默认logger方便快速记录日志
// Debug 使用全局logger记录调试日志
func Debug(format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Debug(format, v...)
}
}
// Info 使用全局logger记录信息日志
func Info(format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Info(format, v...)
}
}
// Warn 使用全局logger记录警告日志
func Warn(format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Warn(format, v...)
}
}
// Error 使用全局logger记录错误日志
func Error(format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Error(format, v...)
}
}
// Debugf 使用全局logger记录调试日志带字段
func Debugf(fields map[string]interface{}, format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Debugf(fields, format, v...)
}
}
// Infof 使用全局logger记录信息日志带字段
func Infof(fields map[string]interface{}, format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Infof(fields, format, v...)
}
}
// Warnf 使用全局logger记录警告日志带字段
func Warnf(fields map[string]interface{}, format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Warnf(fields, format, v...)
}
}
// Errorf 使用全局logger记录错误日志带字段
func Errorf(fields map[string]interface{}, format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Errorf(fields, format, v...)
}
}