feat: add error callback for file output writer
Some checks failed
Build RPM Package / Build RPM Packages (CentOS 7, Rocky 8/9/10) (push) Has been cancelled

- Add FileWriterOption type and WithFileErrorCallback option
- Add reportError method to FileWriter for error reporting
- Update Builder to propagate error callback to file writers
- File write errors now logged via the same callback mechanism
- Helps diagnose permission or disk space issues

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Jacquin Antoine
2026-03-03 00:02:11 +01:00
parent babf254215
commit 76e68d15d9

View File

@ -61,13 +61,26 @@ func (w *StdoutWriter) Close() error {
// FileWriter writes log records to a file with rotation support // FileWriter writes log records to a file with rotation support
type FileWriter struct { type FileWriter struct {
file *os.File file *os.File
encoder *json.Encoder encoder *json.Encoder
mutex sync.Mutex mutex sync.Mutex
path string path string
maxSize int64 maxSize int64
maxBackups int maxBackups int
currentSize int64 currentSize int64
errorCallback ErrorCallback
failuresMu sync.Mutex
failures int
}
// FileWriterOption is a function type for configuring FileWriter
type FileWriterOption func(*FileWriter)
// WithFileErrorCallback sets an error callback for file write errors
func WithFileErrorCallback(cb ErrorCallback) FileWriterOption {
return func(w *FileWriter) {
w.errorCallback = cb
}
} }
// NewFileWriter creates a new file writer with rotation // NewFileWriter creates a new file writer with rotation
@ -76,7 +89,7 @@ func NewFileWriter(path string) (*FileWriter, error) {
} }
// NewFileWriterWithConfig creates a new file writer with custom rotation config // NewFileWriterWithConfig creates a new file writer with custom rotation config
func NewFileWriterWithConfig(path string, maxSize int64, maxBackups int) (*FileWriter, error) { func NewFileWriterWithConfig(path string, maxSize int64, maxBackups int, opts ...FileWriterOption) (*FileWriter, error) {
// Create directory if it doesn't exist // Create directory if it doesn't exist
dir := filepath.Dir(path) dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil { if err := os.MkdirAll(dir, 0755); err != nil {
@ -96,14 +109,21 @@ func NewFileWriterWithConfig(path string, maxSize int64, maxBackups int) (*FileW
return nil, fmt.Errorf("failed to stat file: %w", err) return nil, fmt.Errorf("failed to stat file: %w", err)
} }
return &FileWriter{ w := &FileWriter{
file: file, file: file,
encoder: json.NewEncoder(file), encoder: json.NewEncoder(file),
path: path, path: path,
maxSize: maxSize, maxSize: maxSize,
maxBackups: maxBackups, maxBackups: maxBackups,
currentSize: info.Size(), currentSize: info.Size(),
}, nil }
// Apply options (for error callback)
for _, opt := range opts {
opt(w)
}
return w, nil
} }
// rotate rotates the log file if it exceeds the max size // rotate rotates the log file if it exceeds the max size
@ -149,6 +169,7 @@ func (w *FileWriter) Write(rec api.LogRecord) error {
// Check if rotation is needed // Check if rotation is needed
if w.currentSize >= w.maxSize { if w.currentSize >= w.maxSize {
if err := w.rotate(); err != nil { if err := w.rotate(); err != nil {
w.reportError(fmt.Errorf("failed to rotate file: %w", err))
return fmt.Errorf("failed to rotate file: %w", err) return fmt.Errorf("failed to rotate file: %w", err)
} }
} }
@ -163,6 +184,7 @@ func (w *FileWriter) Write(rec api.LogRecord) error {
// Write to file // Write to file
n, err := w.file.Write(data) n, err := w.file.Write(data)
if err != nil { if err != nil {
w.reportError(fmt.Errorf("failed to write to file: %w", err))
return fmt.Errorf("failed to write to file: %w", err) return fmt.Errorf("failed to write to file: %w", err)
} }
w.currentSize += int64(n) w.currentSize += int64(n)
@ -170,6 +192,17 @@ func (w *FileWriter) Write(rec api.LogRecord) error {
return nil return nil
} }
// reportError reports a file write error via the configured callback
func (w *FileWriter) reportError(err error) {
if w.errorCallback != nil {
w.failuresMu.Lock()
w.failures++
failures := w.failures
w.failuresMu.Unlock()
w.errorCallback(w.path, err, failures)
}
}
// Close closes the file // Close closes the file
func (w *FileWriter) Close() error { func (w *FileWriter) Close() error {
w.mutex.Lock() w.mutex.Lock()
@ -531,7 +564,7 @@ func NewBuilder() *BuilderImpl {
return &BuilderImpl{} return &BuilderImpl{}
} }
// WithErrorCallback sets an error callback for all unix_socket writers created by this builder // WithErrorCallback sets an error callback for all unix_socket and file writers created by this builder
func (b *BuilderImpl) WithErrorCallback(cb ErrorCallback) *BuilderImpl { func (b *BuilderImpl) WithErrorCallback(cb ErrorCallback) *BuilderImpl {
b.errorCallback = cb b.errorCallback = cb
return b return b
@ -564,7 +597,12 @@ func (b *BuilderImpl) NewFromConfig(cfg api.AppConfig) (api.Writer, error) {
if path == "" { if path == "" {
return nil, fmt.Errorf("file output requires 'path' parameter") return nil, fmt.Errorf("file output requires 'path' parameter")
} }
writer, err = NewFileWriter(path) // Build options list for file writer
var fileOpts []FileWriterOption
if b.errorCallback != nil {
fileOpts = append(fileOpts, WithFileErrorCallback(b.errorCallback))
}
writer, err = NewFileWriterWithConfig(path, DefaultMaxFileSize, DefaultMaxBackups, fileOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }