优化一些问题&修复一些BUG
This commit is contained in:
+151
-48
@@ -1,7 +1,9 @@
|
||||
package loggerx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -9,99 +11,200 @@ import (
|
||||
|
||||
// 文件操作
|
||||
|
||||
// 每个文件的写缓冲大小:越大越省系统调用,代价是崩溃时可能丢最后一批日志
|
||||
const fileBufSize = 32 * 1024
|
||||
|
||||
// logFile 一个已打开的日志文件句柄
|
||||
// 自己管理写缓冲(不用 bufio):bufio.Writer 在底层短写时会永久置位内部错误状态,
|
||||
// 之后所有写入和 Flush 都会失败,已攒下的一批日志会整批丢掉
|
||||
type logFile struct {
|
||||
file *os.File
|
||||
buf []byte
|
||||
pending int
|
||||
fileName string
|
||||
}
|
||||
|
||||
// Write 写入缓冲,满了就落盘一次
|
||||
func (f *logFile) Write(b []byte) (int, error) {
|
||||
written := 0
|
||||
for len(b) > 0 {
|
||||
if f.pending == len(f.buf) {
|
||||
if err := f.Flush(); err != nil {
|
||||
return written, err
|
||||
}
|
||||
}
|
||||
n := copy(f.buf[f.pending:], b)
|
||||
f.pending += n
|
||||
written += n
|
||||
b = b[n:]
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
// writeFull 保证把整块数据写进文件(允许底层短写,循环补齐)
|
||||
func (f *logFile) writeFull(p []byte) error {
|
||||
for len(p) > 0 {
|
||||
n, err := f.file.Write(p)
|
||||
p = p[n:]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush 把缓冲写入文件(不做 fsync)
|
||||
func (f *logFile) Flush() error {
|
||||
if f.pending == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := f.writeFull(f.buf[:f.pending]); err != nil {
|
||||
// 保留未写成功的部分,下次继续
|
||||
return err
|
||||
}
|
||||
f.pending = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sync 把缓冲刷到文件并 fsync
|
||||
func (f *logFile) Sync() error {
|
||||
var errs []error
|
||||
if err := f.Flush(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if f.file != nil {
|
||||
if err := f.file.Sync(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return joinErrors(errs)
|
||||
}
|
||||
|
||||
// Close 落盘并关闭文件
|
||||
func (f *logFile) Close() error {
|
||||
return joinErrors([]error{f.Sync(), f.file.Close()})
|
||||
}
|
||||
|
||||
// joinErrors 汇总错误,Go 1.20+ 用 errors.Join
|
||||
func joinErrors(errs []error) error {
|
||||
var real []error
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
real = append(real, err)
|
||||
}
|
||||
}
|
||||
if len(real) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.Join(real...)
|
||||
}
|
||||
|
||||
// 获取最新的文件名
|
||||
func (l *Logger) nowFileName(event string) string {
|
||||
// ioc, _ := time.LoadLocation("Asia/Shanghai")
|
||||
// timeDir := fmt.Sprint(time.Now().In(ioc).Format("2006/01/02/15")) // 2006-01-02 15:04:05
|
||||
|
||||
prefix := ""
|
||||
|
||||
switch l.option.fileSplit {
|
||||
case FileSplitTimeA:
|
||||
// (年/月/日/时)
|
||||
prefix = fmt.Sprint(time.Now().In(l.option.timeZone).Format("2006/01/02/15")) // 2006-01-02 15:04:05
|
||||
prefix = time.Now().In(l.option.timeZone).Format("2006/01/02/15")
|
||||
case FileSplitTimeB:
|
||||
// (年/月/日)
|
||||
prefix = fmt.Sprint(time.Now().In(l.option.timeZone).Format("2006/01/02")) // 2006-01-02 15:04:05
|
||||
prefix = time.Now().In(l.option.timeZone).Format("2006/01/02")
|
||||
case FileSplitTimeC:
|
||||
// (年/月-日)
|
||||
prefix = fmt.Sprint(time.Now().In(l.option.timeZone).Format("2006/01-02")) // 2006-01-02 15:04:05
|
||||
prefix = time.Now().In(l.option.timeZone).Format("2006/01-02")
|
||||
case FileSplitTimeD:
|
||||
// (年-月-日-时)
|
||||
prefix = fmt.Sprint(time.Now().In(l.option.timeZone).Format("2006-01-02-15")) // 2006-01-02 15:04:05
|
||||
prefix = time.Now().In(l.option.timeZone).Format("2006-01-02-15")
|
||||
case FileSplitTimeE:
|
||||
// (年-月-日)
|
||||
prefix = fmt.Sprint(time.Now().In(l.option.timeZone).Format("2006-01-02")) // 2006-01-02 15:04:05
|
||||
prefix = time.Now().In(l.option.timeZone).Format("2006-01-02")
|
||||
}
|
||||
|
||||
if prefix != "" {
|
||||
prefix = prefix + "_"
|
||||
}
|
||||
|
||||
// timeDir := fmt.Sprint(time.Now().Local().Format("2006/01/02")) // 2006-01-02 15:04:05
|
||||
if l.channel != "" {
|
||||
prefix = l.channel + "/" + prefix
|
||||
}
|
||||
path := l.option.dir + "/" + prefix + event + ".log"
|
||||
// fmt.Println(filepath.Abs(path))
|
||||
return path
|
||||
return l.option.dir + "/" + prefix + event + ".log"
|
||||
}
|
||||
|
||||
// 新建文件
|
||||
func (l *Logger) getFile(event string, isRefresh bool) (*os.File, error) {
|
||||
f := l.loadFile(event)
|
||||
if f != nil && !isRefresh {
|
||||
func (l *Logger) getFile(event string) (*logFile, error) {
|
||||
key := fileKey{channel: l.channel, event: event}
|
||||
|
||||
if f := l.loadFile(key); f != nil {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
// 双检:可能在等锁期间已经被别的 goroutine 建好了
|
||||
if f := l.loadFileLocked(key); f != nil {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
fileName := l.nowFileName(event)
|
||||
|
||||
dir, _ := filepath.Split(fileName) // 识别目录与文件
|
||||
os.MkdirAll(dir, os.ModePerm) // 创建多层目录,如果存在不会报错
|
||||
// 识别目录与文件,创建多层目录(已存在不报错)
|
||||
if dir := filepath.Dir(fileName); dir != "" {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 打开该文件,如果不存在则创建
|
||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
// 打开失败,尝试创建
|
||||
fmt.Println("打开日志文件失败")
|
||||
return nil, err
|
||||
}
|
||||
// 关闭原来的
|
||||
if f != nil {
|
||||
closeFile(f)
|
||||
return nil, fmt.Errorf("打开日志文件失败 %s: %w", fileName, err)
|
||||
}
|
||||
|
||||
l.filePath.Store(l.channel, &filePath{
|
||||
lf := &logFile{
|
||||
file: file,
|
||||
buf: make([]byte, fileBufSize),
|
||||
fileName: fileName,
|
||||
})
|
||||
}
|
||||
l.filePath[key] = lf
|
||||
|
||||
return file, nil
|
||||
return lf, nil
|
||||
}
|
||||
|
||||
// 加载文件
|
||||
func (l *Logger) loadFile(event string) *os.File {
|
||||
val, ok := l.filePath.Load(l.channel)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
f := val.(*filePath)
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
if f.fileName != l.nowFileName(event) {
|
||||
// 原来的文件需关闭
|
||||
closeFile(f.file)
|
||||
return nil
|
||||
}
|
||||
return f.file
|
||||
// 加载文件(读锁)
|
||||
func (l *Logger) loadFile(key fileKey) *logFile {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return l.loadFileLocked(key)
|
||||
}
|
||||
|
||||
// 关闭文件
|
||||
func closeFile(f *os.File) error {
|
||||
f.Sync()
|
||||
return f.Close()
|
||||
// loadFileLocked 调用方必须已持有锁
|
||||
func (l *Logger) loadFileLocked(key fileKey) *logFile {
|
||||
f, ok := l.filePath[key]
|
||||
if !ok || f == nil {
|
||||
return nil
|
||||
}
|
||||
// 时间切割导致文件名变化:关闭旧文件,让调用方按新名字重建
|
||||
if f.fileName != l.nowFileName(key.event) {
|
||||
delete(l.filePath, key)
|
||||
f.Close()
|
||||
return nil
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// close 关闭所有文件句柄(Close 专用)
|
||||
func (l *Logger) close() error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
var errs []error
|
||||
for key, f := range l.filePath {
|
||||
errs = append(errs, f.Close())
|
||||
delete(l.filePath, key)
|
||||
}
|
||||
return joinErrors(errs)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user