From bca3cd84d1e08dd91a3151307636fb29edd24333 Mon Sep 17 00:00:00 2001 From: yusing Date: Tue, 29 Apr 2025 00:46:30 +0800 Subject: [PATCH] fix(accesslog): os: invalid use of WriteAt on file opened with O_APPEND --- internal/logging/accesslog/access_logger.go | 6 +++ internal/logging/accesslog/file_logger.go | 43 +++++++++++++++++---- internal/logging/accesslog/rotate.go | 19 +++++++-- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/internal/logging/accesslog/access_logger.go b/internal/logging/accesslog/access_logger.go index c5feb28..14f2f55 100644 --- a/internal/logging/accesslog/access_logger.go +++ b/internal/logging/accesslog/access_logger.go @@ -43,6 +43,12 @@ type ( Name() string // file name or path } + SupportRotate interface { + io.Writer + supportRotate + Name() string + } + RequestFormatter interface { // AppendRequestLog appends a log line to line with or without a trailing newline AppendRequestLog(line []byte, req *http.Request, res *http.Response) []byte diff --git a/internal/logging/accesslog/file_logger.go b/internal/logging/accesslog/file_logger.go index b1691a3..f887271 100644 --- a/internal/logging/accesslog/file_logger.go +++ b/internal/logging/accesslog/file_logger.go @@ -2,8 +2,9 @@ package accesslog import ( "fmt" + "io" "os" - pathPkg "path" + "path/filepath" "sync" "github.com/yusing/go-proxy/internal/logging" @@ -11,7 +12,7 @@ import ( ) type File struct { - *os.File + f *os.File // os.File.Name() may not equal to key of `openedFiles`. // Store it for later delete from `openedFiles`. @@ -25,21 +26,25 @@ var ( openedFilesMu sync.Mutex ) -func newFileIO(path string) (WriterWithName, error) { +func newFileIO(path string) (SupportRotate, error) { openedFilesMu.Lock() defer openedFilesMu.Unlock() var file *File - path = pathPkg.Clean(path) + path = filepath.Clean(path) if opened, ok := openedFiles[path]; ok { opened.refCount.Add() return opened, nil } else { - f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0o644) + // cannot open as O_APPEND as we need Seek and WriteAt + f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o644) if err != nil { return nil, fmt.Errorf("access log open error: %w", err) } - file = &File{File: f, path: path, refCount: utils.NewRefCounter()} + if _, err := f.Seek(0, io.SeekEnd); err != nil { + return nil, fmt.Errorf("access log seek error: %w", err) + } + file = &File{f: f, path: path, refCount: utils.NewRefCounter()} openedFiles[path] = file go file.closeOnZero() } @@ -47,6 +52,30 @@ func newFileIO(path string) (WriterWithName, error) { return file, nil } +func (f *File) Name() string { + return f.f.Name() +} + +func (f *File) Write(p []byte) (n int, err error) { + return f.f.Write(p) +} + +func (f *File) ReadAt(p []byte, off int64) (n int, err error) { + return f.f.ReadAt(p, off) +} + +func (f *File) WriteAt(p []byte, off int64) (n int, err error) { + return f.f.WriteAt(p, off) +} + +func (f *File) Seek(offset int64, whence int) (int64, error) { + return f.f.Seek(offset, whence) +} + +func (f *File) Truncate(size int64) error { + return f.f.Truncate(size) +} + func (f *File) Close() error { f.refCount.Sub() return nil @@ -62,5 +91,5 @@ func (f *File) closeOnZero() { openedFilesMu.Lock() delete(openedFiles, f.path) openedFilesMu.Unlock() - f.File.Close() + f.f.Close() } diff --git a/internal/logging/accesslog/rotate.go b/internal/logging/accesslog/rotate.go index f21348c..b004472 100644 --- a/internal/logging/accesslog/rotate.go +++ b/internal/logging/accesslog/rotate.go @@ -12,7 +12,7 @@ import ( ) type supportRotate interface { - io.ReadSeeker + io.Seeker io.ReaderAt io.WriterAt Truncate(size int64) error @@ -66,9 +66,23 @@ var rotateBytePool = synk.NewBytesPool(0, 16*1024*1024) // If the file does not need to be rotated, it returns nil, nil. func rotateLogFile(file supportRotate, config *Retention) (result *RotateResult, err error) { if config.KeepSize > 0 { - return rotateLogFileBySize(file, config) + result, err = rotateLogFileBySize(file, config) + } else { + result, err = rotateLogFileByPolicy(file, config) } + if err != nil { + return nil, err + } + + if _, err := file.Seek(0, io.SeekEnd); err != nil { + return nil, err + } + + return result, nil +} + +func rotateLogFileByPolicy(file supportRotate, config *Retention) (result *RotateResult, err error) { var shouldStop func() bool t := utils.TimeNow() @@ -234,7 +248,6 @@ var timeJSON = []byte(`"time":"`) // // The returned time is not validated. func ExtractTime(line []byte) []byte { - //TODO: optimize this switch line[0] { case '{': // JSON format if i := bytes.Index(line, timeJSON); i != -1 {