#pragma once
|
#include "common.h"
|
#include <cstring> // ÓÃÓÚstrerror
|
#include <cstdio> // ÓÃÓÚstd::rename, std::remove
|
#include <utility> // ÓÃÓÚstd::forward
|
// ÈÕÖ¾¼¶±ðö¾Ù
|
enum class LogLevel {
|
TRACE, // ×îÏêϸµÄ¸ú×ÙÐÅÏ¢
|
DEBUG, // µ÷ÊÔÐÅÏ¢
|
INFO, // ³£¹æÐÅÏ¢
|
WARNING, // ¾¯¸æÐÅÏ¢
|
ERRORL, // ´íÎóÐÅÏ¢
|
FATAL // ÑÏÖØ´íÎó
|
};
|
|
// ½«ÈÕÖ¾¼¶±ðת»»Îª×Ö·û´®
|
inline const char* levelToString(LogLevel level) {
|
switch (level) {
|
case LogLevel::TRACE: return "TRACE";
|
case LogLevel::DEBUG: return "DEBUG";
|
case LogLevel::INFO: return "INFO";
|
case LogLevel::WARNING: return "WARNING";
|
case LogLevel::ERRORL: return "ERROR";
|
case LogLevel::FATAL: return "FATAL";
|
default: return "UNKNOWN";
|
}
|
}
|
|
// ÈÕÖ¾ÅäÖýṹ
|
struct LogConfig {
|
LogLevel minLevel = LogLevel::INFO; // ×îСÈÕÖ¾¼¶±ð
|
bool consoleOutput = true; // ÊÇ·ñÊä³öµ½¿ØÖÆÌ¨
|
bool fileOutput = false; // ÊÇ·ñÊä³öµ½Îļþ
|
std::string logFilePath = "temp.log";// ÈÕÖ¾Îļþ·¾¶
|
bool includeTimestamp = true; // ÊÇ·ñ°üº¬Ê±¼ä´Á
|
bool includeThreadId = false; // ÊÇ·ñ°üº¬Ïß³ÌID
|
size_t maxFileSize = 10 * 1024 * 1024; // ×î´óÎļþ´óС (10MB)
|
int maxBackupFiles = 5; // ×î´ó±¸·ÝÎļþÊý
|
};
|
|
class Logger {
|
public:
|
// »ñÈ¡ÈÕ־ʵÀý
|
static Logger& instance() {
|
static Logger instance;
|
return instance;
|
}
|
|
// ÅäÖÃÈÕ־ϵͳ
|
void configure(const LogConfig& config) {
|
std::lock_guard<std::mutex> lock(configMutex_);
|
config_ = config;
|
|
// Èç¹ûÆôÓÃÎļþÊä³ö£¬È·±£ÎļþÒÑ´ò¿ª
|
if (config.fileOutput) {
|
openLogFile();
|
}
|
}
|
|
// ¼Ç¼ÈÕÖ¾ (Ä£°å·½·¨£¬Ö§³ÖÈκοÉÁ÷ʽÊä³öµÄÀàÐÍ)
|
template<typename... Args>
|
void log(LogLevel level, Args&&... args) {
|
if (shutdown_) return;
|
|
// ¸´ÖÆÅäÖÃÒÔ×îС»¯Ëø·¶Î§
|
LogConfig currentConfig;
|
{
|
std::lock_guard<std::mutex> lock(configMutex_);
|
if (level < config_.minLevel) return;
|
currentConfig = config_;
|
}
|
|
// ¹¹ÔìÈÕÖ¾ÏûÏ¢
|
std::ostringstream oss;
|
formatLogPrefix(oss, level, currentConfig);
|
|
using expander = int[];
|
(void)expander {
|
0, (void(oss << std::forward<Args>(args)), 0)...
|
};
|
|
// Ìí¼Ó»»Ðзû
|
oss << '\n';
|
std::string message = oss.str();
|
|
// Ö±½ÓдÈëÈÕÖ¾
|
/*writeLog(message);*/
|
|
// ½«ÈÕÖ¾ÏûÏ¢¼ÓÈë¶ÓÁÐ
|
{
|
std::lock_guard<std::mutex> lock(queueMutex_);
|
logQueue_.push(std::move(message));
|
}
|
|
// ֪ͨÈÕÖ¾Ïß³ÌÓÐÐÂÏûÏ¢
|
queueCond_.notify_one();
|
}
|
// ±ã½ÝÈÕÖ¾·½·¨
|
template<typename... Args> void trace(Args&&... args) {
|
log(LogLevel::TRACE, std::forward<Args>(args)...);
|
}
|
|
template<typename... Args> void debug(Args&&... args) {
|
log(LogLevel::DEBUG, std::forward<Args>(args)...);
|
}
|
|
template<typename... Args> void info(Args&&... args) {
|
log(LogLevel::INFO, std::forward<Args>(args)...);
|
}
|
|
template<typename... Args> void warning(Args&&... args) {
|
log(LogLevel::WARNING, std::forward<Args>(args)...);
|
}
|
|
template<typename... Args> void error(Args&&... args) {
|
log(LogLevel::ERRORL, std::forward<Args>(args)...);
|
}
|
|
template<typename... Args> void fatal(Args&&... args) {
|
log(LogLevel::FATAL, std::forward<Args>(args)...);
|
flush();
|
std::exit(EXIT_FAILURE);
|
}
|
|
// Ë¢ÐÂÈÕÖ¾»º³åÇø
|
void flush() {
|
std::lock_guard<std::mutex> lock(fileMutex_);
|
if (logFile_.is_open()) {
|
logFile_.flush();
|
}
|
}
|
|
// Îö¹¹º¯Êý
|
~Logger() {
|
shutdown();
|
flush();
|
}
|
// °²È«¹Ø±ÕÈÕ־ϵͳ
|
void shutdown() {
|
if (shutdown_) return;
|
|
shutdown_ = true;
|
queueCond_.notify_all(); // »½ÐÑÏß³ÌÒÔ´¦ÀíÍ˳ö
|
|
if (workerThread_.joinable()) {
|
workerThread_.join(); // µÈ´ýÏ߳̽áÊø
|
}
|
|
flush(); // ×îÖÕË¢ÐÂ
|
}
|
// ɾ³ý¿½±´¹¹Ô캯ÊýºÍ¸³ÖµÔËËã·û
|
Logger(const Logger&) = delete;
|
Logger& operator=(const Logger&) = delete;
|
|
private:
|
LogConfig config_; // ÈÕÖ¾ÅäÖÃ
|
std::ofstream logFile_; // ÈÕÖ¾ÎļþÁ÷
|
std::mutex configMutex_; // ÅäÖû¥³âËø
|
std::mutex fileMutex_; // Îļþ²Ù×÷»¥³âËø
|
std::atomic<bool> shutdown_{ false }; // ¹Ø±Õ±êÖ¾
|
// ÐÂÔöḬ̈߳²È«¶ÓÁÐÏà¹Ø³ÉÔ±
|
std::queue<std::string> logQueue_; // ÈÕÖ¾ÏûÏ¢¶ÓÁÐ
|
std::mutex queueMutex_; // ¶ÓÁл¥³âËø
|
std::condition_variable queueCond_; // ¶ÓÁÐÌõ¼þ±äÁ¿
|
std::thread workerThread_; // ÈÕ־дÈëÏß³Ì
|
|
// ˽Óй¹Ô캯Êý
|
Logger() {
|
// Æô¶¯ÈÕ־дÈëÏß³Ì
|
workerThread_ = std::thread(&Logger::processLogs, this);
|
};
|
// ÈÕÖ¾´¦ÀíÏ̺߳¯Êý
|
void processLogs() {
|
while (true) {
|
std::unique_lock<std::mutex> lock(queueMutex_);
|
|
// µÈ´ýÐÂÈÕÖ¾»ò¹Ø±ÕÐźÅ
|
queueCond_.wait(lock, [this] {
|
return !logQueue_.empty() || shutdown_;
|
});
|
|
// ´¦Àí¹Ø±ÕÐźÅ
|
if (shutdown_ && logQueue_.empty()) {
|
break;
|
}
|
|
// È¡³öËùÓдý´¦ÀíÈÕÖ¾
|
std::queue<std::string> tempQueue;
|
std::swap(logQueue_, tempQueue);
|
lock.unlock();
|
|
// ´¦ÀíËùÓÐÈ¡³öµÄÈÕÖ¾
|
while (!tempQueue.empty()) {
|
writeLog(tempQueue.front());
|
tempQueue.pop();
|
}
|
}
|
}
|
|
// ¸ñʽ»¯ÈÕ־ǰ׺
|
void formatLogPrefix(std::ostringstream& oss, LogLevel level, const LogConfig& config) {
|
// ÈÕÖ¾¼¶±ð±êÇ©
|
oss << "[" << levelToString(level) << "] ";
|
|
// ʱ¼ä´Á
|
if (config.includeTimestamp) {
|
auto now = std::chrono::system_clock::now();
|
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
now.time_since_epoch()) % 1000;
|
|
std::tm tm;
|
#ifdef _WIN32
|
localtime_s(&tm, &in_time_t);
|
#else
|
localtime_r(&in_time_t, &tm);
|
#endif
|
|
oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
|
oss << '.' << std::setfill('0') << std::setw(3) << ms.count() << " ";
|
}
|
|
// Ïß³ÌID
|
if (config.includeThreadId) {
|
oss << "[Thread:" << std::this_thread::get_id() << "] ";
|
}
|
}
|
|
// ´ò¿ªÈÕÖ¾Îļþ
|
void openLogFile() {
|
// ¹Ø±Õµ±Ç°Îļþ£¨Èç¹ûÒÑ´ò¿ª£©
|
if (logFile_.is_open()) {
|
logFile_.close();
|
}
|
|
// ¼ì²éÎļþ´óС£¬±ØÒªÊ±ÂÖת
|
std::ifstream in(config_.logFilePath, std::ios::binary | std::ios::ate);
|
if (in) {
|
auto size = in.tellg();
|
if (static_cast<size_t>(size) >= config_.maxFileSize) {
|
rotateLogFiles();
|
}
|
}
|
in.close();
|
|
// ´ò¿ªÐÂÈÕÖ¾Îļþ
|
logFile_.open(config_.logFilePath, std::ios::out | std::ios::app);
|
if (!logFile_.is_open()) {
|
std::cerr << "Failed to open log file: "
|
<< config_.logFilePath << " - "
|
<< errno << std::endl;
|
}
|
}
|
|
// ÂÖתÈÕÖ¾Îļþ
|
void rotateLogFiles() {
|
// ɾ³ý×î¾ÉµÄ±¸·ÝÎļþ
|
if (config_.maxBackupFiles > 0) {
|
std::string oldestFile = config_.logFilePath + "." + std::to_string(config_.maxBackupFiles);
|
std::remove(oldestFile.c_str());
|
|
// ÖØÃüÃûÏÖÓб¸·ÝÎļþ
|
for (int i = config_.maxBackupFiles - 1; i >= 1; i--) {
|
std::string oldName = config_.logFilePath + "." + std::to_string(i);
|
std::string newName = config_.logFilePath + "." + std::to_string(i + 1);
|
|
if (fileExists(oldName)) {
|
std::rename(oldName.c_str(), newName.c_str());
|
}
|
}
|
|
// ÖØÃüÃûµ±Ç°ÈÕÖ¾ÎļþΪ±¸·Ý1
|
if (fileExists(config_.logFilePath)) {
|
std::string newName = config_.logFilePath + ".1";
|
std::rename(config_.logFilePath.c_str(), newName.c_str());
|
}
|
}
|
}
|
|
// ¼ì²éÎļþÊÇ·ñ´æÔÚ
|
bool fileExists(const std::string& path) {
|
std::ifstream f(path);
|
return f.good();
|
}
|
|
// ʵ¼ÊдÈëÈÕÖ¾
|
void writeLog(const std::string& message) {
|
LogConfig currentConfig;
|
{
|
std::lock_guard<std::mutex> configLock(configMutex_);
|
currentConfig = config_;
|
}
|
|
// Êä³öµ½¿ØÖÆÌ¨
|
if (currentConfig.consoleOutput) {
|
if (message.find("[ERROR]") != std::string::npos ||
|
message.find("[FATAL]") != std::string::npos) {
|
std::cerr << message;
|
}
|
else {
|
std::cout << message;
|
}
|
}
|
|
// Êä³öµ½Îļþ
|
if (currentConfig.fileOutput) {
|
std::lock_guard<std::mutex> fileLock(fileMutex_);
|
if (!logFile_.is_open() || !logFile_.good()) {
|
openLogFile();
|
}
|
|
if (logFile_.good()) {
|
logFile_ << message;
|
logFile_.flush(); // ʵʱˢе½´ÅÅÌ
|
|
// ¼ì²éÎļþ´óС
|
auto pos = logFile_.tellp();
|
if (static_cast<size_t>(pos) >= currentConfig.maxFileSize) {
|
logFile_.close();
|
rotateLogFiles();
|
logFile_.open(currentConfig.logFilePath, std::ios::out | std::ios::app);
|
}
|
}
|
}
|
}
|
};
|
|
// ÈÕÖ¾ºê¶¨Òå - ÌṩÎļþÃûºÍÐкÅÐÅÏ¢
|
#define LOG_TRACE(...) Logger::instance().trace("(", __FILE__, ":", __LINE__, ") ", __VA_ARGS__)
|
#define LOG_DEBUG(...) Logger::instance().debug("(", __FILE__, ":", __LINE__, ") ", __VA_ARGS__)
|
#define LOG_INFO(...) Logger::instance().info(__VA_ARGS__)
|
#define LOG_WARN(...) Logger::instance().warning("(", __FILE__, ":", __LINE__, ") ", __VA_ARGS__)
|
#define LOG_ERROR(...) Logger::instance().error("(", __FILE__, ":", __LINE__, ") ", __VA_ARGS__)
|
#define LOG_FATAL(...) Logger::instance().fatal("(", __FILE__, ":", __LINE__, ") ", __VA_ARGS__)
|