Overview
The FileHandler currently re-opens the log file on each flush, resulting in unnecessary syscalls and potential contention. This issue proposes mirroring the JsonHandler implementation by keeping a Mutex<BufWriter<File>> open for the lifetime of the handler, writing and flushing without reopening.
Current State
The FileHandler reopens the log file on every flush:
// crates/lambda-rs-logging/src/handler.rs:71-79letmut file = OpenOptions::new().append(true).create(true).open(self.file.clone())// Reopened on every flush.unwrap();
file
.write_all(log_message.as_bytes()).expect("Unable to write data");This results in:
- Repeated
open() syscalls on every log flush - Potential file descriptor exhaustion under heavy logging
- Contention when multiple threads attempt to log simultaneously
- Unnecessary overhead compared to keeping a persistent file handle
In contrast, JsonHandler already maintains a persistent file handle wrapped in Mutex<BufWriter<File>>, demonstrating the preferred pattern.
Scope
Goals:
- Refactor
FileHandler to keep the log file open for the handler's lifetime - Use
Mutex<BufWriter<File>> pattern consistent with JsonHandler - Reduce syscall overhead and improve logging throughput
- Maintain thread-safety for concurrent logging
Non-Goals:
- Log rotation (separate concern, can be built on top of this)
- Async file I/O
- Changing the public
FileHandler API
Proposed API
No public API changes required. The refactoring is internal to FileHandler:
// crates/lambda-rs-logging/src/handler.rsuse std::fs::{File,OpenOptions};use std::io::{BufWriter,Write};use std::sync::Mutex;pubstructFileHandler{/// Persistent buffered file handlewriter:Mutex<BufWriter<File>>,// ... other fields}implFileHandler{pubfnnew(path:implAsRef<Path>) -> std::io::Result<Self>{let file = OpenOptions::new().create(true).append(true).open(path)?;Ok(Self{writer:Mutex::new(BufWriter::new(file)),})}}implHandlerforFileHandler{fnhandle(&self,record:&Record){let formatted = self.format(record);ifletOk(mut writer) = self.writer.lock(){let _ = writeln!(writer,"{}", formatted);// Flush can be deferred or done periodically for better performance}}fnflush(&self){ifletOk(mut writer) = self.writer.lock(){let _ = writer.flush();}}}Acceptance Criteria
Affected Crates
lambda-rs-logging
Notes
- Consider whether
flush() should be called after every handle() or batched for better performance - File handle will be held for the lifetime of the handler; document that log rotation requires handler recreation
Overview
The
FileHandlercurrently re-opens the log file on each flush, resulting in unnecessary syscalls and potential contention. This issue proposes mirroring theJsonHandlerimplementation by keeping aMutex<BufWriter<File>>open for the lifetime of the handler, writing and flushing without reopening.Current State
The
FileHandlerreopens the log file on every flush:This results in:
open()syscalls on every log flushIn contrast,
JsonHandleralready maintains a persistent file handle wrapped inMutex<BufWriter<File>>, demonstrating the preferred pattern.Scope
Goals:
FileHandlerto keep the log file open for the handler's lifetimeMutex<BufWriter<File>>pattern consistent withJsonHandlerNon-Goals:
FileHandlerAPIProposed API
No public API changes required. The refactoring is internal to
FileHandler:Acceptance Criteria
FileHandlerstores aMutex<BufWriter<File>>instead of reopening on each flushFileHandlerconstructionhandle()method writes to the buffered writer without reopeningflush()method flushes the buffered writer without reopeningMutexAffected Crates
lambda-rs-logging
Notes
flush()should be called after everyhandle()or batched for better performance