Back to Site
Loading...
Searching...
No Matches
logger.h
Go to the documentation of this file.
1#pragma once
2
3#include <string>
4#include <iostream>
5#include <mutex>
6#include <sstream>
7#include <chrono>
8#include <iomanip>
9#include <cstdint>
10#include <fstream>
11#include "fs.h"
12
13#ifdef _WIN32
14 #include <windows.h>
15 #include <io.h>
16 #define isatty _isatty
17 #define fileno _fileno
18 // Undefine Windows ERROR macro to avoid conflicts with our enum
19 #ifdef ERROR
20 #undef ERROR
21 #endif
22#else
23 #include <unistd.h>
24#endif
25
26namespace librats {
27
28enum class LogLevel {
29 DEBUG = 0,
30 INFO = 1,
31 WARN = 2,
32 ERROR = 3
33};
34
35class Logger {
36public:
37 // Singleton pattern - implementation in logger.cpp to ensure single instance across TUs
39
40 // Delete copy constructor and assignment operator
41 Logger(const Logger&) = delete;
42 Logger& operator=(const Logger&) = delete;
43
44 // Set the minimum log level
45 void set_log_level(LogLevel level) {
46 std::lock_guard<std::mutex> lock(mutex_);
47 min_level_ = level;
48 }
49
50 // Enable/disable colors
51 void set_colors_enabled(bool enabled) {
52 std::lock_guard<std::mutex> lock(mutex_);
53 colors_enabled_ = enabled;
54 }
55
56 // Enable/disable timestamps
57 void set_timestamps_enabled(bool enabled) {
58 std::lock_guard<std::mutex> lock(mutex_);
59 timestamps_enabled_ = enabled;
60 }
61
62 // Console logging configuration
63 void set_console_logging_enabled(bool enabled) {
64 std::lock_guard<std::mutex> lock(mutex_);
65 console_logging_enabled_ = enabled;
66 }
67
69 std::lock_guard<std::mutex> lock(mutex_);
70 return console_logging_enabled_;
71 }
72
73 // File logging configuration
74 void set_file_logging_enabled(bool enabled) {
75 std::lock_guard<std::mutex> lock(mutex_);
76 file_logging_enabled_ = enabled;
77 if (enabled && !log_file_path_.empty()) {
78 open_log_file();
79 } else if (!enabled) {
80 close_log_file();
81 }
82 }
83
84 void set_log_file_path(const std::string& path) {
85 std::lock_guard<std::mutex> lock(mutex_);
86 log_file_path_ = path;
87 if (file_logging_enabled_) {
88 close_log_file();
89 open_log_file();
90 }
91 }
92
93 void set_log_rotation_size(size_t max_size_bytes) {
94 std::lock_guard<std::mutex> lock(mutex_);
95 max_log_file_size_ = max_size_bytes;
96 }
97
98 void set_log_retention_count(int count) {
99 std::lock_guard<std::mutex> lock(mutex_);
100 max_log_files_ = count;
101 }
102
103 // Rotate on startup configuration
104 void set_rotate_on_startup(bool enabled) {
105 std::lock_guard<std::mutex> lock(mutex_);
106 rotate_on_startup_ = enabled;
107 }
108
110 std::lock_guard<std::mutex> lock(mutex_);
111 return rotate_on_startup_;
112 }
113
114 // Get current file logging status
116 std::lock_guard<std::mutex> lock(mutex_);
117 return file_logging_enabled_;
118 }
119
120 std::string get_log_file_path() const {
121 std::lock_guard<std::mutex> lock(mutex_);
122 return log_file_path_;
123 }
124
125 // Main logging function
126 void log(LogLevel level, const std::string& module, const std::string& message) {
127 std::lock_guard<std::mutex> lock(mutex_);
128
129 if (level < min_level_) {
130 return;
131 }
132
133 // Prepare console output with colors
134 std::ostringstream console_oss;
135
136 // Add timestamp if enabled
137 if (timestamps_enabled_) {
138 auto now = std::chrono::system_clock::now();
139 auto time_t = std::chrono::system_clock::to_time_t(now);
140 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
141 now.time_since_epoch()) % 1000;
142
143 struct tm local_tm;
144#ifdef _WIN32
145 localtime_s(&local_tm, &time_t);
146#else
147 local_tm = *std::localtime(&time_t);
148#endif
149 console_oss << "[" << std::put_time(&local_tm, "%H:%M:%S");
150 console_oss << "." << std::setfill('0') << std::setw(3) << ms.count() << "] ";
151 }
152
153 // Add colored log level
154 if (colors_enabled_ && is_terminal_) {
155 console_oss << get_color_code(level) << "[" << get_level_string(level) << "]" << get_reset_code();
156 } else {
157 console_oss << "[" << get_level_string(level) << "]";
158 }
159
160 // Add colored module tag
161 if (!module.empty()) {
162 if (colors_enabled_ && is_terminal_) {
163 console_oss << " " << get_module_color(module) << "[" << module << "]" << get_reset_code();
164 } else {
165 console_oss << " [" << module << "]";
166 }
167 }
168
169 // Add message
170 console_oss << " " << message << std::endl;
171
172 // Output to appropriate console stream (if console logging is enabled)
173 if (console_logging_enabled_) {
174 if (level >= LogLevel::ERROR) {
175 std::cerr << console_oss.str();
176 std::cerr.flush();
177 } else {
178 std::cout << console_oss.str();
179 std::cout.flush();
180 }
181 }
182
183 // Also write to file if file logging is enabled
184 if (file_logging_enabled_ && log_file_.is_open()) {
185 write_to_file(level, module, message);
186 }
187 }
188
189private:
190 Logger() : min_level_(LogLevel::INFO), colors_enabled_(true), timestamps_enabled_(true),
191 console_logging_enabled_(true), file_logging_enabled_(false),
192 max_log_file_size_(10 * 1024 * 1024), max_log_files_(5), current_file_size_(0),
193 rotate_on_startup_(false), startup_rotation_done_(false) {
194 // Check if we're outputting to a terminal
195 is_terminal_ = isatty(fileno(stdout));
196
197 // On Windows, enable ANSI color codes
198#ifdef _WIN32
199 if (is_terminal_) {
200 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
201 DWORD dwMode = 0;
202 GetConsoleMode(hOut, &dwMode);
203 dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
204 SetConsoleMode(hOut, dwMode);
205 }
206#endif
207 }
208
209 ~Logger() {
210 close_log_file();
211 }
212
213 std::string get_level_string(LogLevel level) {
214 switch (level) {
215 case LogLevel::DEBUG: return "DEBUG";
216 case LogLevel::INFO: return "INFO ";
217 case LogLevel::WARN: return "WARN ";
218 case LogLevel::ERROR: return "ERROR";
219 default: return "UNKNOWN";
220 }
221 }
222
223 std::string get_color_code(LogLevel level) {
224 if (!colors_enabled_ || !is_terminal_) return "";
225
226 switch (level) {
227 case LogLevel::DEBUG: return "\033[36m"; // Cyan
228 case LogLevel::INFO: return "\033[32m"; // Green
229 case LogLevel::WARN: return "\033[33m"; // Yellow
230 case LogLevel::ERROR: return "\033[31m"; // Red
231 default: return "";
232 }
233 }
234
235 std::string get_module_color(const std::string& module) {
236 if (!colors_enabled_ || !is_terminal_) return "";
237
238 // Generate hash for module name
239 uint32_t hash = hash_string(module);
240
241 // Map hash to a predefined set of nice, readable colors
242 const char* colors[] = {
243 "\033[35m", // Magenta
244 "\033[36m", // Cyan
245 "\033[94m", // Bright Blue
246 "\033[95m", // Bright Magenta
247 "\033[96m", // Bright Cyan
248 "\033[93m", // Bright Yellow
249 "\033[91m", // Bright Red
250 "\033[92m", // Bright Green
251 "\033[90m", // Bright Black (Gray)
252 "\033[37m", // White
253 "\033[34m", // Blue
254 "\033[33m", // Yellow
255 "\033[31m", // Red
256 "\033[32m", // Green
257 "\033[97m", // Bright White
258 "\033[38;5;208m", // Orange
259 "\033[38;5;165m", // Pink
260 "\033[38;5;141m", // Purple
261 "\033[38;5;51m", // Bright Turquoise
262 "\033[38;5;226m", // Bright Yellow
263 "\033[38;5;46m", // Bright Green
264 "\033[38;5;196m", // Bright Red
265 "\033[38;5;21m", // Bright Blue
266 "\033[38;5;129m" // Bright Purple
267 };
268
269 size_t color_count = sizeof(colors) / sizeof(colors[0]);
270 return colors[hash % color_count];
271 }
272
273 // Simple hash function for strings
274 uint32_t hash_string(const std::string& str) {
275 uint32_t hash = 5381;
276 for (char c : str) {
277 hash = ((hash << 5) + hash) + c; // hash * 33 + c
278 }
279 return hash;
280 }
281
282 std::string get_reset_code() {
283 if (!colors_enabled_ || !is_terminal_) return "";
284 return "\033[0m";
285 }
286
287 // File logging methods
288 void write_to_file(LogLevel level, const std::string& module, const std::string& message) {
289 if (!log_file_.is_open()) return;
290
291 // Check if rotation is needed
292 if (max_log_file_size_ > 0 && current_file_size_ >= max_log_file_size_) {
293 rotate_log_files();
294 }
295
296 std::ostringstream file_oss;
297
298 // Add timestamp (always enabled for file logs)
299 auto now = std::chrono::system_clock::now();
300 auto time_t = std::chrono::system_clock::to_time_t(now);
301 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
302 now.time_since_epoch()) % 1000;
303
304 struct tm local_tm;
305#ifdef _WIN32
306 localtime_s(&local_tm, &time_t);
307#else
308 local_tm = *std::localtime(&time_t);
309#endif
310 file_oss << "[" << std::put_time(&local_tm, "%Y-%m-%d %H:%M:%S");
311 file_oss << "." << std::setfill('0') << std::setw(3) << ms.count() << "] ";
312
313 // Add log level (no colors in file)
314 file_oss << "[" << get_level_string(level) << "]";
315
316 // Add module tag
317 if (!module.empty()) {
318 file_oss << " [" << module << "]";
319 }
320
321 // Add message
322 file_oss << " " << message << std::endl;
323
324 std::string log_line = file_oss.str();
325 log_file_ << log_line;
326 log_file_.flush();
327
328 current_file_size_ += log_line.length();
329 }
330
331 void open_log_file() {
332 if (log_file_path_.empty()) return;
333
334 close_log_file();
335
336 // Create directory if it doesn't exist
337 size_t last_slash = log_file_path_.find_last_of("/\\");
338 if (last_slash != std::string::npos) {
339 std::string dir_path = log_file_path_.substr(0, last_slash);
340 if (!directory_exists(dir_path.c_str())) {
341 create_directories(dir_path.c_str());
342 }
343 }
344
345 // Perform rotation on startup if enabled and not yet done this session
346 if (rotate_on_startup_ && !startup_rotation_done_) {
347 // Check if the log file exists and has content
348 if (file_exists(log_file_path_.c_str())) {
349 std::ifstream check_file(log_file_path_, std::ios::ate);
350 if (check_file.is_open() && check_file.tellg() > 0) {
351 check_file.close();
352 rotate_log_files_impl();
353 }
354 }
355 startup_rotation_done_ = true;
356 }
357
358 log_file_.open(log_file_path_, std::ios::app);
359 if (log_file_.is_open()) {
360 // Get current file size
361 log_file_.seekp(0, std::ios::end);
362 current_file_size_ = static_cast<size_t>(log_file_.tellp());
363 log_file_.seekp(0, std::ios::end); // Ensure we're at the end for appending
364 }
365 }
366
367 void close_log_file() {
368 if (log_file_.is_open()) {
369 log_file_.close();
370 }
371 current_file_size_ = 0;
372 }
373
374 void rotate_log_files() {
375 if (log_file_path_.empty() || max_log_files_ <= 0) return;
376
377 close_log_file();
378 rotate_log_files_impl();
379
380 // Reopen the log file (new empty file)
381 open_log_file();
382 }
383
384 // Internal rotation implementation - does not close/open files
385 // Called directly when we need to rotate before opening (e.g., on startup)
386 void rotate_log_files_impl() {
387 if (log_file_path_.empty() || max_log_files_ <= 0) return;
388
389 // Move existing log files
390 for (int i = max_log_files_ - 1; i >= 1; i--) {
391 std::string old_name = log_file_path_ + "." + std::to_string(i);
392 std::string new_name = log_file_path_ + "." + std::to_string(i + 1);
393
394 // Delete the oldest file if it exists
395 if (i == max_log_files_ - 1) {
396 std::remove(new_name.c_str());
397 }
398
399 // Rename old file to new name
400 std::rename(old_name.c_str(), new_name.c_str());
401 }
402
403 // Move current log file to .1
404 std::string backup_name = log_file_path_ + ".1";
405 std::rename(log_file_path_.c_str(), backup_name.c_str());
406 }
407
408 mutable std::mutex mutex_;
409 LogLevel min_level_;
410 bool colors_enabled_;
411 bool timestamps_enabled_;
412 bool is_terminal_;
413
414 // Console logging control
415 bool console_logging_enabled_;
416
417 // File logging members
418 bool file_logging_enabled_;
419 std::string log_file_path_;
420 std::ofstream log_file_;
421 size_t max_log_file_size_;
422 int max_log_files_;
423 size_t current_file_size_;
424
425 // Rotate on startup members
426 bool rotate_on_startup_;
427 bool startup_rotation_done_; // Tracks if we've already done startup rotation for this session
428};
429
430} // namespace librats
431
432// Convenience macros for easy logging
433#define LOG_DEBUG(module, message) \
434 do { \
435 std::ostringstream oss; \
436 oss << message; \
437 librats::Logger::getInstance().log(librats::LogLevel::DEBUG, module, oss.str()); \
438 } while(0)
439
440#define LOG_INFO(module, message) \
441 do { \
442 std::ostringstream oss; \
443 oss << message; \
444 librats::Logger::getInstance().log(librats::LogLevel::INFO, module, oss.str()); \
445 } while(0)
446
447#define LOG_WARN(module, message) \
448 do { \
449 std::ostringstream oss; \
450 oss << message; \
451 librats::Logger::getInstance().log(librats::LogLevel::WARN, module, oss.str()); \
452 } while(0)
453
454#define LOG_ERROR(module, message) \
455 do { \
456 std::ostringstream oss; \
457 oss << message; \
458 librats::Logger::getInstance().log(librats::LogLevel::ERROR, module, oss.str()); \
459 } while(0)
460
461
bool is_rotate_on_startup_enabled() const
Definition logger.h:109
void log(LogLevel level, const std::string &module, const std::string &message)
Definition logger.h:126
void set_log_file_path(const std::string &path)
Definition logger.h:84
std::string get_log_file_path() const
Definition logger.h:120
static Logger & getInstance()
void set_console_logging_enabled(bool enabled)
Definition logger.h:63
void set_colors_enabled(bool enabled)
Definition logger.h:51
bool is_console_logging_enabled() const
Definition logger.h:68
Logger(const Logger &)=delete
void set_rotate_on_startup(bool enabled)
Definition logger.h:104
void set_timestamps_enabled(bool enabled)
Definition logger.h:57
bool is_file_logging_enabled() const
Definition logger.h:115
void set_file_logging_enabled(bool enabled)
Definition logger.h:74
void set_log_level(LogLevel level)
Definition logger.h:45
void set_log_retention_count(int count)
Definition logger.h:98
void set_log_rotation_size(size_t max_size_bytes)
Definition logger.h:93
Logger & operator=(const Logger &)=delete
LogLevel
Definition logger.h:28
bool file_exists(const char *path)
bool create_directories(const char *path)
bool directory_exists(const char *path)