-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLogger.hpp
More file actions
355 lines (310 loc) · 12.8 KB
/
Logger.hpp
File metadata and controls
355 lines (310 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/**
* @brief Header-only modern thread-safe logger using spdlog library
* @author Ömer Bulut
* Version: 1.1.5
*
* Features:
* - Thread-safe logging
* - Multiple log levels (TRACE, DEBUG, INFO, WARNING, ERROR, FATAL)
* - Console and file output
* - File rotation with configurable size and count
* - Asynchronous logging support
* - Custom log patterns
* - Memory-efficient design
*/
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/async.h>
#include <memory>
#include <vector>
#include <string>
#include <iostream>
#include <filesystem>
#include <fstream> // For std::ofstream
#include <sstream> // For std::stringstream
#include <streambuf> // For std::streambuf
#include <stdexcept> // For std::runtime_error
#include <exception> // For std::exception
#include <cstdint> // For uintptr_t
// Constants for magic numbers
namespace LoggerConstants {
constexpr size_t DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
constexpr int DEFAULT_MAX_FILES = 5;
constexpr size_t DEFAULT_QUEUE_SIZE = 8192;
constexpr size_t DEFAULT_FLUSH_INTERVAL = 3;
constexpr size_t KILOBYTE = 1024;
constexpr size_t MEGABYTE = KILOBYTE * KILOBYTE;
}
// Set global spdlog error handler to suppress file rotation warnings
namespace {
struct SpdlogErrorHandlerInitializer {
SpdlogErrorHandlerInitializer() {
try {
// Set spdlog to be completely silent about file rotation issues
spdlog::set_error_handler([](const std::string&) {
// Suppress ALL spdlog errors to avoid CI/CD noise
// This is intentional for production stability
});
// Set global log level to critical only
spdlog::set_level(spdlog::level::critical);
// Also suppress stderr output from spdlog
std::cerr.setstate(std::ios_base::failbit);
} catch (...) {
// Ignore any initialization errors
}
}
};
// Static instance to ensure error handler is set before any logger creation
static SpdlogErrorHandlerInitializer spdlog_init;
}
class Logger {
public:
/**
* @brief Log levels supported by the logger
*/
enum class LogLevel {
TRACE = 0, ///< Trace level for detailed debugging
DEBUG = 1, ///< Debug level for development information
INFO = 2, ///< Info level for general information
WARNING = 3, ///< Warning level for potential issues
ERROR = 4, ///< Error level for error conditions
FATAL = 5 ///< Fatal level for critical errors
};
/**
* @brief Configuration structure for logger setup
*/
struct Config {
std::string logFilePath; ///< Path to log file (empty for console only)
LogLevel minLevel; ///< Minimum log level to output
bool consoleOutput; ///< Enable console output
bool asyncLogging; ///< Enable asynchronous logging
size_t maxFileSize; ///< Maximum file size before rotation (10MB)
int maxFiles; ///< Maximum number of rotated files to keep
std::string pattern; ///< Log message pattern
size_t queueSize; ///< Queue size for async logging
size_t flushInterval; ///< Flush interval in seconds
// Default constructor with default values
Config() :
logFilePath(""),
minLevel(LogLevel::INFO),
consoleOutput(true),
asyncLogging(false), // Default to sync for better compatibility
maxFileSize(LoggerConstants::DEFAULT_MAX_FILE_SIZE),
maxFiles(LoggerConstants::DEFAULT_MAX_FILES),
pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v"),
queueSize(LoggerConstants::DEFAULT_QUEUE_SIZE),
flushInterval(LoggerConstants::DEFAULT_FLUSH_INTERVAL) {}
};
/**
* @brief Constructor with configuration
* @param config Logger configuration
*/
explicit Logger(const Config& config = Config{}) : m_config(config) {
setupLogger(config);
}
/**
* @brief Destructor - ensures proper cleanup and flush
*/
~Logger() {
if (m_logger) {
m_logger->flush();
}
}
// Logging methods
void info(const std::string& message) {
if (m_logger) {
m_logger->info(message);
}
}
void warning(const std::string& message) {
if (m_logger) {
m_logger->warn(message);
}
}
void error(const std::string& message) {
if (m_logger) {
m_logger->error(message);
}
}
void debug(const std::string& message) {
if (m_logger) {
m_logger->debug(message);
}
}
void trace(const std::string& message) {
if (m_logger) {
m_logger->trace(message);
}
}
void fatal(const std::string& message) {
if (m_logger) {
m_logger->critical(message);
}
}
/**
* @brief Set minimum log level
* @param level New minimum level
*/
void setLogLevel(LogLevel level) {
if (m_logger) {
m_logger->set_level(convertLevel(level));
m_config.minLevel = level;
}
}
/**
* @brief Update logger configuration
* @param config New configuration
*/
void setConfig(const Config& config) {
m_config = config;
setupLogger(config);
}
/**
* @brief Flush all pending log messages
*/
void flush() {
if (m_logger) {
m_logger->flush();
}
}
/**
* @brief Get underlying spdlog logger instance
* @return Shared pointer to spdlog logger
*/
[[nodiscard]] std::shared_ptr<spdlog::logger> getLogger() const {
return m_logger;
}
private:
void setupLogger(const Config& config) {
std::vector<std::shared_ptr<spdlog::sinks::sink>> sinks;
// Console sink setup
if (config.consoleOutput) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(convertLevel(config.minLevel));
sinks.push_back(console_sink);
}
// File sink setup
if (!config.logFilePath.empty()) {
try {
// Temporarily redirect stderr to suppress file rotation errors
std::stringstream buffer;
struct StderrRedirectGuard {
std::ostream& stream;
std::streambuf* old;
explicit StderrRedirectGuard(std::ostream& s, std::streambuf* nb)
: stream(s), old(s.rdbuf(nb)) {}
~StderrRedirectGuard() { stream.rdbuf(old); }
} guard(std::cerr, buffer.rdbuf());
// Ensure directory exists and is writable
std::filesystem::path logPath(config.logFilePath);
auto logDir = logPath.parent_path();
if (!logDir.empty()) {
// Create directory if it doesn't exist
if (!std::filesystem::exists(logDir)) {
std::filesystem::create_directories(logDir);
}
// Test write permissions with a temporary file
auto testFile = logDir / ".test_write_permissions";
try {
std::ofstream testStream(testFile);
if (testStream.is_open()) {
testStream << "test\n";
testStream.close();
std::filesystem::remove(testFile);
} else {
throw std::runtime_error("Cannot create test file in log directory");
}
} catch (const std::exception& ex) {
// stderr will be restored by guard destructor
std::cerr << "Warning: Log directory not writable: " << logDir << " - " << ex.what() << '\n';
// Fall back to console only
if (sinks.empty()) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(convertLevel(config.minLevel));
sinks.push_back(console_sink);
}
return; // Skip file sink creation
}
}
// Create rotating file sink with custom error handling
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
config.logFilePath,
config.maxFileSize,
config.maxFiles
);
file_sink->set_level(convertLevel(config.minLevel));
sinks.push_back(file_sink);
// stderr will be restored automatically when guard goes out of scope
} catch (const std::exception& ex) {
// Fallback to console if file creation fails
std::cerr << "Warning: Could not create log file: " << config.logFilePath
<< " - " << ex.what() << '\n';
if (sinks.empty()) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(convertLevel(config.minLevel));
sinks.push_back(console_sink);
}
}
}
// Ensure at least one sink exists
if (sinks.empty()) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(convertLevel(config.minLevel));
sinks.push_back(console_sink);
}
// Create logger based on configuration
if (config.asyncLogging) {
// Initialize async thread pool if not already done
static bool thread_pool_initialized = false;
if (!thread_pool_initialized) {
spdlog::init_thread_pool(config.queueSize, 1);
thread_pool_initialized = true;
}
// Asynchronous logger for better performance
auto async_logger = std::make_shared<spdlog::async_logger>(
"async_logger_" + std::to_string(reinterpret_cast<uintptr_t>(this)),
sinks.begin(),
sinks.end(),
spdlog::thread_pool(),
spdlog::async_overflow_policy::block
);
async_logger->set_level(convertLevel(config.minLevel));
async_logger->set_pattern(config.pattern);
async_logger->flush_on(spdlog::level::err);
m_logger = async_logger;
} else {
// Synchronous logger for simple use cases
auto sync_logger = std::make_shared<spdlog::logger>(
"sync_logger_" + std::to_string(reinterpret_cast<uintptr_t>(this)),
sinks.begin(),
sinks.end()
);
sync_logger->set_level(convertLevel(config.minLevel));
sync_logger->set_pattern(config.pattern);
sync_logger->flush_on(spdlog::level::err);
m_logger = sync_logger;
}
}
[[nodiscard]] static spdlog::level::level_enum convertLevel(LogLevel level) {
switch (level) {
case LogLevel::TRACE: return spdlog::level::trace;
case LogLevel::DEBUG: return spdlog::level::debug;
case LogLevel::INFO: return spdlog::level::info;
case LogLevel::WARNING: return spdlog::level::warn;
case LogLevel::ERROR: return spdlog::level::err;
case LogLevel::FATAL: return spdlog::level::critical;
default: return spdlog::level::info;
}
}
std::shared_ptr<spdlog::logger> m_logger; ///< Underlying spdlog logger instance
Config m_config; ///< Current logger configuration
};
// Convenience macros for quick logging (requires a Logger instance named 'logger')
#define LOG_TRACE(msg) logger.trace(msg)
#define LOG_DEBUG(msg) logger.debug(msg)
#define LOG_INFO(msg) logger.info(msg)
#define LOG_WARNING(msg) logger.warning(msg)
#define LOG_ERROR(msg) logger.error(msg)
#define LOG_FATAL(msg) logger.fatal(msg) // Test comment for workflow