Sync and async sinks, redaction, and a queryable HTTP service keep concurrent logs tied to a trace id. Co-authored-by: Cursor <cursoragent@cursor.com>
117 lines
2.1 KiB
Go
117 lines
2.1 KiB
Go
package sink
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"logging-system/logsys"
|
|
)
|
|
|
|
// File 以互斥锁保护的方式追加日志,可选按大小滚动。
|
|
// MaxBytes 为 0 时不滚动。Keep 是保留的历史文件份数(不含当前文件)。
|
|
type File struct {
|
|
mu sync.Mutex
|
|
Path string
|
|
Format string
|
|
MaxBytes int64
|
|
Keep int
|
|
|
|
f *os.File
|
|
size int64
|
|
}
|
|
|
|
// Write 追加一条日志,必要时先滚动。
|
|
func (f *File) Write(e logsys.Entry) error {
|
|
line, err := formatLine(f.Format, e, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if err := f.open(); err != nil {
|
|
return err
|
|
}
|
|
if f.MaxBytes > 0 && f.size > 0 && f.size+int64(len(line)) > f.MaxBytes {
|
|
if err := f.rotate(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
n, err := f.f.Write(line)
|
|
f.size += int64(n)
|
|
return err
|
|
}
|
|
|
|
// Sync 把文件缓冲刷到磁盘。
|
|
func (f *File) Sync() error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.f == nil {
|
|
return nil
|
|
}
|
|
return f.f.Sync()
|
|
}
|
|
|
|
// Close 关闭文件句柄。
|
|
func (f *File) Close() error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.f == nil {
|
|
return nil
|
|
}
|
|
err := f.f.Close()
|
|
f.f = nil
|
|
return err
|
|
}
|
|
|
|
func (f *File) open() error {
|
|
if f.f != nil {
|
|
return nil
|
|
}
|
|
if f.Path == "" {
|
|
return fmt.Errorf("sink: file path is required")
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(f.Path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
fp, err := os.OpenFile(f.Path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
info, err := fp.Stat()
|
|
if err != nil {
|
|
_ = fp.Close()
|
|
return err
|
|
}
|
|
f.f = fp
|
|
f.size = info.Size()
|
|
return nil
|
|
}
|
|
|
|
func (f *File) rotate() error {
|
|
if f.f != nil {
|
|
if err := f.f.Close(); err != nil {
|
|
return err
|
|
}
|
|
f.f = nil
|
|
}
|
|
keep := f.Keep
|
|
if keep < 1 {
|
|
keep = 1
|
|
}
|
|
_ = os.Remove(fmt.Sprintf("%s.%d", f.Path, keep))
|
|
for i := keep - 1; i >= 1; i-- {
|
|
src := fmt.Sprintf("%s.%d", f.Path, i)
|
|
dst := fmt.Sprintf("%s.%d", f.Path, i+1)
|
|
if _, err := os.Stat(src); err == nil {
|
|
_ = os.Rename(src, dst)
|
|
}
|
|
}
|
|
if err := os.Rename(f.Path, f.Path+".1"); err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
f.size = 0
|
|
return f.open()
|
|
}
|