Files
loggerx/loggerx.go
T

224 lines
5.5 KiB
Go
Raw Normal View History

2024-01-22 18:05:51 +08:00
package loggerx
// author:黄新云
2024-04-03 21:18:44 +08:00
// lastTime:2024年4月3日21:18:05
2024-01-22 18:05:51 +08:00
// desc: 日志封装类
import (
"context"
"fmt"
"io"
"log"
"os"
"sync"
// "sync_log/global"
"github.com/gin-gonic/gin"
)
// 需要实现io.Writer接口
type Logger struct {
2024-11-22 20:08:25 +08:00
ctx context.Context
2026-09-13 20:41:27 +08:00
filePath map[fileKey]*logFile // 每个 (channel,event) 一个句柄
mu *sync.RWMutex // 保护 filePath
writeMu *sync.Mutex // 串行化文件写入(bufio 不是并发安全的)
2024-11-22 20:08:25 +08:00
option loggerOption
channel string
writeType writeType // 是否异步落盘,这里作用范围是本条,优先判断这里
2026-09-13 20:41:27 +08:00
closeOnce *sync.Once
done chan struct{} // 通用关闭信号(delete / ctx 相关 goroutine 用)
workerDone chan struct{} // 异步消费 goroutine 退出信号
closing chan struct{} // 异步投递关闭信号:只关一次
// 异步队列相关的整块状态都挂在指针后面:Logger 会被 Channel()/WriteAsync()
// 浅拷贝,如果状态直接放在 Logger 里,拷贝出的实例就会各看各的
// (曾经因此让 Close 看不到真正的队列,白等一场、最后一条日志丢失)
async *asyncCtl
2024-01-23 00:12:08 +08:00
}
2026-09-13 20:41:27 +08:00
// asyncCtl 异步队列的共享控制块:所有拷贝共享同一份
type asyncCtl struct {
mu sync.Mutex
ch chan cacheData
closed bool // 只能读写于 mu 保护下
}
// fileKey 文件缓存的键
// 必须同时包含 channel 与 event:文件名由两者共同决定,只按 channel 缓存会
// 导致 info/error 互相顶掉句柄,每次写入都 close+open 一次文件
type fileKey struct {
channel string
event string
2024-01-22 18:05:51 +08:00
}
2024-04-15 23:40:55 +08:00
func NewLogger(ctx context.Context, opts ...Option) *Logger {
2024-01-22 18:05:51 +08:00
opt := defaultOptions()
for _, apply := range opts {
apply(&opt)
}
2024-04-03 21:18:44 +08:00
// 验证文件夹权限
if !checkDir(opt.dir) {
panic("文件夹权限不足")
}
2024-01-23 00:12:08 +08:00
l := &Logger{
2026-09-13 20:41:27 +08:00
ctx: ctx,
filePath: make(map[fileKey]*logFile),
mu: &sync.RWMutex{},
writeMu: &sync.Mutex{},
option: opt,
writeType: writeTypeDefault,
closeOnce: &sync.Once{},
done: make(chan struct{}),
workerDone: make(chan struct{}),
async: &asyncCtl{},
2024-01-22 18:05:51 +08:00
}
2024-01-23 00:12:08 +08:00
2024-01-22 18:05:51 +08:00
log.SetOutput(l)
log.SetFlags(log.LstdFlags | log.Llongfile | log.Lmicroseconds) // log.Lshortfile | log.LUTC
// 保存Gin日志写入到文件+控制台
2024-01-23 00:12:08 +08:00
if opt.isGinLog {
gin.DefaultWriter = io.MultiWriter(l, os.Stdout)
gin.DefaultErrorWriter = io.MultiWriter(l, os.Stdout)
2024-01-22 18:05:51 +08:00
}
2024-01-23 13:37:51 +08:00
// 日志删除
go l.delete()
2024-04-15 23:40:55 +08:00
// 强制刷盘
2026-09-13 20:41:27 +08:00
// 用 done 而不是只监听 ctxctx 为 Background 时 Close 也要能把 goroutine 收回去
2024-04-15 23:40:55 +08:00
go func() {
2026-09-13 20:41:27 +08:00
select {
case <-ctx.Done():
l.MustSync()
case <-l.done:
}
2024-04-15 23:40:55 +08:00
}()
2024-01-22 18:05:51 +08:00
return l
}
2026-09-13 20:41:27 +08:00
// 关闭日志:关闭异步队列、落盘并关闭所有文件句柄
// 重复调用安全,调用后本实例的后台 goroutine 会全部退出
func (l *Logger) Close() error {
var err error
l.closeOnce.Do(func() {
// 1. 停投递 + 等消费协程把队列里已入队的任务全部写完
l.drainAsync()
// 2. 在写锁内完成最后一次刷盘与关闭
// 此后不会再有写入(投递已关闭,同步写入也要先抢到这把锁)
l.writeMu.Lock()
syncErr := l.MustSync()
closeErr := l.close()
l.writeMu.Unlock()
err = joinErrors([]error{syncErr, closeErr})
// 3. 最后再发通用关闭信号,让 delete / ctx 相关 goroutine 退出
close(l.done)
2024-01-23 00:12:08 +08:00
})
2026-09-13 20:41:27 +08:00
return err
}
// 强制刷盘:只 flush 缓存数据,文件继续可写
func (l *Logger) MustSync() error {
l.mu.RLock()
files := make([]*logFile, 0, len(l.filePath))
for _, f := range l.filePath {
files = append(files, f)
}
l.mu.RUnlock()
var errs []error
for _, f := range files {
errs = append(errs, f.Sync())
}
return joinErrors(errs)
2024-01-23 00:12:08 +08:00
}
2024-01-22 18:05:51 +08:00
2024-01-23 00:12:08 +08:00
func (l *Logger) Channel(ch string) (r *Logger) {
rr := *l
rr.channel = ch
return &rr
}
2024-01-22 18:05:51 +08:00
2024-11-22 20:08:25 +08:00
func (l *Logger) WriteAsync() (r *Logger) {
rr := *l
rr.writeType = writeTypeAsync
return &rr
}
func (l *Logger) WriteSync() (r *Logger) {
rr := *l
rr.writeType = writeTypeSync
return &rr
}
2024-07-03 16:58:14 +08:00
// 获取TraceField的字段
func (l *Logger) GetTraceField() string {
return l.option.traceField
}
2024-01-23 00:12:08 +08:00
// 实现io.Writer接口
2024-01-22 18:05:51 +08:00
func (l *Logger) Write(b []byte) (n int, err error) {
2024-01-23 00:12:08 +08:00
return l.write("info", b)
2024-01-22 18:05:51 +08:00
}
func (l *Logger) Info(ctx context.Context, v ...any) {
l.logger(ctx, "info", v...)
}
func (l *Logger) Infof(ctx context.Context, format string, v ...any) {
s := fmt.Sprintf(format, v...)
l.logger(ctx, "info", s)
}
func (l *Logger) Error(ctx context.Context, v ...any) {
l.logger(ctx, "error", v...)
}
func (l *Logger) Errorf(ctx context.Context, format string, v ...any) {
s := fmt.Sprintf(format, v...)
l.logger(ctx, "error", s)
}
2025-08-23 19:17:40 +08:00
func (l *Logger) Debug(ctx context.Context, v ...any) {
l.logger(ctx, "debug", v...)
}
func (l *Logger) Debugf(ctx context.Context, format string, v ...any) {
s := fmt.Sprintf(format, v...)
l.logger(ctx, "debug", s)
}
func (l *Logger) Warn(ctx context.Context, v ...any) {
l.logger(ctx, "warn", v...)
}
func (l *Logger) Warnf(ctx context.Context, format string, v ...any) {
s := fmt.Sprintf(format, v...)
l.logger(ctx, "warn", s)
}
2024-04-03 21:18:44 +08:00
// 验证文件夹权限
// 根文件夹如果不存在则创建
func checkDir(dir string) bool {
if _, err := os.Stat(dir); err != nil {
if os.IsNotExist(err) {
2026-09-13 20:41:27 +08:00
if err := os.MkdirAll(dir, 0755); err != nil {
2024-04-03 21:18:44 +08:00
log.Println("创建文件夹失败", err)
return false
}
2026-09-13 20:41:27 +08:00
return true
2024-04-03 21:18:44 +08:00
}
2026-09-13 20:41:27 +08:00
// 目标存在但不可访问(权限不足等),此时返回 false 才是这个函数的本意
log.Println("日志文件夹不可用", dir, err)
return false
2024-04-03 21:18:44 +08:00
}
return true
}