Logger System 0.1.3
High-performance C++20 thread-safe logging system with asynchronous capabilities
Loading...
Searching...
No Matches
kcenon::logger::rotating_file_writer Class Reference

File writer with automatic log rotation support. More...

#include <rotating_file_writer.h>

Inheritance diagram for kcenon::logger::rotating_file_writer:
Inheritance graph
Collaboration diagram for kcenon::logger::rotating_file_writer:
Collaboration graph

Public Member Functions

 rotating_file_writer (const std::string &filename, size_t max_size, size_t max_files, size_t check_interval=100)
 Construct with size-based rotation.
 
 rotating_file_writer (const std::string &filename, rotation_type type, size_t max_files, size_t check_interval=100)
 Construct with time-based rotation.
 
 rotating_file_writer (const std::string &filename, rotation_type type, size_t max_size, size_t max_files, size_t check_interval=100)
 Construct with combined size and time rotation.
 
std::string get_name () const override
 Get writer name.
 
void rotate ()
 Manually trigger log rotation (thread-safe)
 
common::VoidResult write (const log_entry &entry) override
 Write operation with automatic rotation check.
 
- Public Member Functions inherited from kcenon::logger::file_writer
 file_writer (const std::string &filename, bool append=true, std::unique_ptr< log_formatter_interface > formatter=nullptr)
 Constructor.
 
 ~file_writer () override
 Destructor.
 
 file_writer (const file_writer &)=delete
 
file_writeroperator= (const file_writer &)=delete
 
 file_writer (file_writer &&)=delete
 
file_writeroperator= (file_writer &&)=delete
 
common::VoidResult flush () override
 Flush file stream.
 
common::VoidResult close () override
 Close the file.
 
bool is_open () const override
 Check if file is open.
 
bool is_healthy () const override
 Check if writer is healthy.
 
size_t get_file_size () const
 Get current file size.
 
- Public Member Functions inherited from kcenon::logger::log_writer_interface
virtual ~log_writer_interface ()=default
 

Private Member Functions

bool should_rotate () const
 Check if rotation should occur.
 
void perform_rotation ()
 Perform the actual rotation operation.
 
std::string generate_rotated_filename (int index=-1) const
 Generate filename for rotated log.
 
void cleanup_old_files ()
 Remove old backup files beyond max_files limit.
 
std::vector< std::string > get_backup_files () const
 Get list of existing backup files.
 
bool should_rotate_by_time () const
 Check if time-based rotation should occur.
 
std::size_t get_file_size () const
 Get current file size from filesystem.
 

Private Attributes

rotation_type rotation_type_
 
size_t max_size_
 
size_t max_files_
 
size_t check_interval_
 Number of writes between rotation checks.
 
size_t writes_since_check_ {0}
 Counter for writes since last check.
 
std::string base_filename_
 
std::string file_extension_
 
std::chrono::system_clock::time_point last_rotation_time_
 
std::chrono::system_clock::time_point current_period_start_
 

Additional Inherited Members

- Static Public Attributes inherited from kcenon::logger::sync_writer_tag
static constexpr writer_category category = writer_category::synchronous
 
- Protected Member Functions inherited from kcenon::logger::file_writer
std::string format_entry (const log_entry &entry) const
 Format a log entry using the current formatter.
 
common::VoidResult open_internal ()
 Open the file (internal, caller must hold mutex)
 
void close_internal ()
 Close the file (internal, caller must hold mutex)
 
std::mutex & get_mutex () const
 Access the writer mutex for extended operations.
 
- Protected Attributes inherited from kcenon::logger::file_writer
std::string filename_
 
bool append_mode_
 
std::ofstream file_stream_
 
std::atomic< bool > is_open_ {false}
 
std::atomic< size_t > bytes_written_ {0}
 
std::unique_ptr< log_formatter_interfaceformatter_
 
std::mutex mutex_
 

Detailed Description

File writer with automatic log rotation support.

This writer extends file_writer to add automatic log rotation based on file size, time intervals, or both. When rotation occurs, the current log file is renamed with a timestamp or index, and a new file is created.

Rotation strategies:

  • Size-based: Rotates when file reaches max_size bytes
  • Time-based: Rotates daily or hourly
  • Combined: Rotates when either condition is met

Thread Safety:

Category: Synchronous (blocking I/O, inherits from file_writer)

Since
1.0.0
1.3.0 Refactored to use thread_safe_writer via file_writer
1.4.0 Inherits sync_writer_tag from file_writer

Definition at line 60 of file rotating_file_writer.h.

Constructor & Destructor Documentation

◆ rotating_file_writer() [1/3]

kcenon::logger::rotating_file_writer::rotating_file_writer ( const std::string & filename,
size_t max_size,
size_t max_files,
size_t check_interval = 100 )

Construct with size-based rotation.

Parameters
filenamePath to the log file
max_sizeMaximum file size in bytes before rotation
max_filesMaximum number of backup files to keep
check_intervalNumber of writes between rotation checks (default: 100)

Definition at line 15 of file rotating_file_writer.cpp.

19 : file_writer(filename, true)
21 , max_size_(max_size)
22 , max_files_(max_files)
23 , check_interval_(check_interval)
24 , last_rotation_time_(std::chrono::system_clock::now())
25 , current_period_start_(std::chrono::system_clock::now()) {
26
27 // Extract base filename and extension
28 std::filesystem::path path(filename);
29 base_filename_ = path.stem().string();
30 file_extension_ = path.extension().string();
31 if (file_extension_.empty()) {
32 file_extension_ = ".log";
33 }
34}
file_writer(const std::string &filename, bool append=true, std::unique_ptr< log_formatter_interface > formatter=nullptr)
Constructor.
std::chrono::system_clock::time_point current_period_start_
std::chrono::system_clock::time_point last_rotation_time_
size_t check_interval_
Number of writes between rotation checks.
@ size
Rotate based on file size only.

References base_filename_, and file_extension_.

◆ rotating_file_writer() [2/3]

kcenon::logger::rotating_file_writer::rotating_file_writer ( const std::string & filename,
rotation_type type,
size_t max_files,
size_t check_interval = 100 )

Construct with time-based rotation.

Parameters
filenamePath to the log file
typeRotation type (daily or hourly)
max_filesMaximum number of backup files to keep
check_intervalNumber of writes between rotation checks (default: 100)

Definition at line 36 of file rotating_file_writer.cpp.

40 : file_writer(filename, true)
41 , rotation_type_(type)
42 , max_size_(0)
43 , max_files_(max_files)
44 , check_interval_(check_interval)
45 , last_rotation_time_(std::chrono::system_clock::now())
46 , current_period_start_(std::chrono::system_clock::now()) {
47
48 // Extract base filename and extension
49 std::filesystem::path path(filename);
50 base_filename_ = path.stem().string();
51 file_extension_ = path.extension().string();
52 if (file_extension_.empty()) {
53 file_extension_ = ".log";
54 }
55}

References base_filename_, and file_extension_.

◆ rotating_file_writer() [3/3]

kcenon::logger::rotating_file_writer::rotating_file_writer ( const std::string & filename,
rotation_type type,
size_t max_size,
size_t max_files,
size_t check_interval = 100 )

Construct with combined size and time rotation.

Parameters
filenamePath to the log file
typeMust be rotation_type::size_and_time
max_sizeMaximum file size in bytes before rotation
max_filesMaximum number of backup files to keep
check_intervalNumber of writes between rotation checks (default: 100)
Exceptions
std::invalid_argumentif type is not size_and_time

Definition at line 57 of file rotating_file_writer.cpp.

62 : file_writer(filename, true)
63 , rotation_type_(type)
64 , max_size_(max_size)
65 , max_files_(max_files)
66 , check_interval_(check_interval)
67 , last_rotation_time_(std::chrono::system_clock::now())
68 , current_period_start_(std::chrono::system_clock::now()) {
69
70 if (type != rotation_type::size_and_time) {
71 throw std::invalid_argument("This constructor is only for size_and_time rotation");
72 }
73
74 // Extract base filename and extension
75 std::filesystem::path path(filename);
76 base_filename_ = path.stem().string();
77 file_extension_ = path.extension().string();
78 if (file_extension_.empty()) {
79 file_extension_ = ".log";
80 }
81}
@ size_and_time
Rotate based on both size and time.

References base_filename_, file_extension_, and kcenon::logger::size_and_time.

Member Function Documentation

◆ cleanup_old_files()

void kcenon::logger::rotating_file_writer::cleanup_old_files ( )
private

Remove old backup files beyond max_files limit.

Definition at line 250 of file rotating_file_writer.cpp.

250 {
251 auto backup_files = get_backup_files();
252
253 if (backup_files.size() > max_files_) {
254 // Sort by modification time (oldest first)
255 std::sort(backup_files.begin(), backup_files.end(),
256 [](const std::string& a, const std::string& b) {
257 return std::filesystem::last_write_time(a) <
258 std::filesystem::last_write_time(b);
259 });
260
261 // Remove oldest files
262 size_t files_to_remove = backup_files.size() - max_files_;
263 for (size_t i = 0; i < files_to_remove; ++i) {
264 auto remove_result = utils::try_write_operation([&]() -> common::VoidResult {
265 std::filesystem::remove(backup_files[i]);
266 return common::ok();
268
269 if (remove_result.is_err()) {
270 std::cerr << "Failed to remove old log file: " << remove_result.error().message << std::endl;
271 }
272 }
273 }
274}
std::vector< std::string > get_backup_files() const
Get list of existing backup files.
VoidResult ok()
common::VoidResult try_write_operation(F &&operation, logger_error_code default_error_code=logger_error_code::file_write_failed)
Error handling helper for write operations.

References kcenon::logger::file_rotation_failed, get_backup_files(), max_files_, kcenon::common::ok(), and kcenon::logger::utils::try_write_operation().

Referenced by perform_rotation().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ generate_rotated_filename()

std::string kcenon::logger::rotating_file_writer::generate_rotated_filename ( int index = -1) const
private

Generate filename for rotated log.

Parameters
indexOptional index for size-based rotation (-1 for auto)

Definition at line 195 of file rotating_file_writer.cpp.

195 {
196 std::ostringstream oss;
197 std::filesystem::path dir = std::filesystem::path(filename_).parent_path();
198
199 if (!dir.empty()) {
200 oss << dir.string() << "/";
201 }
202
203 // Build: base + extension + separator + timestamp/index
204 // Example: "test.log.1" instead of "test.1.log"
206
207 // Add timestamp or index
208 auto now = std::chrono::system_clock::now();
209 auto time_t = std::chrono::system_clock::to_time_t(now);
210
211 // Use thread-safe time conversion
212 std::tm tm_buf{};
213#ifdef _WIN32
214 localtime_s(&tm_buf, &time_t); // Windows thread-safe version
215#else
216 localtime_r(&time_t, &tm_buf); // POSIX thread-safe version
217#endif
218
219 switch (rotation_type_) {
221 if (index >= 0) {
222 oss << "." << index;
223 } else {
224 // Find next available index
225 std::string base_with_ext = oss.str();
226 int next_index = 1;
227 while (std::filesystem::exists(base_with_ext + "." + std::to_string(next_index))) {
228 next_index++;
229 }
230 oss << "." << next_index;
231 }
232 break;
233
235 oss << "." << std::put_time(&tm_buf, "%Y%m%d");
236 break;
237
239 oss << "." << std::put_time(&tm_buf, "%Y%m%d_%H");
240 break;
241
243 oss << "." << std::put_time(&tm_buf, "%Y%m%d_%H%M%S");
244 break;
245 }
246
247 return oss.str();
248}
@ hourly
Rotate every hour.
@ daily
Rotate daily at midnight.

References base_filename_, kcenon::logger::daily, file_extension_, kcenon::logger::file_writer::filename_, kcenon::logger::hourly, rotation_type_, kcenon::logger::size, and kcenon::logger::size_and_time.

Referenced by perform_rotation().

Here is the caller graph for this function:

◆ get_backup_files()

std::vector< std::string > kcenon::logger::rotating_file_writer::get_backup_files ( ) const
private

Get list of existing backup files.

Definition at line 276 of file rotating_file_writer.cpp.

276 {
277 std::vector<std::string> files;
278 std::filesystem::path dir = std::filesystem::path(filename_).parent_path();
279 if (dir.empty()) {
280 dir = ".";
281 }
282
283 // Create regex pattern for backup files
284 // Pattern: base_filename + file_extension + "." + (number or timestamp)
285 // Example: "test_rotating.log.1" or "test.log.20250108"
286 std::string escaped_ext = std::regex_replace(file_extension_, std::regex(R"(\.)"), R"(\\.)");
287 std::string pattern = base_filename_ + escaped_ext + R"(\.(\d+|\d{8}|\d{8}_\d{2}|\d{8}_\d{6}))";
288 std::regex backup_regex(pattern);
289
290 // Use error handling utility for directory iteration
291 auto result = utils::try_write_operation([&]() -> common::VoidResult {
292 for (const auto& entry : std::filesystem::directory_iterator(dir)) {
293 if (entry.is_regular_file()) {
294 std::string filename = entry.path().filename().string();
295 if (std::regex_match(filename, backup_regex)) {
296 files.push_back(entry.path().string());
297 }
298 }
299 }
300 return common::ok();
302
303 if (result.is_err()) {
304 std::cerr << "Error listing backup files: " << result.error().message << std::endl;
305 }
306
307 return files;
308}

References base_filename_, file_extension_, kcenon::logger::file_rotation_failed, kcenon::logger::file_writer::filename_, kcenon::common::ok(), and kcenon::logger::utils::try_write_operation().

Referenced by cleanup_old_files().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_file_size()

std::size_t kcenon::logger::rotating_file_writer::get_file_size ( ) const
private

Get current file size from filesystem.

Definition at line 347 of file rotating_file_writer.cpp.

347 {
348 // IMPORTANT: This method should only be called while holding the mutex
349 // to avoid race conditions with concurrent writes and file rotation.
350 //
351 // Race condition example:
352 // - Thread A: Calls should_rotate() -> get_file_size() reads filesystem
353 // - Thread B: Simultaneously writing, bytes_written_ is updating
354 // - Result: get_file_size() returns stale data, rotation check is incorrect
355 //
356 // Better approach: Always use bytes_written_.load() which is atomic and
357 // thread-safe. Only call filesystem functions during actual rotation when
358 // the caller already holds the mutex.
359
360 if (!file_stream_.is_open()) {
361 return 0;
362 }
363
364 // Prefer atomic counter for thread safety
365 // Only fall back to filesystem if absolutely necessary
366 std::size_t atomic_size = bytes_written_.load(std::memory_order_relaxed);
367
368 // Optional: Validate with filesystem size for debugging
369 // (Remove in production for performance)
370 #ifdef DEBUG_FILE_SIZE_VALIDATION
371 std::error_code ec;
372 auto fs_size = std::filesystem::file_size(filename_, ec);
373 if (!ec && fs_size != atomic_size) {
374 // Log discrepancy for debugging
375 std::cerr << "File size mismatch: atomic=" << atomic_size
376 << " filesystem=" << fs_size << std::endl;
377 }
378 #endif
379
380 return atomic_size;
381}
std::atomic< size_t > bytes_written_

References kcenon::logger::file_writer::bytes_written_, kcenon::logger::file_writer::file_stream_, and kcenon::logger::file_writer::filename_.

Referenced by should_rotate().

Here is the caller graph for this function:

◆ get_name()

std::string kcenon::logger::rotating_file_writer::get_name ( ) const
inlineoverridevirtual

Get writer name.

Reimplemented from kcenon::logger::file_writer.

Definition at line 104 of file rotating_file_writer.h.

104{ return "rotating_file"; }

◆ perform_rotation()

void kcenon::logger::rotating_file_writer::perform_rotation ( )
private

Perform the actual rotation operation.

Note
Caller must hold the mutex

Definition at line 137 of file rotating_file_writer.cpp.

137 {
138 // IMPORTANT: Caller must hold the mutex before calling this method
139 // This ensures thread safety for all file operations and mutable state modifications
140
141 // Close current file
142 if (file_stream_.is_open()) {
143 file_stream_.flush();
144 file_stream_.close();
145 }
146
147 // Generate new filename for the current log
148 std::string rotated_name = generate_rotated_filename();
149
150 // Rename current file (with error handling)
151 auto rename_result = utils::try_write_operation([&]() -> common::VoidResult {
152 if (std::filesystem::exists(filename_)) {
153 std::filesystem::rename(filename_, rotated_name);
154 }
155 return common::ok();
157
158 if (rename_result.is_err()) {
159 std::cerr << "Failed to rotate log file: " << rename_result.error().message << std::endl;
160 }
161
162 // Clean up old files
164
165 // Open new file (with error handling)
166 auto open_result = utils::try_open_operation([&]() -> common::VoidResult {
167 std::filesystem::path file_path(filename_);
168 std::filesystem::path dir = file_path.parent_path();
169
170 auto dir_result = utils::ensure_directory_exists(dir);
171 if (dir_result.is_err()) return dir_result;
172
173 auto mode = append_mode_ ? std::ios::app : std::ios::trunc;
174 file_stream_.open(filename_, std::ios::out | mode);
175
176 if (file_stream_.is_open()) {
177 bytes_written_ = 0;
178 }
179
180 return common::ok();
181 });
182
183 if (open_result.is_err()) {
184 std::cerr << "Failed to open new log file: " << open_result.error().message << std::endl;
185 }
186
187 // Update rotation time - protected by mutex
188 last_rotation_time_ = std::chrono::system_clock::now();
190
191 // Reset write counter after rotation
193}
size_t writes_since_check_
Counter for writes since last check.
void cleanup_old_files()
Remove old backup files beyond max_files limit.
std::string generate_rotated_filename(int index=-1) const
Generate filename for rotated log.
common::VoidResult try_open_operation(F &&operation)
Error handling helper for file open operations.
common::VoidResult ensure_directory_exists(const std::filesystem::path &dir)
Directory creation helper.

References kcenon::logger::file_writer::append_mode_, kcenon::logger::file_writer::bytes_written_, cleanup_old_files(), current_period_start_, kcenon::logger::utils::ensure_directory_exists(), kcenon::logger::file_rotation_failed, kcenon::logger::file_writer::file_stream_, kcenon::logger::file_writer::filename_, generate_rotated_filename(), last_rotation_time_, kcenon::common::ok(), kcenon::logger::utils::try_open_operation(), kcenon::logger::utils::try_write_operation(), and writes_since_check_.

Referenced by rotate(), and write().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ rotate()

void kcenon::logger::rotating_file_writer::rotate ( )

Manually trigger log rotation (thread-safe)

Definition at line 114 of file rotating_file_writer.cpp.

114 {
115 // Public API for manual rotation
116 std::lock_guard<std::mutex> lock(get_mutex());
118}
std::mutex & get_mutex() const
Access the writer mutex for extended operations.
void perform_rotation()
Perform the actual rotation operation.

References kcenon::logger::file_writer::get_mutex(), and perform_rotation().

Here is the call graph for this function:

◆ should_rotate()

bool kcenon::logger::rotating_file_writer::should_rotate ( ) const
private

Check if rotation should occur.

Definition at line 120 of file rotating_file_writer.cpp.

120 {
121 switch (rotation_type_) {
123 return get_file_size() >= max_size_;
124
127 return should_rotate_by_time();
128
131
132 default:
133 return false;
134 }
135}
bool should_rotate_by_time() const
Check if time-based rotation should occur.
std::size_t get_file_size() const
Get current file size from filesystem.

References kcenon::logger::daily, get_file_size(), kcenon::logger::hourly, max_size_, rotation_type_, should_rotate_by_time(), kcenon::logger::size, and kcenon::logger::size_and_time.

Referenced by write().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ should_rotate_by_time()

bool kcenon::logger::rotating_file_writer::should_rotate_by_time ( ) const
private

Check if time-based rotation should occur.

Definition at line 310 of file rotating_file_writer.cpp.

310 {
311 auto now = std::chrono::system_clock::now();
312
313 switch (rotation_type_) {
316 // Check if we're in a new day
317 auto now_time_t = std::chrono::system_clock::to_time_t(now);
318 auto start_time_t = std::chrono::system_clock::to_time_t(current_period_start_);
319
320 // Use thread-safe time conversion
321 std::tm now_tm{};
322 std::tm start_tm{};
323#ifdef _WIN32
324 localtime_s(&now_tm, &now_time_t);
325 localtime_s(&start_tm, &start_time_t);
326#else
327 localtime_r(&now_time_t, &now_tm);
328 localtime_r(&start_time_t, &start_tm);
329#endif
330
331 return now_tm.tm_year != start_tm.tm_year ||
332 now_tm.tm_mon != start_tm.tm_mon ||
333 now_tm.tm_mday != start_tm.tm_mday;
334 }
335
337 // Check if we're in a new hour
338 auto duration = now - current_period_start_;
339 return duration >= std::chrono::hours(1);
340 }
341
342 default:
343 return false;
344 }
345}

References current_period_start_, kcenon::logger::daily, kcenon::logger::hourly, rotation_type_, and kcenon::logger::size_and_time.

Referenced by should_rotate().

Here is the caller graph for this function:

◆ write()

common::VoidResult kcenon::logger::rotating_file_writer::write ( const log_entry & entry)
overridevirtual

Write operation with automatic rotation check.

Parameters
entryThe log entry to write (includes structured fields)
Since
3.4.0
3.5.0 This is now the only write implementation (legacy API removed)
4.0.0 Changed to override write() directly after Decorator pattern refactoring

Reimplemented from kcenon::logger::file_writer.

Definition at line 83 of file rotating_file_writer.cpp.

83 {
84 std::lock_guard<std::mutex> lock(get_mutex());
85
86 // Check precondition
87 if (!file_stream_.is_open()) {
88 return make_logger_void_result(logger_error_code::file_write_failed, "File stream is not open");
89 }
90
91 // Format and write - preserves all structured fields
92 std::string formatted = format_entry(entry);
93 file_stream_ << formatted << '\n';
94 bytes_written_.fetch_add(formatted.size() + 1);
95
96 // Verify stream state
97 if (file_stream_.fail()) {
99 }
100
101 // Periodic rotation check optimization (Phase 2)
103
105 if (should_rotate()) {
107 }
109 }
110
111 return common::ok();
112}
std::string format_entry(const log_entry &entry) const
Format a log entry using the current formatter.
bool should_rotate() const
Check if rotation should occur.
common::VoidResult make_logger_void_result(logger_error_code code, const std::string &message="")

References kcenon::logger::file_writer::bytes_written_, check_interval_, kcenon::logger::file_writer::file_stream_, kcenon::logger::file_write_failed, kcenon::logger::file_writer::format_entry(), kcenon::logger::file_writer::get_mutex(), kcenon::logger::make_logger_void_result(), kcenon::common::ok(), perform_rotation(), should_rotate(), and writes_since_check_.

Here is the call graph for this function:

Member Data Documentation

◆ base_filename_

std::string kcenon::logger::rotating_file_writer::base_filename_
private

◆ check_interval_

size_t kcenon::logger::rotating_file_writer::check_interval_
private

Number of writes between rotation checks.

Definition at line 163 of file rotating_file_writer.h.

Referenced by write().

◆ current_period_start_

std::chrono::system_clock::time_point kcenon::logger::rotating_file_writer::current_period_start_
private

Definition at line 168 of file rotating_file_writer.h.

Referenced by perform_rotation(), and should_rotate_by_time().

◆ file_extension_

std::string kcenon::logger::rotating_file_writer::file_extension_
private

◆ last_rotation_time_

std::chrono::system_clock::time_point kcenon::logger::rotating_file_writer::last_rotation_time_
private

Definition at line 167 of file rotating_file_writer.h.

Referenced by perform_rotation().

◆ max_files_

size_t kcenon::logger::rotating_file_writer::max_files_
private

Definition at line 162 of file rotating_file_writer.h.

Referenced by cleanup_old_files().

◆ max_size_

size_t kcenon::logger::rotating_file_writer::max_size_
private

Definition at line 161 of file rotating_file_writer.h.

Referenced by should_rotate().

◆ rotation_type_

rotation_type kcenon::logger::rotating_file_writer::rotation_type_
private

◆ writes_since_check_

size_t kcenon::logger::rotating_file_writer::writes_since_check_ {0}
private

Counter for writes since last check.

Definition at line 164 of file rotating_file_writer.h.

164{0};

Referenced by perform_rotation(), and write().


The documentation for this class was generated from the following files: