LogIt++
Loading...
Searching...
No Matches
FileLogger.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef _LOGIT_FILE_LOGGER_HPP_INCLUDED
3#define _LOGIT_FILE_LOGGER_HPP_INCLUDED
4
7
8#include "ILogger.hpp"
9#include <iostream>
10#include <fstream>
11#include <mutex>
12#include <atomic>
13#include <regex>
14#include <queue>
15#include <functional>
16#include <utility>
17#include <vector>
18#include <algorithm>
19#include <cstdlib>
20#include <cstdio>
21#include <time_shield/time_parser.hpp>
22
23namespace logit {
24
25#if defined(__EMSCRIPTEN__)
26
27 class FileLogger : public ILogger {
28 public:
29 struct Config {
30 std::string directory = "logs";
31 bool async = false;
32 int auto_delete_days = 30;
33 uint64_t max_file_size_bytes = 0;
34 uint32_t max_rotated_files = 0;
35 bool compress_rotated = false;
36 std::string compress_cmd;
37 };
38
39 FileLogger() { warn(); }
40 FileLogger(const Config&) { warn(); }
41 FileLogger(const std::string&, const bool& = true, const int& = 30,
42 const uint64_t& = 0, const uint32_t& = 0,
43 const bool& = false, std::string = {}) { warn(); }
44
45 void log(const LogRecord&, const std::string&) override { warn(); }
46 std::string get_string_param(const LoggerParam&) const override { return {}; }
47 int64_t get_int_param(const LoggerParam&) const override { return 0; }
48 double get_float_param(const LoggerParam&) const override { return 0.0; }
49 void set_log_level(LogLevel) override {}
50 LogLevel get_log_level() const override { return LogLevel::LOG_LVL_TRACE; }
51 void wait() override {}
52
53 private:
54 void warn() const { std::cerr << "FileLogger is not supported under Emscripten" << std::endl; }
55 std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
56 };
57
58#else
59
71 class FileLogger : public ILogger {
72 public:
73
76 struct Config {
77 std::string directory = "logs";
78 bool async = true;
80 uint64_t max_file_size_bytes = 0;
81 uint32_t max_rotated_files = 0;
82 bool compress_rotated = false;
83 std::string compress_cmd;
84 };
85
89 }
90
93 FileLogger(const Config& config) : m_config(config) {
95 }
96
102 const std::string& directory,
103 const bool& async = true,
104 const int& auto_delete_days = 30) {
105 m_config.directory = directory;
106 m_config.async = async;
107 m_config.auto_delete_days = auto_delete_days;
109 }
110
113 const std::string& directory,
114 const bool& async,
115 const int& auto_delete_days,
116 uint64_t max_file_size_bytes,
117 uint32_t max_rotated_files,
118 bool compress_rotated = false,
119 std::string compress_cmd = {}) {
120 m_config.directory = directory;
121 m_config.async = async;
122 m_config.auto_delete_days = auto_delete_days;
123 m_config.max_file_size_bytes = max_file_size_bytes;
124 m_config.max_rotated_files = max_rotated_files;
125 m_config.compress_rotated = compress_rotated;
126 m_config.compress_cmd = std::move(compress_cmd);
128 }
129
131 virtual ~FileLogger() {
132 stop_logging();
133 }
134
142 void log(const LogRecord& record, const std::string& message) override {
144 if (!m_config.async) {
145 std::lock_guard<std::mutex> lock(m_mutex);
146 try {
147 write_log(message, record.timestamp_ms);
148 } catch (const std::exception& e) {
149 std::cerr << "Log error: " << e.what() << std::endl;
150 }
151 return;
152 }
153 auto timestamp_ms = record.timestamp_ms;
154 detail::TaskExecutor::get_instance().add_task([this, message, timestamp_ms]() {
155 std::lock_guard<std::mutex> lock(m_mutex);
156 try {
157 write_log(message, timestamp_ms);
158 } catch (const std::exception& e) {
159 std::cerr << "Log async log error: " << e.what() << std::endl;
160 }
161 });
162 }
163
167 std::string get_string_param(const LoggerParam& param) const override {
168 switch (param) {
171 case LoggerParam::LastLogTimestamp: return std::to_string(get_last_log_ts());
172 case LoggerParam::TimeSinceLastLog: return std::to_string(get_time_since_last_log());
173 default:
174 break;
175 };
176 return std::string();
177 }
178
182 int64_t get_int_param(const LoggerParam& param) const override {
183 switch (param) {
186 default:
187 break;
188 };
189 return 0;
190 }
191
195 double get_float_param(const LoggerParam& param) const override {
196 switch (param) {
197 case LoggerParam::LastLogTimestamp: return (double)get_last_log_ts() / 1000.0;
198 case LoggerParam::TimeSinceLastLog: return (double)get_time_since_last_log() / 1000.0;
199 default:
200 break;
201 };
202 return 0.0;
203 }
204
206 void set_log_level(LogLevel level) override {
207 m_log_level = static_cast<int>(level);
208 }
209
211 LogLevel get_log_level() const override {
212 return static_cast<LogLevel>(m_log_level.load());
213 }
214
216 void wait() override {
217 if (!m_config.async) return;
219 }
220
221 private:
222 mutable std::mutex m_mutex;
224 std::ofstream m_file;
225 mutable std::mutex m_file_path_mutex;
226 std::string m_file_path;
227 std::string m_file_name;
228 int64_t m_current_date_ts = 0;
229 uint64_t m_current_file_size = 0;
230 std::atomic<int64_t> m_last_log_ts = ATOMIC_VAR_INIT(0);
231 std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
232
235 // I/O streams (e.g., std::cin, std::cout, std::cerr) may be closed before the program exits.
236 // In this case, calls to functions that use I/O streams (for example, the std::regex constructor)
237 // can lead to undesirable behavior such as hangs or segmentation faults.
238 is_valid_log_filename("2024-01-01.log");
239 std::lock_guard<std::mutex> lock(m_mutex);
240 try {
244 } catch (const std::exception& e) {
245 std::cerr << "Initialization error: " << e.what() << std::endl;
246 }
247 }
248
251 wait();
252 std::lock_guard<std::mutex> lock(m_mutex);
253 if (m_file.is_open()) {
254 m_file.close();
255 }
256 }
257
262
265 std::string get_directory_path() const {
266# if defined(_WIN32)
267 return get_exec_dir() + "\\" + m_config.directory;
268# else
269 return get_exec_dir() + "/" + m_config.directory;
270# endif
271 }
272
275 void open_log_file(const int64_t& date_ts) {
276 if (m_file.is_open()) {
277 m_file.close();
278 }
279 m_current_date_ts = date_ts;
280 std::unique_lock<std::mutex> lock(m_file_path_mutex);
281 m_file_path = create_file_path(date_ts);
283 lock.unlock();
284# if defined(_WIN32)
285 m_file.open(utf8_to_ansi(m_file_path), std::ios_base::app);
286# else
287 m_file.open(m_file_path, std::ios_base::app);
288# endif
289 if (!m_file.is_open()) {
290 throw std::runtime_error("Failed to open log file: " + m_file_path);
291 }
292 m_file.seekp(0, std::ios::end);
293 m_current_file_size = static_cast<uint64_t>(m_file.tellp());
294 }
295
299 std::string create_file_path(int64_t date_ts) const {
300 std::string date_str = time_shield::to_iso8601_date(date_ts);
301 return get_directory_path() + "/" + date_str + ".log";
302 }
303
307 void write_log(const std::string& message, const int64_t& timestamp_ms) {
308 const int64_t message_date_ts = time_shield::start_of_day(time_shield::ms_to_sec(timestamp_ms));
309 if (message_date_ts != m_current_date_ts) {
310 open_log_file(message_date_ts);
311 }
312 if (m_config.max_file_size_bytes > 0) {
313 const uint64_t add = static_cast<uint64_t>(message.size() + 1);
314 if (m_current_file_size + add > m_config.max_file_size_bytes) {
316 }
317 }
318 if (m_file.is_open()) {
319 m_file << message << std::endl;
320 m_current_file_size += static_cast<uint64_t>(message.size() + 1);
321 }
323 }
324
326 if (m_file.is_open()) m_file.close();
327
328 const std::string base = time_shield::to_iso8601_date(m_current_date_ts);
329 const std::string dir = get_directory_path();
330 const std::string cur = dir + "/" + base + ".log";
331 std::string rotated;
332 uint32_t idx = 1;
333# if __cplusplus >= 201703L
334 for (;; ++idx) {
335 rotated = dir + "/" + base + "." + std::to_string(idx) + ".log";
336 if (!fs::exists(rotated)) break;
337 }
338# else
339 auto file_exists = [](const std::string& path) {
340# if defined(_WIN32)
341 std::ifstream f(utf8_to_ansi(path).c_str());
342# else
343 std::ifstream f(path.c_str());
344# endif
345 return f.good();
346 };
347 for (;; ++idx) {
348 rotated = dir + "/" + base + "." + std::to_string(idx) + ".log";
349 if (!file_exists(rotated)) break;
350 }
351# endif
352# if defined(_WIN32)
353 std::rename(utf8_to_ansi(cur).c_str(), utf8_to_ansi(rotated).c_str());
354# else
355 std::rename(cur.c_str(), rotated.c_str());
356# endif
357
358 if (m_config.compress_rotated && !m_config.compress_cmd.empty()) {
359 std::string cmd = m_config.compress_cmd;
360 size_t pos = cmd.find("{file}");
361 if (pos != std::string::npos) cmd.replace(pos, 6, "\"" + rotated + "\"");
362 std::system(cmd.c_str());
363 }
364
365 if (m_config.max_rotated_files > 0) {
366 enforce_rotation_retention(base, m_config.max_rotated_files, dir);
367 }
368
371 }
372
373 void enforce_rotation_retention(const std::string& base, uint32_t max_files, const std::string& dir) {
374# if __cplusplus >= 201703L
375 std::vector<std::pair<uint32_t, fs::path>> files;
376 std::regex pattern(base + R"(\.(\d+)\.log(\..*)?)");
377 for (const auto& entry : fs::directory_iterator(dir)) {
378 if (!fs::is_regular_file(entry.status())) continue;
379 std::smatch m;
380 std::string name = entry.path().filename().string();
381 if (std::regex_match(name, m, pattern)) {
382 uint32_t idx = static_cast<uint32_t>(std::stoul(m[1].str()));
383 files.emplace_back(idx, entry.path());
384 }
385 }
386 if (files.size() <= max_files) return;
387 std::sort(files.begin(), files.end(), [](const std::pair<uint32_t, fs::path>& a, const std::pair<uint32_t, fs::path>& b) { return a.first < b.first; });
388 size_t to_remove = files.size() - max_files;
389 for (size_t i = 0; i < to_remove; ++i) {
390 fs::remove(files[i].second);
391 }
392# else
393 std::vector<std::pair<uint32_t, std::string>> files;
394 std::regex pattern(base + R"(\.(\d+)\.log(\..*)?)");
395 std::vector<std::string> file_list = get_list_files(dir);
396 for (const auto& path : file_list) {
397 std::string name = path.substr(path.find_last_of("/\\") + 1);
398 std::smatch m;
399 if (std::regex_match(name, m, pattern)) {
400 uint32_t idx = static_cast<uint32_t>(std::stoul(m[1].str()));
401 files.emplace_back(idx, path);
402 }
403 }
404 if (files.size() <= max_files) return;
405 std::sort(files.begin(), files.end(), [](const std::pair<uint32_t, std::string>& a, const std::pair<uint32_t, std::string>& b) { return a.first < b.first; });
406 size_t to_remove = files.size() - max_files;
407 for (size_t i = 0; i < to_remove; ++i) {
408# if defined(_WIN32)
409 remove(utf8_to_ansi(files[i].second).c_str());
410# else
411 remove(files[i].second.c_str());
412# endif
413 }
414# endif
415 }
416
419 const int64_t threshold_ts = m_current_date_ts - (time_shield::SEC_PER_DAY * m_config.auto_delete_days);
420# if __cplusplus >= 201703L
421# ifdef _WIN32
422 fs::path dir_path = fs::u8path(get_directory_path());
423# else
424 fs::path dir_path(get_directory_path());
425# endif
426
427 if (!fs::exists(dir_path) ||
428 !fs::is_directory(dir_path)) {
429 return;
430 }
431
432 for (const auto& entry : fs::directory_iterator(dir_path)) {
433 if (!fs::is_regular_file(entry.status())) continue;
434 std::string filename = entry.path().filename().string();
435 if (is_valid_log_filename(filename)) {
436 const int64_t file_date_ts = get_date_ts_from_filename(filename);
437 if (file_date_ts < threshold_ts) {
438 fs::remove(entry.path());
439 }
440 }
441 }
442# else
443 std::vector<std::string> file_list = get_list_files(get_directory_path());
444 for (const auto& file_path : file_list) {
445 // Extract the file name
446 std::string filename = file_path.substr(file_path.find_last_of("/\\") + 1);
447 if (is_valid_log_filename(filename)) {
448 const int64_t file_date_ts = get_date_ts_from_filename(filename);
449 if (file_date_ts < threshold_ts) {
450# if defined(_WIN32)
451 remove(utf8_to_ansi(file_path).c_str());
452# else
453 remove(file_path.c_str());
454# endif
455 }
456 }
457 }
458# endif
459 }
460
464 bool is_valid_log_filename(const std::string& filename) const {
465 return filename.size() >= 10 && filename[4] == '-' && filename[7] == '-';
466 }
467
471 int64_t get_date_ts_from_filename(const std::string& filename) const {
472 return time_shield::ts(filename.substr(0, 10));
473 }
474
477 int64_t get_current_utc_date_ts() const {
478 return time_shield::start_of_day(time_shield::ms_to_sec(current_timestamp_ms()));
479 }
480
483 int64_t current_timestamp_ms() const {
485 }
486
489 std::string get_last_log_file_path() const {
490 std::lock_guard<std::mutex> lock(m_file_path_mutex);
491 return m_file_path;
492 }
493
496 std::string get_last_log_file_name() const {
497 std::lock_guard<std::mutex> lock(m_file_path_mutex);
498 return m_file_name;
499 }
500
503 int64_t get_last_log_ts() const {
504 return m_last_log_ts;
505 }
506
509 int64_t get_time_since_last_log() const {
511 }
512 }; // FileLogger
513#endif // defined(__EMSCRIPTEN__)
514
515}; // namespace logit
516
517#endif // _LOGIT_FILE_LOGGER_HPP_INCLUDED
Defines the interface for loggers used in the logging system.
std::mutex m_mutex
Mutex to protect file operations.
int64_t m_current_date_ts
Timestamp of the current log file's date.
void stop_logging()
Stops the logging process by closing the file and waiting for tasks.
void initialize_directory()
Initializes the logging directory.
std::string get_last_log_file_name() const
Retrieves the last log file name.
void remove_old_logs()
Removes old log files based on the auto-delete days configuration.
int64_t get_date_ts_from_filename(const std::string &filename) const
Extracts the date timestamp from the log filename.
FileLogger(const Config &config)
Constructor with custom configuration.
int64_t current_timestamp_ms() const
Gets the current timestamp in milliseconds.
Config m_config
Configuration for the file logger.
int64_t get_time_since_last_log() const
Retrieves the time since the last log.
std::string get_last_log_file_path() const
Retrieves the last log file path.
FileLogger(const std::string &directory, const bool &async=true, const int &auto_delete_days=30)
Constructor with directory and asynchronous flag.
std::mutex m_file_path_mutex
Mutex to protect file path operations.
uint64_t m_current_file_size
Current size of the log file.
FileLogger(const std::string &directory, const bool &async, const int &auto_delete_days, uint64_t max_file_size_bytes, uint32_t max_rotated_files, bool compress_rotated=false, std::string compress_cmd={})
Constructor with directory, size-based rotation and additional options.
void set_log_level(LogLevel level) override
Sets the minimal log level for this logger.
int64_t get_int_param(const LoggerParam &param) const override
Retrieves an integer parameter from the logger.
std::ofstream m_file
Output file stream for logging.
std::atomic< int64_t > m_last_log_ts
Timestamp of the last log.
virtual ~FileLogger()
Destructor to stop logging and close file.
void start_logging()
Starts the logging process by initializing the file and directory.
bool is_valid_log_filename(const std::string &filename) const
Checks if the filename matches the log file naming pattern.
std::string get_string_param(const LoggerParam &param) const override
Retrieves a string parameter from the logger.
int64_t get_last_log_ts() const
Retrieves the timestamp of the last log.
std::string m_file_path
Path of the currently open log file.
void log(const LogRecord &record, const std::string &message) override
Logs a message to a file with thread safety.
FileLogger()
Default constructor that uses default configuration.
int64_t get_current_utc_date_ts() const
Gets the current UTC date timestamp in seconds.
void open_log_file(const int64_t &date_ts)
Opens a new log file based on the provided date timestamp.
std::string get_directory_path() const
Gets the full path to the logging directory.
void write_log(const std::string &message, const int64_t &timestamp_ms)
Writes a log message to the file.
void enforce_rotation_retention(const std::string &base, uint32_t max_files, const std::string &dir)
std::string create_file_path(int64_t date_ts) const
Creates a file path for the log file based on the date timestamp.
LogLevel get_log_level() const override
Gets the minimal log level for this logger.
std::atomic< int > m_log_level
void wait() override
Waits for all asynchronous tasks to complete.
std::string m_file_name
Name of the currently open log file.
double get_float_param(const LoggerParam &param) const override
Retrieves a floating-point parameter from the logger.
Interface for loggers that handle log message output.
Definition ILogger.hpp:15
void add_task(std::function< void()> task)
Adds a task to the queue in a thread-safe manner.
void wait()
Waits for all tasks in the queue to be processed.
static TaskExecutor & get_instance()
Get the singleton instance of the TaskExecutor.
#define LOGIT_CURRENT_TIMESTAMP_MS()
Macro to get the current timestamp in milliseconds.
Definition config.hpp:63
The primary namespace for the LogIt++ library.
std::vector< std::string > get_list_files(const std::string &path)
Recursively retrieves a list of all files in a directory.
LogLevel
Logging levels.
Definition enums.hpp:15
@ LOG_LVL_TRACE
Trace level logging.
Definition enums.hpp:16
void create_directories(const std::string &path)
Creates directories recursively for the given path.
LoggerParam
Enumeration for different logger parameters that can be retrieved.
Definition enums.hpp:47
@ TimeSinceLastLog
The time elapsed since the last log in seconds.
Definition enums.hpp:51
@ LastLogTimestamp
The timestamp of the last log.
Definition enums.hpp:50
@ LastFileName
The name of the last file written to.
Definition enums.hpp:48
@ LastFilePath
The full path of the last file written to.
Definition enums.hpp:49
std::string get_file_name(const std::string &file_path)
Extracts the file name from a full file path.
std::string get_exec_dir()
Retrieves the directory of the executable file.
Configuration for the file logger.
std::string compress_cmd
External command used for compression.
bool compress_rotated
Whether to compress rotated files.
uint64_t max_file_size_bytes
Max size for log file before rotation (0 = off).
uint32_t max_rotated_files
Number of rotated files to keep (0 = unlimited).
int auto_delete_days
Number of days after which old log files are deleted.
bool async
Flag indicating whether logging should be asynchronous.
std::string directory
Directory where log files are stored.
Stores log metadata and content.
Definition LogRecord.hpp:17
const int64_t timestamp_ms
Timestamp in milliseconds.
Definition LogRecord.hpp:19