2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_UNIQUEFILELOGGER_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_UNIQUEFILELOGGER_HPP_INCLUDED
20#include <unordered_map>
26#if defined(__EMSCRIPTEN__)
28 class UniqueFileLogger :
public ILogger {
42 UniqueFileLogger(
const std::string&,
bool =
true,
int = 30,
size_t = 8) { warn(); }
46 void log(
const LogRecord&,
const std::string&)
override { warn(); }
52 void wait()
override {}
56 static bool warned =
false;
59 std::cerr <<
"UniqueFileLogger is not supported under Emscripten" << std::endl;
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);
116 const std::string& directory,
118 int auto_delete_days = 30,
119 size_t hash_length = 8) {
129 const std::string& directory,
131 int auto_delete_days,
133 bool use_dedicated_executor,
134 std::size_t queue_capacity,
141 use_dedicated_executor,
157 std::unique_ptr<detail::SingleThreadExecutor> old_executor;
160 if (
m_shutdown.load(std::memory_order_acquire))
return;
171 std::lock_guard<std::mutex> lock(
m_mutex);
189 old_executor->shutdown();
205 if (
m_shutdown.load(std::memory_order_acquire)) {
209 std::lock_guard<std::mutex> lock(
m_mutex);
218 m_executor->set_max_queue_size(queue_capacity);
230 void log(
const LogRecord& record,
const std::string& message)
override {
232 if (
m_shutdown.load(std::memory_order_acquire))
return;
237 std::lock_guard<std::mutex> lock(
m_mutex);
238 std::string file_path;
241 }
catch (
const std::exception& e) {
243 std::cerr <<
"Log error: " << e.what() << std::endl;
249 if (!file_path.empty()) {
257 if (!file_path.empty()) {
258 it->second.last_file_path = file_path;
261 it->second.last_file_path =
"Not available";
262 it->second.last_file_name =
"Not available";
270 }
catch (
const std::exception& e) {
271 std::cerr <<
"Log error: " << e.what() << std::endl;
281 auto async_task = [
this, message, timestamp_ms, thread_id]() {
282 std::lock_guard<std::mutex> lock(
m_mutex);
283 std::string file_path;
285 file_path =
write_log(message, timestamp_ms);
286 }
catch (
const std::exception& e) {
288 std::cerr <<
"Async log error: " << e.what() << std::endl;
295 if (!file_path.empty()) {
296 it->second.last_file_path = file_path;
299 it->second.last_file_path =
"Not available";
300 it->second.last_file_name =
"Not available";
302 it->second.pending_logs--;
304 if (it->second.pending_logs == 0) {
311 }
catch (
const std::exception& e) {
312 std::cerr <<
"Async log error: " << e.what() << std::endl;
334 return std::string();
393 if (
m_shutdown.load(std::memory_order_acquire)) {
396 result.
message =
"UniqueFileLogger is shut down";
401 std::lock_guard<std::mutex> lock(
m_mutex);
403 for (
size_t i = 0; i < files.size(); ++i) {
417 }
catch (
const std::exception& e) {
420 result.
message = std::string(
"UniqueFileLogger clear error: ") + e.what();
424 result.
message =
"UniqueFileLogger clear error";
433 if (
m_shutdown.exchange(
true, std::memory_order_acq_rel))
return;
475 const std::string& directory,
477 int auto_delete_days,
479 bool use_dedicated_executor,
480 std::size_t queue_capacity,
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;
495 std::lock_guard<std::mutex> lock(
m_mutex);
499 }
catch (
const std::exception& e) {
500 std::cerr <<
"Initialization error: " << e.what() << std::endl;
524 std::string
write_log(
const std::string& message,
const int64_t& timestamp_ms) {
527 std::ofstream file(utf8_to_ansi(file_path), std::ios_base::binary);
529 std::ofstream file(file_path, std::ios_base::binary);
531 if (!file.is_open()) {
532 throw std::runtime_error(
"Failed to open log file: " + file_path);
534 file.write(message.data(), message.size());
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);
562 static const char charset[] =
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);
570 hash.reserve(length);
571 for (
size_t i = 0; i < length; ++i) {
572 hash += charset[distribution(generator)];
587 info.
path = file_path;
588 info.
name = filename;
609 std::ifstream in(utf8_to_ansi(file_path).c_str(), std::ios_base::binary);
611 std::ifstream in(file_path.c_str(), std::ios_base::binary);
618 std::ostringstream stream;
619 stream << in.rdbuf();
626 return std::remove(utf8_to_ansi(file_path).c_str()) == 0;
628 return std::remove(file_path.c_str()) == 0;
633# if __cplusplus >= 201703L
635 return fs::u8path(file_path).parent_path().lexically_normal() ==
636 fs::u8path(directory_path).lexically_normal();
638 return fs::path(file_path).parent_path().lexically_normal() ==
639 fs::path(directory_path).lexically_normal();
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;
649 bool same_path(
const std::string& lhs,
const std::string& rhs)
const {
650# if __cplusplus >= 201703L
652 return fs::u8path(lhs).lexically_normal() == fs::u8path(rhs).lexically_normal();
654 return fs::path(lhs).lexically_normal() == fs::path(rhs).lexically_normal();
662 std::string normalized = path;
663 for (
size_t i = 0; i < normalized.size(); ++i) {
664 if (normalized[i] ==
'\\') normalized[i] =
'/';
686 bool ends_with(
const std::string& value,
const std::string& suffix)
const {
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;
697 return filename.substr(0, filename.size() - 3);
700 return filename.substr(0, filename.size() - 4);
707 const int64_t threshold_ts =
710# if __cplusplus >= 201703L
718 if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
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();
727 if (file_ts < threshold_ts) {
728 fs::remove(entry.path());
734 for (
const auto& file_path : file_list) {
738 if (file_ts < threshold_ts) {
740 remove(utf8_to_ansi(file_path).c_str());
742 remove(file_path.c_str());
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);
763 return time_shield::ts(filename.substr(0, 10));
779 auto thread_id = std::this_thread::get_id();
787 return it->second.last_file_name;
789 return std::string();
799 auto thread_id = std::this_thread::get_id();
807 return it->second.last_file_path;
809 return std::string();
829 std::vector<LogFileInfo> files;
831# if __cplusplus >= 201703L
833 fs::path dir_path = fs::u8path(directory);
835 fs::path dir_path(directory);
837 if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
841 for (
const auto& entry : fs::directory_iterator(dir_path)) {
842 if (!fs::is_regular_file(entry.status()))
continue;
844 const std::string file_path = entry.path().u8string();
846 const std::string file_path = entry.path().string();
850 files.push_back(info);
854 const std::vector<std::string> file_list =
get_list_files(directory);
855 for (
size_t i = 0; i < file_list.size(); ++i) {
858 files.push_back(info);
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;
869 if (lhs_key.artifact_name == rhs_key.artifact_name &&
871 return !lhs.is_compressed && rhs.is_compressed;
873 if (lhs_key.artifact_name != rhs_key.artifact_name) {
874 return lhs_key.artifact_name > rhs_key.artifact_name;
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]);
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());
907 for (
size_t i = 0; i < paths.size(); ++i) {
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]));
922 results.push_back(result);
Defines the interface for loggers used in the logging system.
Optional buffered snapshot access returns empty results by default.
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 ¶m) 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.
virtual ~UniqueFileLogger()
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 ¶m) 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 ×tamp_ms) const
Creates a unique file path based on the timestamp and a hash.
std::string format_timestamp(const int64_t ×tamp_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 ¶m) 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 ×tamp_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.
#define LOGIT_MONOTONIC_MS()
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.
@ 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.
@ 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.
std::string get_exec_dir()
Retrieves the directory of the executable file.
Selects which data categories a logger cleanup should remove.
Result returned by logger cleanup operations.
std::string message
Optional diagnostic or unsupported reason.
std::size_t cleared_records
Number of primary records/files cleared when available.
LogClearStatus status
Detailed cleanup status.
bool ok
True when the backend completed cleanup.
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.
std::thread::id thread_id
ID of the logging thread.
const int64_t timestamp_ms
Timestamp in milliseconds.
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.
std::string last_file_path
ThreadLogInfo(const int pending_logs, const std::string last_file_path, const std::string last_file_name)
std::string last_file_name
std::string timestamp_prefix
std::string artifact_name