重构项目的实现,优化使用方法与使用逻辑

This commit is contained in:
2026-06-25 00:03:59 +08:00
parent a6e8101e09
commit 6072ec57e8
49 changed files with 1663 additions and 12534 deletions

View File

@@ -1,72 +1,100 @@
package logger
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
"path/filepath"
"sync"
"sync/atomic"
"time"
"git.toowon.com/jimmy/go-common/config"
)
type ctxKey int
const requestIDKey ctxKey = iota
// WithRequestID 将 Request ID 写入 context
func WithRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, requestIDKey, id)
}
// RequestIDFromContext 从 context 读取 Request ID
func RequestIDFromContext(ctx context.Context) string {
if ctx == nil {
return ""
}
if id, ok := ctx.Value(requestIDKey).(string); ok {
return id
}
return ""
}
var (
// defaultLogger 全局默认日志记录器
// 用于中间件和其他需要快速日志记录的场景
defaultLogger *Logger
defaultMux sync.RWMutex
)
func init() {
// 初始化默认logger同步模式输出到stdout
var err error
defaultLogger, err = NewLogger(nil)
l, err := NewLogger(nil)
if err != nil {
// 如果初始化失败使用nil后续会降级到标准输出
defaultLogger = nil
return
}
defaultLogger = l
}
// logMessage 异步日志消息结构
type logMessage struct {
level string // debug, info, warn, error
format string
args []interface{}
fields map[string]interface{} // 用于带字段的日志
// SetDefaultLogger 设置全局默认 logger
func SetDefaultLogger(l *Logger) {
defaultMux.Lock()
defer defaultMux.Unlock()
if defaultLogger != nil && defaultLogger != l {
_ = defaultLogger.Close()
}
defaultLogger = l
}
func getDefaultLogger() *Logger {
defaultMux.RLock()
defer defaultMux.RUnlock()
return defaultLogger
}
type logEntry struct {
level string
message string
fields map[string]any
}
// 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字段
level string
writers []io.Writer
prefix string
async bool
logChan chan logEntry
done chan struct{}
wg sync.WaitGroup
closed bool
mu sync.RWMutex
dropped atomic.Uint64
}
// NewLogger 创建日志记录器
func NewLogger(cfg *config.LoggerConfig) (*Logger, error) {
if cfg == nil {
// 使用默认配置
cfg = &config.LoggerConfig{
Level: "info",
Output: "stdout",
FilePath: "",
Async: false, // 默认同步
BufferSize: 1000, // 默认缓冲区大小
Async: config.BoolPtr(true),
BufferSize: 1000,
}
}
// 设置默认值
if cfg.Level == "" {
cfg.Level = "info"
}
@@ -74,10 +102,37 @@ func NewLogger(cfg *config.LoggerConfig) (*Logger, error) {
cfg.Output = "stdout"
}
if cfg.BufferSize <= 0 {
cfg.BufferSize = 1000 // 默认缓冲区大小
cfg.BufferSize = 1000
}
// 创建输出目标
writers, err := createWriters(cfg)
if err != nil {
return nil, err
}
prefix := ""
if cfg.Prefix != "" {
prefix = cfg.Prefix + " "
}
l := &Logger{
level: cfg.Level,
writers: writers,
prefix: prefix,
async: cfg.IsAsync(),
}
if cfg.IsAsync() {
l.logChan = make(chan logEntry, cfg.BufferSize)
l.done = make(chan struct{})
l.wg.Add(1)
go l.processLogs()
}
return l, nil
}
func createWriters(cfg *config.LoggerConfig) ([]io.Writer, error) {
var writers []io.Writer
switch cfg.Output {
case "stdout":
@@ -88,96 +143,56 @@ func NewLogger(cfg *config.LoggerConfig) (*Logger, error) {
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)
w, err := openLogFile(cfg.FilePath)
if err != nil {
return nil, fmt.Errorf("failed to open log file: %w", err)
return nil, err
}
writers = append(writers, file)
writers = append(writers, w)
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)
w, err := openLogFile(cfg.FilePath)
if err != nil {
return nil, fmt.Errorf("failed to open log file: %w", err)
return nil, err
}
writers = append(writers, file)
writers = append(writers, w)
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
return writers, nil
}
func openLogFile(path string) (io.Writer, error) {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0o755); err != nil {
return nil, fmt.Errorf("failed to create log directory: %w", err)
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
if err != nil {
return nil, fmt.Errorf("failed to open log file: %w", err)
}
return f, nil
}
// processLogs 异步处理日志goroutine
func (l *Logger) processLogs() {
defer l.wg.Done()
for {
select {
case msg := <-l.logChan:
if msg == nil {
// channel已关闭退出
case msg, ok := <-l.logChan:
if !ok {
return
}
l.writeLog(msg)
l.writeEntry(msg)
case <-l.done:
// 收到关闭信号,处理完剩余日志后退出
for {
select {
case msg := <-l.logChan:
if msg == nil {
case msg, ok := <-l.logChan:
if !ok {
return
}
l.writeLog(msg)
l.writeEntry(msg)
default:
return
}
@@ -186,278 +201,161 @@ func (l *Logger) processLogs() {
}
}
// 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()
l.mu.RLock()
defer l.mu.RUnlock()
return l.closed
}
// setClosed 设置logger为已关闭状态
func (l *Logger) setClosed() {
l.closeMux.Lock()
defer l.closeMux.Unlock()
l.closed = true
func (l *Logger) shouldLog(level string) bool {
switch l.level {
case "debug":
return true
case "info":
return level != "debug"
case "error":
return level == "error"
default:
return level != "debug"
}
}
// log 内部日志方法,根据模式选择同步或异步
func (l *Logger) log(level string, format string, args []interface{}, fields map[string]interface{}) {
// 如果已关闭,直接返回
if l.isClosed() {
func (l *Logger) emit(level, message string, fields map[string]any) {
if l.isClosed() || !l.shouldLog(level) {
return
}
// 如果是异步模式发送到channel
entry := logEntry{level: level, message: message, fields: fields}
if l.async {
// 检查channel是否已关闭
select {
case l.logChan <- &logMessage{
level: level,
format: format,
args: args,
fields: fields,
}:
// 成功发送
case l.logChan <- entry:
default:
// channel已满或已关闭同步写入降级处理
l.writeLog(&logMessage{
level: level,
format: format,
args: args,
fields: fields,
})
l.dropped.Add(1)
}
} else {
// 同步模式,直接写入
l.writeLog(&logMessage{
level: level,
format: format,
args: args,
fields: fields,
})
return
}
l.writeEntry(entry)
}
func (l *Logger) writeEntry(entry logEntry) {
line := l.formatLine(entry.level, entry.message, entry.fields)
for _, w := range l.writers {
_, _ = fmt.Fprintln(w, line)
}
}
func (l *Logger) formatLine(level, message string, fields map[string]any) string {
ts := time.Now().Format("2006-01-02 15:04:05")
payload := map[string]any{
"time": ts,
"level": level,
"message": message,
}
for k, v := range fields {
payload[k] = v
}
b, err := json.Marshal(payload)
if err != nil {
return fmt.Sprintf("%s[%s] %s %v", l.prefix, level, message, fields)
}
return l.prefix + string(b)
}
// Debug 记录调试日志
func (l *Logger) Debug(format string, v ...interface{}) {
l.log("debug", format, v, nil)
func (l *Logger) Debug(message string, fields map[string]any) {
l.emit("debug", message, fields)
}
// 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)
func (l *Logger) Info(message string, fields map[string]any) {
l.emit("info", message, fields)
}
// Error 记录错误日志
func (l *Logger) Error(format string, v ...interface{}) {
l.log("error", format, v, nil)
func (l *Logger) Error(message string, fields map[string]any) {
l.emit("error", message, fields)
}
// 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仅异步模式需要
// 等待所有日志写入完成后再返回
// Close 刷盘并关闭异步队列
func (l *Logger) Close() error {
if !l.async {
// 同步模式不需要关闭
return nil
}
// 检查是否已关闭
if l.isClosed() {
l.mu.Lock()
if l.closed {
l.mu.Unlock()
return nil
}
l.closed = true
l.mu.Unlock()
// 设置关闭状态
l.setClosed()
// 发送关闭信号
close(l.done)
// 关闭channel会触发processLogs退出
close(l.logChan)
// 等待所有日志写入完成
l.wg.Wait()
return nil
}
// ========== 全局默认Logger相关方法 ==========
// DroppedCount 返回因队列满而丢弃的日志条数
func (l *Logger) DroppedCount() uint64 {
return l.dropped.Load()
}
// SetDefaultLogger 设置全局默认logger
// 用于在应用启动时统一配置logger
func SetDefaultLogger(log *Logger) {
defaultMux.Lock()
defer defaultMux.Unlock()
// ContextLogger 带 context 的 logger自动附加 request_id
type ContextLogger struct {
base *Logger
ctx context.Context
}
// 如果之前有logger先关闭它
if defaultLogger != nil {
defaultLogger.Close()
// FromContext 从 context 获取 logger自动附加 request_id
func FromContext(ctx context.Context) *ContextLogger {
base := getDefaultLogger()
if base == nil {
if l, err := NewLogger(nil); err == nil {
base = l
}
}
defaultLogger = log
return &ContextLogger{base: base, ctx: ctx}
}
// 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...)
// FromContextWithLogger 使用指定 logger 并从 context 附加 request_id
func FromContextWithLogger(ctx context.Context, base *Logger) *ContextLogger {
if base == nil {
base = getDefaultLogger()
}
return &ContextLogger{base: base, ctx: ctx}
}
// Info 使用全局logger记录信息日志
func Info(format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Info(format, v...)
func (c *ContextLogger) mergeFields(fields map[string]any) map[string]any {
out := make(map[string]any, len(fields)+1)
for k, v := range fields {
out[k] = v
}
if id := RequestIDFromContext(c.ctx); id != "" {
out["request_id"] = id
}
return out
}
// Warn 使用全局logger记录警告日志
func Warn(format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Warn(format, v...)
// Debug 记录调试日志
func (c *ContextLogger) Debug(message string, fields map[string]any) {
if c.base == nil {
return
}
c.base.Debug(message, c.mergeFields(fields))
}
// Error 使用全局logger记录错误日志
func Error(format string, v ...interface{}) {
if log := GetDefaultLogger(); log != nil {
log.Error(format, v...)
// Info 记录信息日志
func (c *ContextLogger) Info(message string, fields map[string]any) {
if c.base == nil {
return
}
c.base.Info(message, c.mergeFields(fields))
}
// 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...)
// Error 记录错误日志
func (c *ContextLogger) Error(message string, fields map[string]any) {
if c.base == nil {
return
}
c.base.Error(message, c.mergeFields(fields))
}