LogIt++
Header-only, macro-first C++ logging library
Loading...
Searching...
No Matches
UniqueFileLogger.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_UNIQUEFILELOGGER_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_UNIQUEFILELOGGER_HPP_INCLUDED
4
7
8#include "ILogger.hpp"
9#include <iostream>
10#include <fstream>
11#include <mutex>
12#include <atomic>
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#include <regex>
22#include <memory>
23
24namespace logit {
25
26#if defined(__EMSCRIPTEN__)
27
28 class UniqueFileLogger : public ILogger {
29 public:
30 struct Config {
31 std::string directory = "unique_logs";
32 bool async = false;
33 int auto_delete_days = 30;
34 size_t hash_length = 8;
35 bool use_dedicated_executor = false;
36 std::size_t queue_capacity = 0;
37 QueuePolicy queue_policy = QueuePolicy::Block;
38 };
39
40 UniqueFileLogger() { warn(); }
41 UniqueFileLogger(const Config&) { warn(); }
42 UniqueFileLogger(const std::string&, bool = true, int = 30, size_t = 8) { warn(); }
43 UniqueFileLogger(const std::string&, bool, int, size_t, bool, std::size_t,
44 QueuePolicy) { warn(); }
45
46 void log(const LogRecord&, const std::string&) override { warn(); }
47 std::string get_string_param(const LoggerParam&) const override { return {}; }
48 int64_t get_int_param(const LoggerParam&) const override { return 0; }
49 double get_float_param(const LoggerParam&) const override { return 0.0; }
50 void set_log_level(LogLevel) override {}
51 LogLevel get_log_level() const override { return LogLevel::LOG_LVL_TRACE; }
52 void wait() override {}
53
54 private:
55 void warn() const {
56 static bool warned = false;
57 if (!warned) {
58 warned = true;
59 std::cerr << "UniqueFileLogger is not supported under Emscripten" << std::endl;
60 }
61 }
62 std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
63 };
64
65#else
66
79 class UniqueFileLogger : public ILogger {
80 public:
81
84 struct Config {
85 std::string directory = "unique_logs";
86 bool async = true;
88 size_t hash_length = 8;
90 std::size_t queue_capacity = 0;
91 QueuePolicy queue_policy = QueuePolicy::Block;
92 };
93
98
101 UniqueFileLogger(const Config& config) : m_config(config) {
103 m_executor.reset(new detail::SingleThreadExecutor());
104 m_executor->set_max_queue_size(m_config.queue_capacity);
105 m_executor->set_queue_policy(m_config.queue_policy);
106 }
108 }
109
116 const std::string& directory,
117 bool async = true,
118 int auto_delete_days = 30,
119 size_t hash_length = 8) {
120 m_config.directory = directory;
121 m_config.async = async;
122 m_config.auto_delete_days = auto_delete_days;
123 m_config.hash_length = hash_length;
125 }
126
129 const std::string& directory,
130 bool async,
131 int auto_delete_days,
132 size_t hash_length,
133 bool use_dedicated_executor,
134 std::size_t queue_capacity,
135 QueuePolicy queue_policy)
137 directory,
138 async,
139 auto_delete_days,
140 hash_length,
141 use_dedicated_executor,
142 queue_capacity,
143 queue_policy)) {}
144
146 shutdown();
147 stop_logging();
148 }
149
156 void set_config(const Config& config) {
157 std::unique_ptr<detail::SingleThreadExecutor> old_executor;
158 {
159 std::lock_guard<std::mutex> lifecycle_lock(m_lifecycle_mutex);
160 if (m_shutdown.load(std::memory_order_acquire)) return;
161
162 if (m_config.async) {
163 if (m_executor) {
164 m_executor->wait();
165 } else {
167 }
168 }
169
170 {
171 std::lock_guard<std::mutex> lock(m_mutex);
172 m_config.async = config.async;
173 m_config.use_dedicated_executor = config.use_dedicated_executor;
174 m_config.queue_capacity = config.queue_capacity;
175 m_config.queue_policy = config.queue_policy;
177 if (!m_executor) {
179 }
180 m_executor->set_max_queue_size(m_config.queue_capacity);
181 m_executor->set_queue_policy(m_config.queue_policy);
182 } else if (m_executor) {
183 old_executor = std::move(m_executor);
184 m_executor.reset();
185 }
186 }
187 }
188 if (old_executor) {
189 old_executor->shutdown();
190 }
191 }
192
201 bool set_queue_config(std::size_t queue_capacity,
202 QueuePolicy queue_policy) {
203 std::lock_guard<std::mutex> lifecycle_lock(m_lifecycle_mutex);
204
205 if (m_shutdown.load(std::memory_order_acquire)) {
206 return false;
207 }
208
209 std::lock_guard<std::mutex> lock(m_mutex);
210
211 m_config.queue_capacity = queue_capacity;
212 m_config.queue_policy = queue_policy;
213
214 if (!m_executor) {
215 return false;
216 }
217
218 m_executor->set_max_queue_size(queue_capacity);
219 m_executor->set_queue_policy(queue_policy);
220 return true;
221 }
222
230 void log(const LogRecord& record, const std::string& message) override {
231 std::lock_guard<std::mutex> lifecycle_lock(m_lifecycle_mutex);
232 if (m_shutdown.load(std::memory_order_acquire)) return;
233 auto thread_id = record.thread_id;
236 if (!m_config.async) {
237 std::lock_guard<std::mutex> lock(m_mutex);
238 std::string file_path;
239 try {
240 file_path = write_log(message, record.timestamp_ms);
241 } catch (const std::exception& e) {
242 file_path.clear();
243 std::cerr << "Log error: " << e.what() << std::endl;
244 }
245
246 std::unique_lock<std::mutex> info_lock(m_thread_log_info_mutex);
247 auto it = m_thread_log_info.find(thread_id);
248 if (it == m_thread_log_info.end()) {
249 if (!file_path.empty()) {
250 m_thread_log_info[thread_id] = {0, file_path, get_file_name(file_path)};
251 } else {
252 m_thread_log_info[thread_id] = {0, "Not available", "Not available"};
253 }
254 return;
255 }
256
257 if (!file_path.empty()) {
258 it->second.last_file_path = file_path;
259 it->second.last_file_name = get_file_name(file_path);
260 } else {
261 it->second.last_file_path = "Not available";
262 it->second.last_file_name = "Not available";
263 }
264
265 m_pending_logs_cv.notify_all();
266 info_lock.unlock();
267
268 try {
270 } catch (const std::exception& e) {
271 std::cerr << "Log error: " << e.what() << std::endl;
272 }
273 return;
274 }
275
276 std::unique_lock<std::mutex> info_lock(m_thread_log_info_mutex);
277 m_thread_log_info[thread_id].pending_logs++;
278 info_lock.unlock();
279
280 auto timestamp_ms = record.timestamp_ms;
281 auto async_task = [this, message, timestamp_ms, thread_id]() {
282 std::lock_guard<std::mutex> lock(m_mutex);
283 std::string file_path;
284 try {
285 file_path = write_log(message, timestamp_ms);
286 } catch (const std::exception& e) {
287 file_path.clear();
288 std::cerr << "Async log error: " << e.what() << std::endl;
289 }
290
291 std::unique_lock<std::mutex> info_lock(m_thread_log_info_mutex);
292 auto it = m_thread_log_info.find(thread_id);
293 if (it == m_thread_log_info.end()) return;
294
295 if (!file_path.empty()) {
296 it->second.last_file_path = file_path;
297 it->second.last_file_name = get_file_name(file_path);
298 } else {
299 it->second.last_file_path = "Not available";
300 it->second.last_file_name = "Not available";
301 }
302 it->second.pending_logs--;
303
304 if (it->second.pending_logs == 0) {
305 m_pending_logs_cv.notify_all();
306 }
307 info_lock.unlock();
308
309 try {
311 } catch (const std::exception& e) {
312 std::cerr << "Async log error: " << e.what() << std::endl;
313 }
314 };
315 if (m_executor) {
316 m_executor->add_task(std::move(async_task));
317 } else {
318 detail::TaskExecutor::get_instance().add_task(std::move(async_task));
319 }
320 }
321
325 std::string get_string_param(const LoggerParam& param) const override {
326 switch (param) {
329 case LoggerParam::LastLogTimestamp: return std::to_string(get_last_log_ts());
330 case LoggerParam::TimeSinceLastLog: return std::to_string(get_time_since_last_log());
331 default:
332 break;
333 };
334 return std::string();
335 }
336
340 int64_t get_int_param(const LoggerParam& param) const override {
341 switch (param) {
344 default:
345 break;
346 };
347 return 0;
348 }
349
353 double get_float_param(const LoggerParam& param) const override {
354 switch (param) {
356 return static_cast<double>(get_last_log_ts()) / 1000.0;
358 return static_cast<double>(get_time_since_last_log()) / 1000.0;
359 default:
360 break;
361 };
362 return 0.0;
363 }
364
366 void set_log_level(LogLevel level) override {
367 m_log_level = static_cast<int>(level);
368 }
369
371 LogLevel get_log_level() const override {
372 return static_cast<LogLevel>(m_log_level.load());
373 }
374
376 void wait() override {
377 std::lock_guard<std::mutex> lifecycle_lock(m_lifecycle_mutex);
378 if (!m_config.async) return;
379 if (m_executor) {
380 m_executor->wait();
381 } else {
383 }
384 }
385
388 (void)options;
389 wait();
390
391 std::lock_guard<std::mutex> lifecycle_lock(m_lifecycle_mutex);
392 LogClearResult result;
393 if (m_shutdown.load(std::memory_order_acquire)) {
394 result.ok = false;
396 result.message = "UniqueFileLogger is shut down";
397 return result;
398 }
399
400 try {
401 std::lock_guard<std::mutex> lock(m_mutex);
402 const std::vector<LogFileInfo> files = list_log_files();
403 for (size_t i = 0; i < files.size(); ++i) {
404 if (remove_file_path(files[i].path)) {
405 ++result.cleared_records;
406 }
407 }
408 {
409 std::lock_guard<std::mutex> info_lock(m_thread_log_info_mutex);
410 m_thread_log_info.clear();
411 }
412 m_last_log_ts.store(0, std::memory_order_release);
413 m_last_log_mono_ts.store(0, std::memory_order_release);
414 result.ok = true;
416 result.message = "cleared";
417 } catch (const std::exception& e) {
418 result.ok = false;
420 result.message = std::string("UniqueFileLogger clear error: ") + e.what();
421 } catch (...) {
422 result.ok = false;
424 result.message = "UniqueFileLogger clear error";
425 }
426 return result;
427 }
428
430 void shutdown() override {
431 {
432 std::lock_guard<std::mutex> lifecycle_lock(m_lifecycle_mutex);
433 if (m_shutdown.exchange(true, std::memory_order_acq_rel)) return;
434 }
435 if (m_executor) {
436 m_executor->shutdown();
437 } else if (m_config.async) {
439 }
440 }
441
442 private:
443 mutable std::mutex m_mutex;
444 std::mutex m_lifecycle_mutex;
446 std::unique_ptr<detail::SingleThreadExecutor> m_executor;
447
450 std::string last_file_path;
451 std::string last_file_name;
452
454
456 const int pending_logs,
457 const std::string last_file_path,
458 const std::string last_file_name) :
462 }
463 };
464
465 mutable std::mutex m_thread_log_info_mutex;
466 mutable std::condition_variable m_pending_logs_cv;
467 std::unordered_map<std::thread::id, ThreadLogInfo> m_thread_log_info;
468
469 std::atomic<int64_t> m_last_log_ts = ATOMIC_VAR_INIT(0);
470 std::atomic<int64_t> m_last_log_mono_ts = ATOMIC_VAR_INIT(0);
471 std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
472 std::atomic<bool> m_shutdown = ATOMIC_VAR_INIT(false);
473
475 const std::string& directory,
476 bool async,
477 int auto_delete_days,
478 size_t hash_length,
479 bool use_dedicated_executor,
480 std::size_t queue_capacity,
481 QueuePolicy queue_policy) {
482 Config config;
483 config.directory = directory;
484 config.async = async;
485 config.auto_delete_days = auto_delete_days;
486 config.hash_length = hash_length;
487 config.use_dedicated_executor = use_dedicated_executor;
488 config.queue_capacity = queue_capacity;
489 config.queue_policy = queue_policy;
490 return config;
491 }
492
495 std::lock_guard<std::mutex> lock(m_mutex);
496 try {
499 } catch (const std::exception& e) {
500 std::cerr << "Initialization error: " << e.what() << std::endl;
501 }
502 }
503
506 wait();
507 }
508
513
516 std::string get_directory_path() const {
517 return get_exec_dir() + "/" + m_config.directory;
518 }
519
524 std::string write_log(const std::string& message, const int64_t& timestamp_ms) {
525 std::string file_path = create_unique_file_path(timestamp_ms);
526# if defined(_WIN32)
527 std::ofstream file(utf8_to_ansi(file_path), std::ios_base::binary);
528# else
529 std::ofstream file(file_path, std::ios_base::binary);
530# endif
531 if (!file.is_open()) {
532 throw std::runtime_error("Failed to open log file: " + file_path);
533 }
534 file.write(message.data(), message.size());
535 file.close();
536 return file_path;
537 }
538
542 std::string create_unique_file_path(const int64_t& timestamp_ms) const {
543 const std::string timestamp_str = format_timestamp(timestamp_ms);
544 const std::string hash_str = generate_fixed_length_hash(m_config.hash_length);
545 return get_directory_path() + "/" + timestamp_str + "-" + hash_str + ".log";
546 }
547
551 std::string format_timestamp(const int64_t& timestamp_ms) const {
552 const auto dt = time_shield::to_date_time_ms<time_shield::DateTimeStruct>(timestamp_ms);
553 char buffer[32] = {0};
554 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);
555 return std::string(buffer);
556 }
557
561 std::string generate_fixed_length_hash(size_t length) const {
562 static const char charset[] =
563 "0123456789"
564 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
565 "abcdefghijklmnopqrstuvwxyz";
566 static thread_local std::mt19937 generator(std::random_device{}());
567 static thread_local std::uniform_int_distribution<size_t> distribution(0, sizeof(charset) - 2);
568
569 std::string hash;
570 hash.reserve(length);
571 for (size_t i = 0; i < length; ++i) {
572 hash += charset[distribution(generator)];
573 }
574 return hash;
575 }
576
577 bool try_make_log_file_info(const std::string& file_path, LogFileInfo& info) const {
578 if (!is_direct_child_path(file_path, get_directory_path())) {
579 return false;
580 }
581
582 const std::string filename = get_file_name(file_path);
583 if (!is_valid_log_filename(filename)) {
584 return false;
585 }
586
587 info.path = file_path;
588 info.name = filename;
589 info.day_start_ms = time_shield::sec_to_ms(get_date_ts_from_filename(filename));
590 info.is_current = false;
592 return true;
593 }
594
596 LogFileReadResult result;
597 result.file = info;
598 if (info.is_compressed) {
599 result.ok = false;
600 return result;
601 }
602
603 result.ok = read_plain_file(info.path, result.content);
604 return result;
605 }
606
607 bool read_plain_file(const std::string& file_path, std::string& out) const {
608# if defined(_WIN32)
609 std::ifstream in(utf8_to_ansi(file_path).c_str(), std::ios_base::binary);
610# else
611 std::ifstream in(file_path.c_str(), std::ios_base::binary);
612# endif
613 if (!in.is_open()) {
614 out.clear();
615 return false;
616 }
617
618 std::ostringstream stream;
619 stream << in.rdbuf();
620 out = stream.str();
621 return !in.bad();
622 }
623
624 bool remove_file_path(const std::string& file_path) const {
625# if defined(_WIN32)
626 return std::remove(utf8_to_ansi(file_path).c_str()) == 0;
627# else
628 return std::remove(file_path.c_str()) == 0;
629# endif
630 }
631
632 bool is_direct_child_path(const std::string& file_path, const std::string& directory_path) const {
633# if __cplusplus >= 201703L
634# if defined(_WIN32)
635 return fs::u8path(file_path).parent_path().lexically_normal() ==
636 fs::u8path(directory_path).lexically_normal();
637# else
638 return fs::path(file_path).parent_path().lexically_normal() ==
639 fs::path(directory_path).lexically_normal();
640# endif
641# else
642 const size_t pos = file_path.find_last_of("/\\");
643 if (pos == std::string::npos) return false;
644 std::string parent = file_path.substr(0, pos);
645 return parent == directory_path;
646# endif
647 }
648
649 bool same_path(const std::string& lhs, const std::string& rhs) const {
650# if __cplusplus >= 201703L
651# if defined(_WIN32)
652 return fs::u8path(lhs).lexically_normal() == fs::u8path(rhs).lexically_normal();
653# else
654 return fs::path(lhs).lexically_normal() == fs::path(rhs).lexically_normal();
655# endif
656# else
658# endif
659 }
660
661 std::string normalize_path_separators(const std::string& path) const {
662 std::string normalized = path;
663 for (size_t i = 0; i < normalized.size(); ++i) {
664 if (normalized[i] == '\\') normalized[i] = '/';
665 }
666 return normalized;
667 }
668
670 std::string timestamp_prefix;
671 std::string artifact_name;
672 };
673
677 key.timestamp_prefix =
678 key.artifact_name.size() >= 23 ? key.artifact_name.substr(0, 23) : key.artifact_name;
679 return key;
680 }
681
682 bool is_compressed_log_filename(const std::string& filename) const {
683 return ends_with(filename, ".gz") || ends_with(filename, ".zst");
684 }
685
686 bool ends_with(const std::string& value, const std::string& suffix) const {
687 return ends_with_impl(value, suffix);
688 }
689
690 static bool ends_with_impl(const std::string& value, const std::string& suffix) {
691 return value.size() >= suffix.size() &&
692 value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0;
693 }
694
695 static std::string strip_compression_suffix_impl(const std::string& filename) {
696 if (ends_with_impl(filename, ".gz")) {
697 return filename.substr(0, filename.size() - 3);
698 }
699 if (ends_with_impl(filename, ".zst")) {
700 return filename.substr(0, filename.size() - 4);
701 }
702 return filename;
703 }
704
707 const int64_t threshold_ts =
708 time_shield::ms_to_sec(current_timestamp_ms()) -
709 (m_config.auto_delete_days * time_shield::SEC_PER_DAY);
710# if __cplusplus >= 201703L
711
712# if defined(_WIN32)
713 fs::path dir_path = fs::u8path(get_directory_path());
714# else
715 fs::path dir_path(get_directory_path());
716# endif
717
718 if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
719 return;
720 }
721
722 for (const auto& entry : fs::directory_iterator(dir_path)) {
723 if (!fs::is_regular_file(entry.status())) continue;
724 std::string filename = entry.path().filename().string();
725 if (is_valid_log_filename(filename)) {
726 const int64_t file_ts = get_date_ts_from_filename(filename);
727 if (file_ts < threshold_ts) {
728 fs::remove(entry.path());
729 }
730 }
731 }
732# else
733 const std::vector<std::string> file_list = get_list_files(get_directory_path());
734 for (const auto& file_path : file_list) {
735 std::string filename = get_file_name(file_path);
736 if (is_valid_log_filename(filename)) {
737 const int64_t file_ts = get_date_ts_from_filename(filename);
738 if (file_ts < threshold_ts) {
739# if defined(_WIN32)
740 remove(utf8_to_ansi(file_path).c_str());
741# else
742 remove(file_path.c_str());
743# endif
744 }
745 }
746 }
747# endif
748 }
749
753 bool is_valid_log_filename(const std::string& filename) const {
754 static const std::regex pattern(
755 R"(^(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-\d{3})-[-_A-Za-z0-9]+\.log(\.gz|\.zst)?$)");
756 return std::regex_match(filename, pattern);
757 }
758
762 int64_t get_date_ts_from_filename(const std::string& filename) const {
763 return time_shield::ts(filename.substr(0, 10));
764 }
765
768 int64_t current_timestamp_ms() const {
770 }
771
778 std::string get_last_log_file_name() const {
779 auto thread_id = std::this_thread::get_id();
780 std::unique_lock<std::mutex> lock(m_thread_log_info_mutex);
781 m_pending_logs_cv.wait(lock, [this, thread_id]() {
782 auto it = m_thread_log_info.find(thread_id);
783 return it == m_thread_log_info.end() || it->second.pending_logs == 0;
784 });
785 auto it = m_thread_log_info.find(thread_id);
786 if (it != m_thread_log_info.end()) {
787 return it->second.last_file_name;
788 }
789 return std::string();
790 }
791
798 std::string get_last_log_file_path() const {
799 auto thread_id = std::this_thread::get_id();
800 std::unique_lock<std::mutex> lock(m_thread_log_info_mutex);
801 m_pending_logs_cv.wait(lock, [this, thread_id]() {
802 auto it = m_thread_log_info.find(thread_id);
803 return it == m_thread_log_info.end() || it->second.pending_logs == 0;
804 });
805 auto it = m_thread_log_info.find(thread_id);
806 if (it != m_thread_log_info.end()) {
807 return it->second.last_file_path;
808 }
809 return std::string();
810 }
811
814 int64_t get_last_log_ts() const {
815 return m_last_log_ts;
816 }
817
820 int64_t get_time_since_last_log() const {
822 }
823
824 public:
827 std::vector<LogFileInfo> list_log_files() const override {
828 const std::string directory = get_directory_path();
829 std::vector<LogFileInfo> files;
830
831# if __cplusplus >= 201703L
832# if defined(_WIN32)
833 fs::path dir_path = fs::u8path(directory);
834# else
835 fs::path dir_path(directory);
836# endif
837 if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
838 return files;
839 }
840
841 for (const auto& entry : fs::directory_iterator(dir_path)) {
842 if (!fs::is_regular_file(entry.status())) continue;
843# if defined(_WIN32)
844 const std::string file_path = entry.path().u8string();
845# else
846 const std::string file_path = entry.path().string();
847# endif
848 LogFileInfo info;
849 if (try_make_log_file_info(file_path, info)) {
850 files.push_back(info);
851 }
852 }
853# else
854 const std::vector<std::string> file_list = get_list_files(directory);
855 for (size_t i = 0; i < file_list.size(); ++i) {
856 LogFileInfo info;
857 if (try_make_log_file_info(file_list[i], info)) {
858 files.push_back(info);
859 }
860 }
861# endif
862
863 std::sort(files.begin(), files.end(), [this](const LogFileInfo& lhs, const LogFileInfo& rhs) {
864 const UniqueFileSortKey lhs_key = make_file_sort_key(lhs);
865 const UniqueFileSortKey rhs_key = make_file_sort_key(rhs);
866 if (lhs_key.timestamp_prefix != rhs_key.timestamp_prefix) {
867 return lhs_key.timestamp_prefix > rhs_key.timestamp_prefix;
868 }
869 if (lhs_key.artifact_name == rhs_key.artifact_name &&
870 lhs.is_compressed != rhs.is_compressed) {
871 return !lhs.is_compressed && rhs.is_compressed;
872 }
873 if (lhs_key.artifact_name != rhs_key.artifact_name) {
874 return lhs_key.artifact_name > rhs_key.artifact_name;
875 }
877 });
878 return files;
879 }
880
884 LogFileReadResult read_log_file(const std::string& path) const override {
885 const std::vector<LogFileInfo> files = list_log_files();
886 for (size_t i = 0; i < files.size(); ++i) {
887 if (same_path(files[i].path, path)) {
888 return read_log_file_from_info(files[i]);
889 }
890 }
891
892 LogFileReadResult result;
893 result.file.path = path;
894 result.file.name = get_file_name(path);
895 result.ok = false;
896 return result;
897 }
898
902 std::vector<LogFileReadResult> read_log_files(const std::vector<std::string>& paths) const override {
903 const std::vector<LogFileInfo> files = list_log_files();
904 std::vector<LogFileReadResult> results;
905 results.reserve(paths.size());
906
907 for (size_t i = 0; i < paths.size(); ++i) {
908 bool found = false;
909 for (size_t j = 0; j < files.size(); ++j) {
910 if (same_path(files[j].path, paths[i])) {
911 results.push_back(read_log_file_from_info(files[j]));
912 found = true;
913 break;
914 }
915 }
916
917 if (!found) {
918 LogFileReadResult result;
919 result.file.path = paths[i];
920 result.file.name = get_file_name(paths[i]);
921 result.ok = false;
922 results.push_back(result);
923 }
924 }
925
926 return results;
927 }
928
929
930 }; // UniqueFileLogger
931
932#endif // defined(__EMSCRIPTEN__)
933
934}; // namespace logit
935
936#endif // LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_UNIQUEFILELOGGER_HPP_INCLUDED
Defines the interface for loggers used in the logging system.
Optional buffered snapshot access returns empty results by default.
Definition ILogger.hpp:47
Writes each log message to a unique file with automatic cleanup.
int64_t current_timestamp_ms() const
Gets the current timestamp in milliseconds.
std::atomic< int64_t > m_last_log_mono_ts
Timestamp of the last log.
std::string get_directory_path() const
Gets the full path to the logging directory.
std::string normalize_path_separators(const std::string &path) const
std::string generate_fixed_length_hash(size_t length) const
Generates a fixed-length hash string.
static Config make_config(const std::string &directory, bool async, int auto_delete_days, size_t hash_length, bool use_dedicated_executor, std::size_t queue_capacity, QueuePolicy queue_policy)
std::unique_ptr< detail::SingleThreadExecutor > m_executor
Dedicated executor (null = use global).
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::atomic< bool > m_shutdown
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.
void set_config(const Config &config)
Updates executor-related configuration at runtime.
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.
bool ends_with(const std::string &value, const std::string &suffix) const
UniqueFileLogger()
Default constructor that uses default configuration.
bool is_direct_child_path(const std::string &file_path, const std::string &directory_path) const
bool read_plain_file(const std::string &file_path, std::string &out) const
LogLevel get_log_level() const override
Gets the minimal log level for this logger.
std::vector< LogFileInfo > list_log_files() const override
Lists persisted log files owned by this backend.
UniqueFileLogger(const Config &config)
Constructor with custom configuration.
void stop_logging()
Stops the logging process by waiting for tasks.
std::vector< LogFileReadResult > read_log_files(const std::vector< std::string > &paths) const override
Reads several persisted log files owned by this backend.
bool try_make_log_file_info(const std::string &file_path, LogFileInfo &info) const
std::atomic< int64_t > m_last_log_ts
Timestamp of the last log.
LogFileReadResult read_log_file(const std::string &path) const override
Reads one persisted log file owned by this backend.
bool remove_file_path(const std::string &file_path) const
bool set_queue_config(std::size_t queue_capacity, QueuePolicy queue_policy)
Updates queue_capacity and queue_policy at runtime without draining tasks.
bool same_path(const std::string &lhs, const std::string &rhs) const
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.
static bool ends_with_impl(const std::string &value, const std::string &suffix)
static UniqueFileSortKey make_file_sort_key(const LogFileInfo &info)
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.
int64_t get_date_ts_from_filename(const std::string &filename) const
Extracts the date timestamp from the filename.
LogClearResult clear_logs(const LogClearOptions &options=LogClearOptions()) override
Clears managed unique log files.
void remove_old_logs()
Removes old log files based on the auto-delete days configuration.
std::mutex m_lifecycle_mutex
Serializes direct log() calls with shutdown().
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.
UniqueFileLogger(const std::string &directory, bool async, int auto_delete_days, size_t hash_length, bool use_dedicated_executor, std::size_t queue_capacity, QueuePolicy queue_policy)
Constructor with directory, async flag, and dedicated executor options.
std::mutex m_thread_log_info_mutex
Mutex to protect access to thread log information.
bool is_compressed_log_filename(const std::string &filename) const
void shutdown() override
Stops logger-owned asynchronous resources after draining pending writes.
static std::string strip_compression_suffix_impl(const std::string &filename)
std::atomic< int > m_log_level
LogFileReadResult read_log_file_from_info(const LogFileInfo &info) const
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.
Per-instance single-thread executor for isolated async logging.
void add_task(std::function< void()> task)
Enqueue a task for asynchronous execution.
void wait()
Block until the queue is empty or a shutdown is requested.
static TaskExecutor & get_instance()
Returns the global executor instance.
#define LOGIT_CURRENT_TIMESTAMP_MS()
Macro to get the current timestamp in milliseconds.
Definition config.hpp:84
#define LOGIT_MONOTONIC_MS()
Definition config.hpp:74
QueuePolicy
Queue overflow handling policy used by TaskExecutor and SingleThreadExecutor.
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.
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.
@ LastLogTimestamp
The timestamp of the last log.
@ LastFileName
The name of the last file written to.
@ LastFilePath
The full path of the last file written to.
std::string get_file_name(const std::string &file_path)
Extracts the file name from a full file path.
detail::QueuePolicy QueuePolicy
Public queue overflow handling policy.
Definition loggers.hpp:20
std::string get_exec_dir()
Retrieves the directory of the executable file.
Selects which data categories a logger cleanup should remove.
Definition ILogger.hpp:19
Result returned by logger cleanup operations.
Definition ILogger.hpp:35
std::string message
Optional diagnostic or unsupported reason.
Definition ILogger.hpp:39
std::size_t cleared_records
Number of primary records/files cleared when available.
Definition ILogger.hpp:38
LogClearStatus status
Detailed cleanup status.
Definition ILogger.hpp:37
bool ok
True when the backend completed cleanup.
Definition ILogger.hpp:36
Metadata for a persisted log file owned by a logger backend.
std::string name
File name without the parent directory.
bool is_current
True when this is the file currently used for appends.
int64_t day_start_ms
Start-of-day UTC timestamp derived from the file name.
std::string path
Full path to the file.
bool is_compressed
True when the file is a compressed rotated artifact.
Result of reading a persisted log file from a file-based backend.
LogFileInfo file
Metadata for the requested file.
bool ok
Whether the read succeeded.
std::string content
File contents when the read succeeded.
Stores log metadata and content.
Definition LogRecord.hpp:17
std::thread::id thread_id
ID of the logging thread.
Definition LogRecord.hpp:27
const int64_t timestamp_ms
Timestamp in milliseconds.
Definition LogRecord.hpp:19
Configuration for the unique file logger.
int auto_delete_days
Number of days after which old log files are deleted.
std::size_t queue_capacity
Maximum queue size for the dedicated executor (0 = unlimited).
bool use_dedicated_executor
Use a dedicated executor instead of the global TaskExecutor; native builds create one worker thread p...
QueuePolicy queue_policy
Overflow policy for the dedicated executor.
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)