LogIt++
Loading...
Searching...
No Matches
UniqueFileLogger.hpp
Go to the documentation of this file.
1#ifndef _LOGIT_UNIQUE_FILE_LOGGER_HPP_INCLUDED
2#define _LOGIT_UNIQUE_FILE_LOGGER_HPP_INCLUDED
3
6
7#include "ILogger.hpp"
8#include <iostream>
9#include <fstream>
10#include <mutex>
11#include <atomic>
12#include <regex>
13#include <queue>
14#include <functional>
15#include <sstream>
16#include <iomanip>
17#include <ctime>
18#include <random>
19#include <algorithm>
20#include <unordered_map>
21
22namespace logit {
23
24#if defined(__EMSCRIPTEN__)
25
26 class UniqueFileLogger : public ILogger {
27 public:
28 struct Config {
29 std::string directory = "unique_logs";
30 bool async = false;
31 int auto_delete_days = 30;
32 size_t hash_length = 8;
33 };
34
35 UniqueFileLogger() { warn(); }
36 UniqueFileLogger(const Config&) { warn(); }
37 UniqueFileLogger(const std::string&, bool = true, int = 30, size_t = 8) { warn(); }
38
39 void log(const LogRecord&, const std::string&) override { warn(); }
40 std::string get_string_param(const LoggerParam&) const override { return {}; }
41 int64_t get_int_param(const LoggerParam&) const override { return 0; }
42 double get_float_param(const LoggerParam&) const override { return 0.0; }
43 void set_log_level(LogLevel) override {}
44 LogLevel get_log_level() const override { return LogLevel::LOG_LVL_TRACE; }
45 void wait() override {}
46
47 private:
48 void warn() const { std::cerr << "UniqueFileLogger is not supported under Emscripten" << std::endl; }
49 std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
50 };
51
52#else
53
66 class UniqueFileLogger : public ILogger {
67 public:
68
71 struct Config {
72 std::string directory = "unique_logs";
73 bool async = true;
75 size_t hash_length = 8;
76 };
77
82
85 UniqueFileLogger(const Config& config) : m_config(config) {
87 }
88
95 const std::string& directory,
96 bool async = true,
97 int auto_delete_days = 30,
98 size_t hash_length = 8) {
99 m_config.directory = directory;
100 m_config.async = async;
101 m_config.auto_delete_days = auto_delete_days;
102 m_config.hash_length = hash_length;
104 }
105
107 stop_logging();
108 }
109
117 void log(const LogRecord& record, const std::string& message) override {
118 auto thread_id = record.thread_id;
120 if (!m_config.async) {
121 std::lock_guard<std::mutex> lock(m_mutex);
122 std::string file_path;
123 try {
124 file_path = write_log(message, record.timestamp_ms);
125 } catch (const std::exception& e) {
126 file_path.clear();
127 std::cerr << "Log error: " << e.what() << std::endl;
128 }
129
130 std::unique_lock<std::mutex> info_lock(m_thread_log_info_mutex);
131 auto it = m_thread_log_info.find(thread_id);
132 if (it == m_thread_log_info.end()) {
133 if (!file_path.empty()) {
134 m_thread_log_info[thread_id] = {0, file_path, get_file_name(file_path)};
135 } else {
136 m_thread_log_info[thread_id] = {0, "Not available", "Not available"};
137 }
138 return;
139 }
140
141 if (!file_path.empty()) {
142 it->second.last_file_path = file_path;
143 it->second.last_file_name = get_file_name(file_path);
144 } else {
145 it->second.last_file_path = "Not available";
146 it->second.last_file_name = "Not available";
147 }
148
149 m_pending_logs_cv.notify_all();
150 info_lock.unlock();
151
152 try {
154 } catch (const std::exception& e) {
155 std::cerr << "Log error: " << e.what() << std::endl;
156 }
157 return;
158 }
159
160 std::unique_lock<std::mutex> info_lock(m_thread_log_info_mutex);
161 m_thread_log_info[thread_id].pending_logs++;
162 info_lock.unlock();
163
164 auto timestamp_ms = record.timestamp_ms;
165 TaskExecutor::get_instance().add_task([this, message, timestamp_ms, thread_id]() {
166 std::lock_guard<std::mutex> lock(m_mutex);
167 std::string file_path;
168 try {
169 file_path = write_log(message, timestamp_ms);
170 } catch (const std::exception& e) {
171 file_path.clear();
172 std::cerr << "Async log error: " << e.what() << std::endl;
173 }
174
175 std::unique_lock<std::mutex> info_lock(m_thread_log_info_mutex);
176 auto it = m_thread_log_info.find(thread_id);
177 if (it == m_thread_log_info.end()) return;
178
179 if (!file_path.empty()) {
180 it->second.last_file_path = file_path;
181 it->second.last_file_name = get_file_name(file_path);
182 } else {
183 it->second.last_file_path = "Not available";
184 it->second.last_file_name = "Not available";
185 }
186 it->second.pending_logs--;
187
188 if (it->second.pending_logs == 0) {
189 m_pending_logs_cv.notify_all();
190 }
191 info_lock.unlock();
192
193 try {
195 } catch (const std::exception& e) {
196 std::cerr << "Async log error: " << e.what() << std::endl;
197 }
198 });
199 }
200
204 std::string get_string_param(const LoggerParam& param) const override {
205 switch (param) {
208 case LoggerParam::LastLogTimestamp: return std::to_string(get_last_log_ts());
209 case LoggerParam::TimeSinceLastLog: return std::to_string(get_time_since_last_log());
210 default:
211 break;
212 };
213 return std::string();
214 }
215
219 int64_t get_int_param(const LoggerParam& param) const override {
220 switch (param) {
223 default:
224 break;
225 };
226 return 0;
227 }
228
232 double get_float_param(const LoggerParam& param) const override {
233 switch (param) {
234 case LoggerParam::LastLogTimestamp: return (double)get_last_log_ts() / 1000.0;
235 case LoggerParam::TimeSinceLastLog: return (double)get_time_since_last_log() / 1000.0;
236 default:
237 break;
238 };
239 return 0.0;
240 }
241
243 void set_log_level(LogLevel level) override {
244 m_log_level = static_cast<int>(level);
245 }
246
248 LogLevel get_log_level() const override {
249 return static_cast<LogLevel>(m_log_level.load());
250 }
251
253 void wait() override {
254 if (!m_config.async) return;
256 }
257
258 private:
259 mutable std::mutex m_mutex;
261
264 std::string last_file_path;
265 std::string last_file_name;
266
268
270 const int pending_logs,
271 const std::string last_file_path,
272 const std::string last_file_name) :
276 }
277 };
278
279 mutable std::mutex m_thread_log_info_mutex;
280 mutable std::condition_variable m_pending_logs_cv;
281 std::unordered_map<std::thread::id, ThreadLogInfo> m_thread_log_info;
282
283 std::atomic<int64_t> m_last_log_ts = ATOMIC_VAR_INIT(0);
284 std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
285
286
289 std::lock_guard<std::mutex> lock(m_mutex);
290 try {
293 } catch (const std::exception& e) {
294 std::cerr << "Initialization error: " << e.what() << std::endl;
295 }
296 }
297
300 wait();
301 }
302
307
310 std::string get_directory_path() const {
311 return get_exec_dir() + "/" + m_config.directory;
312 }
313
318 std::string write_log(const std::string& message, const int64_t& timestamp_ms) {
319 std::string file_path = create_unique_file_path(timestamp_ms);
320# if defined(_WIN32)
321 std::ofstream file(utf8_to_ansi(file_path), std::ios_base::binary);
322# else
323 std::ofstream file(file_path, std::ios_base::binary);
324# endif
325 if (!file.is_open()) {
326 throw std::runtime_error("Failed to open log file: " + file_path);
327 }
328 file.write(message.data(), message.size());
329 file.close();
330 return file_path;
331 }
332
336 std::string create_unique_file_path(const int64_t& timestamp_ms) const {
337 const std::string timestamp_str = format_timestamp(timestamp_ms);
338 const std::string hash_str = generate_fixed_length_hash(m_config.hash_length);
339 return get_directory_path() + "/" + timestamp_str + "-" + hash_str + ".log";
340 }
341
345 std::string format_timestamp(const int64_t& timestamp_ms) const {
346 const auto dt = time_shield::to_date_time_ms<time_shield::DateTimeStruct>(timestamp_ms);
347 char buffer[32] = {0};
348 snprintf(buffer, sizeof(buffer), "%lld-%.2d-%.2d_%.2d-%.2d-%.2d-%.3d", dt.year, dt.mon, dt.day, dt.hour, dt.min, dt.sec, dt.ms);
349 return std::string(buffer);
350 }
351
355 std::string generate_fixed_length_hash(size_t length) const {
356 static const char charset[] =
357 "0123456789"
358 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
359 "abcdefghijklmnopqrstuvwxyz";
360 static thread_local std::mt19937 generator(std::random_device{}());
361 static thread_local std::uniform_int_distribution<size_t> distribution(0, sizeof(charset) - 2);
362
363 std::string hash;
364 hash.reserve(length);
365 for (size_t i = 0; i < length; ++i) {
366 hash += charset[distribution(generator)];
367 }
368 return hash;
369 }
370
373 const int64_t threshold_ts =
374 time_shield::ms_to_sec(current_timestamp_ms()) -
375 (m_config.auto_delete_days * time_shield::SEC_PER_DAY);
376# if __cplusplus >= 201703L
377
378# if defined(_WIN32)
379 fs::path dir_path = fs::u8path(get_directory_path());
380# else
381 fs::path dir_path(get_directory_path());
382# endif
383
384 if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
385 return;
386 }
387
388 for (const auto& entry : fs::directory_iterator(dir_path)) {
389 if (!fs::is_regular_file(entry.status())) continue;
390 std::string filename = entry.path().filename().string();
391 if (is_valid_log_filename(filename)) {
392 const int64_t file_ts = get_timestamp_from_filename(filename);
393 if (file_ts < threshold_ts) {
394 fs::remove(entry.path());
395 }
396 }
397 }
398# else
399 const std::vector<std::string> file_list = get_list_files(get_directory_path());
400 for (const auto& file_path : file_list) {
401 std::string filename = get_file_name(file_path);
402 if (is_valid_log_filename(filename)) {
403 const int64_t file_ts = get_timestamp_from_filename(filename);
404 if (file_ts < threshold_ts) {
405# if defined(_WIN32)
406 remove(utf8_to_ansi(file_path).c_str());
407# else
408 remove(file_path.c_str());
409# endif
410 }
411 }
412 }
413# endif
414 }
415
419 bool is_valid_log_filename(const std::string& filename) const {
420 static const std::regex pattern(R"((\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-\d{3})-[a-zA-Z0-9]{1,}\.log)");
421 return std::regex_match(filename, pattern);
422 }
423
427 int64_t get_timestamp_from_filename(const std::string& filename) const {
428 std::string datetime_str = filename.substr(0, 10); // "YYYY-MM-DD"
429 return time_shield::ts(datetime_str);
430 }
431
434 int64_t current_timestamp_ms() const {
436 }
437
444 std::string get_last_log_file_name() const {
445 auto thread_id = std::this_thread::get_id();
446 std::unique_lock<std::mutex> lock(m_thread_log_info_mutex);
447 m_pending_logs_cv.wait(lock, [this, thread_id]() {
448 auto it = m_thread_log_info.find(thread_id);
449 return it == m_thread_log_info.end() || it->second.pending_logs == 0;
450 });
451 auto it = m_thread_log_info.find(thread_id);
452 if (it != m_thread_log_info.end()) {
453 return it->second.last_file_name;
454 }
455 return std::string();
456 }
457
464 std::string get_last_log_file_path() const {
465 auto thread_id = std::this_thread::get_id();
466 std::unique_lock<std::mutex> lock(m_thread_log_info_mutex);
467 m_pending_logs_cv.wait(lock, [this, thread_id]() {
468 auto it = m_thread_log_info.find(thread_id);
469 return it == m_thread_log_info.end() || it->second.pending_logs == 0;
470 });
471 auto it = m_thread_log_info.find(thread_id);
472 if (it != m_thread_log_info.end()) {
473 return it->second.last_file_path;
474 }
475 return std::string();
476 }
477
480 int64_t get_last_log_ts() const {
481 return m_last_log_ts;
482 }
483
486 int64_t get_time_since_last_log() const {
488 }
489
490
491 }; // UniqueFileLogger
492
493#endif // defined(__EMSCRIPTEN__)
494
495}; // namespace logit
496
497#endif // _LOGIT_UNIQUE_FILE_LOGGER_HPP_INCLUDED
Defines the interface for loggers used in the logging system.
#define LOGIT_CURRENT_TIMESTAMP_MS()
Macro to get the current timestamp in milliseconds.
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.
Writes each log message to a unique file with automatic cleanup.
int64_t current_timestamp_ms() const
Gets the current timestamp in milliseconds.
std::string get_directory_path() const
Gets the full path to the logging directory.
std::string generate_fixed_length_hash(size_t length) const
Generates a fixed-length hash string.
void wait() override
Waits for all asynchronous tasks to complete.
int64_t get_last_log_ts() const
Retrieves the timestamp of the last log.
void initialize_directory()
Initializes the logging directory.
double get_float_param(const LoggerParam &param) const override
Retrieves a floating-point parameter from the logger.
std::string get_last_log_file_path() const
Retrieves the last log file path for the calling thread.
std::condition_variable m_pending_logs_cv
Condition variable to wait for pending logs to finish.
std::unordered_map< std::thread::id, ThreadLogInfo > m_thread_log_info
Map to store log information per thread.
UniqueFileLogger(const std::string &directory, bool async=true, int auto_delete_days=30, size_t hash_length=8)
Constructor with directory and asynchronous flag.
Config m_config
Configuration for the unique file logger.
UniqueFileLogger()
Default constructor that uses default configuration.
LogLevel get_log_level() const override
Gets the minimal log level for this logger.
UniqueFileLogger(const Config &config)
Constructor with custom configuration.
int64_t get_timestamp_from_filename(const std::string &filename) const
Extracts the timestamp from the filename.
void stop_logging()
Stops the logging process by waiting for tasks.
std::atomic< int64_t > m_last_log_ts
Timestamp of the last log.
std::string get_string_param(const LoggerParam &param) const override
Retrieves a string parameter from the logger.
void log(const LogRecord &record, const std::string &message) override
Logs a message to a unique file with thread safety.
void set_log_level(LogLevel level) override
Sets the minimal log level for this logger.
std::mutex m_mutex
Mutex to protect file operations.
std::string create_unique_file_path(const int64_t &timestamp_ms) const
Creates a unique file path based on the timestamp and a hash.
std::string format_timestamp(const int64_t &timestamp_ms) const
Formats the timestamp into a string with date and time.
std::string get_last_log_file_name() const
Retrieves the last log file name for the calling thread.
void remove_old_logs()
Removes old log files based on the auto-delete days configuration.
void start_logging()
Starts the logging process by initializing the directory and removing old logs.
int64_t get_int_param(const LoggerParam &param) const override
Retrieves an integer parameter from the logger.
std::mutex m_thread_log_info_mutex
Mutex to protect access to thread log information.
std::atomic< int > m_log_level
int64_t get_time_since_last_log() const
Retrieves the time elapsed since the last log.
std::string write_log(const std::string &message, const int64_t &timestamp_ms)
Writes a log message to a unique file.
bool is_valid_log_filename(const std::string &filename) const
Checks if the filename matches the log file naming pattern.
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.
Stores log metadata and content.
Definition LogRecord.hpp:14
std::thread::id thread_id
ID of the logging thread.
Definition LogRecord.hpp:23
const int64_t timestamp_ms
Timestamp in milliseconds.
Definition LogRecord.hpp:16
Configuration for the unique file logger.
int auto_delete_days
Number of days after which old log files are deleted.
std::string directory
Directory where log files are stored.
bool async
Flag indicating whether logging should be asynchronous.
size_t hash_length
Length of the hash used in filenames.
ThreadLogInfo(const int pending_logs, const std::string last_file_path, const std::string last_file_name)