#ifndef LOGGER_HPP
#define LOGGER_HPP
#include <fstream>
#include <iostream>
#include <string>
#include <cstdarg>
#include <memory>
#include <vector>
#include <mutex>
#include <set>
#include <functional>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <unordered_map>
#include <algorithm>
#ifdef _WIN32
#include <WinSock2.h>
#include <Windows.h>
#else
#include <pthread.h>
#endif
#include "String.hpp"
enum LogLevel
{
Trace,
Debug,
Info,
Warning,
Error,
Fatal
};
class Logger
{
public:
using LoggerErrorCallback = std::function<void(const std::string&)>;
static Logger& getInstance()
{
static Logger instance;
return instance;
}
void setLogPath(const std::string& path = "./");
void setLogLevels(const std::vector<std::string>& levels);
void setLogLevel(const std::string& levels = "F=1,E=1,W=1,I=1,D=1,T=1");
void addLogLevel(LogLevel level);
void removeLogLevel(LogLevel level);
void enableAllLogLevels();
void disableAllLogLevels();
void setErrorCallback(LoggerErrorCallback callback);
void log(LogLevel level, int line, const char* function, const char* format, ...);
void log(LogLevel level, int line, const char* function, const std::string& filename, const char* format, ...);
void log(LogLevel level, int line, const char* function, std::ofstream& file, const char* format, ...);
static unsigned long ProcessId();
static std::string NumberToHex(long num);
private:
std::ofstream m_logFile;
std::string m_logPath;
std::string m_currentDate;
mutable std::mutex m_logMutex;
std::set<LogLevel> m_enabledLogLevels;
LoggerErrorCallback m_errorCallback;
std::unordered_map<std::string, LogLevel> m_logLevelMap;
std::unordered_map<int, std::string> m_logLevelMapRef;
Logger();
~Logger();
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
void openLogFile();
void closeLogFile();
void logToFile(const std::string& message);
void logToFile(const std::string& message, std::ofstream& file);
void logToConsole(const std::string& message) const;
std::string formatString(const std::string& format, va_list args) const;
std::string getCurrentDate() const;
std::string getCurrentTime() const;
void initializeLogLevelMap();
};
inline Logger::Logger()
{
initializeLogLevelMap();
enableAllLogLevels();
}
inline Logger::~Logger()
{
closeLogFile();
}
inline void Logger::setLogPath(const std::string& path)
{
std::lock_guard<std::mutex> lock(m_logMutex);
m_logPath = path;
if (!m_logPath.empty() && m_logPath.back() != '/')
{
m_logPath += '/';
}
openLogFile();
}
inline void Logger::setLogLevels(const std::vector<std::string>& levels)
{
std::lock_guard<std::mutex> lock(m_logMutex);
m_enabledLogLevels.clear();
for (const auto& levelStr : levels)
{
std::string upperLevelStr = levelStr;
std::transform(upperLevelStr.begin(), upperLevelStr.end(), upperLevelStr.begin(), ::towupper);
auto it = m_logLevelMap.find(upperLevelStr);
if (it != m_logLevelMap.end())
{
m_enabledLogLevels.insert(it->second);
}
}
}
inline void Logger::setLogLevel(const std::string& levels)
{
std::lock_guard<std::mutex> lockm_(m_logMutex);
m_enabledLogLevels.clear();
std::stringstream ss(levels);
String levelStr;
while (std::getline(ss, levelStr, ','))
{
std::transform(levelStr.begin(), levelStr.end(), levelStr.begin(), ::toupper);
std::vector<std::string> level = levelStr.split("=");
if (level.size() == 2)
{
if (level.at(1) == "1")
{
m_enabledLogLevels.insert(m_logLevelMap[level.at(0)]);
}
}
}
}
inline void Logger::addLogLevel(LogLevel level)
{
std::lock_guard<std::mutex> lock(m_logMutex);
m_enabledLogLevels.insert(level);
}
inline void Logger::removeLogLevel(LogLevel level)
{
std::lock_guard<std::mutex> lock(m_logMutex);
m_enabledLogLevels.erase(level);
}
inline void Logger::enableAllLogLevels()
{
std::lock_guard<std::mutex> lock(m_logMutex);
for (const auto& pair : m_logLevelMap)
{
m_enabledLogLevels.insert(pair.second);
}
}
inline void Logger::disableAllLogLevels()
{
std::lock_guard<std::mutex> lock(m_logMutex);
m_enabledLogLevels.clear();
}
inline void Logger::setErrorCallback(LoggerErrorCallback callback)
{
std::lock_guard<std::mutex> lock(m_logMutex);
m_errorCallback = callback;
}
inline void Logger::log(LogLevel level, int line, const char* function, const char* format, ...)
{
std::lock_guard<std::mutex> lock(m_logMutex);
va_list va;
va_start(va, format);
std::string message = formatString(format, va);
va_end(va);
unsigned long pid = ProcessId();
std::string fullMessage = "[" + m_logLevelMapRef[(int)level] + "][0x" + NumberToHex(pid) + "]" + getCurrentTime() + " " + std::string(function) + ":" + std::to_string(line) + " " + message;
if ((level == LogLevel::Error || level == LogLevel::Fatal) && m_errorCallback)
{
m_errorCallback(fullMessage);
}
if (m_enabledLogLevels.find(level) == m_enabledLogLevels.end())
{
return;
}
std::string today = getCurrentDate();
if (today != m_currentDate)
{
closeLogFile();
m_currentDate = today;
openLogFile();
}
logToFile(fullMessage);
logToConsole(fullMessage);
}
inline void Logger::log(LogLevel level, int line, const char* function, const std::string& filename, const char* format, ...)
{
va_list va;
va_start(va, format);
std::string message = formatString(format, va);
va_end(va);
unsigned long pid = ProcessId();
std::string fullMessage = "[" + m_logLevelMapRef[(int)level] + "][0x" + NumberToHex(pid) + "]" + getCurrentTime() + " " + std::string(function) + ":" + std::to_string(line) + " " + message;
std::ofstream file(filename, std::ios::out | std::ios::app);
if (!file.is_open())
{
throw std::runtime_error("Failed to open log file: " + filename);
}
logToFile(fullMessage, file);
logToConsole(fullMessage);
}
inline void Logger::log(LogLevel level, int line, const char* function, std::ofstream& file, const char* format, ...)
{
va_list va;
va_start(va, format);
std::string message = formatString(format, va);
va_end(va);
unsigned long pid = ProcessId();
std::string fullMessage = "[" + m_logLevelMapRef[(int)level] + "][0x" + NumberToHex(pid) + "]" + getCurrentTime() + " " + std::string(function) + ":" + std::to_string(line) + " " + message;
logToFile(fullMessage, file);
logToConsole(fullMessage);
}
inline unsigned long Logger::ProcessId()
{
#ifdef _WIN32
DWORD pid = GetCurrentProcessId();
#else
pthread_t pid = pthread_self();
#endif
return pid;
}
inline std::string Logger::NumberToHex(long num)
{
std::stringstream ss;
ss << std::hex << num;
return ss.str();
}
inline void Logger::openLogFile()
{
if (m_logPath.empty())
{
return;
}
std::string filename = m_logPath + m_currentDate + ".log";
m_logFile.open(filename, std::ios::out | std::ios::app);
if (!m_logFile.is_open())
{
throw std::runtime_error("Failed to open log file: " + filename);
}
}
inline void Logger::closeLogFile()
{
if (m_logFile.is_open())
{
m_logFile.close();
}
}
inline void Logger::logToFile(const std::string& message)
{
if (m_logFile.is_open())
{
m_logFile << message << std::endl;
}
}
inline void Logger::logToFile(const std::string& message, std::ofstream& file)
{
if (file.is_open())
{
file << message << std::endl;
}
}
inline void Logger::logToConsole(const std::string& message) const
{
std::cerr << message << std::endl;
}
inline std::string Logger::formatString(const std::string& format, va_list args) const
{
va_list va;
va_copy(va, args);
int size = std::vsnprintf(nullptr, 0, format.c_str(), va);
va_end(va);
if (size < 0)
{
throw std::runtime_error("Failed to format log message");
}
std::vector<char> buffer(size + 1);
std::vsnprintf(buffer.data(), buffer.size(), format.c_str(), args);
return std::string(buffer.data());
}
inline std::string Logger::getCurrentDate() const
{
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::tm tm = *std::localtime(&time);
std::stringstream ss;
ss << std::put_time(&tm, "%Y-%m-%d");
return ss.str();
}
inline std::string Logger::getCurrentTime() const
{
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::tm tm = *std::localtime(&time);
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()
) % 1000;
std::stringstream ss;
ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") << "." << std::setfill('0') << std::setw(3) << milliseconds.count();
return ss.str();
}
inline void Logger::initializeLogLevelMap()
{
m_logLevelMap = {
{"T", LogLevel::Trace},
{"D", LogLevel::Debug},
{"I", LogLevel::Info},
{"W", LogLevel::Warning},
{"E", LogLevel::Error},
{"F", LogLevel::Fatal}
};
for (std::unordered_map<std::string, LogLevel>::iterator it = m_logLevelMap.begin(); it != m_logLevelMap.end(); it++)
{
m_logLevelMapRef[(int)it->second] = it->first;
}
}
#define LOG(level, format, ...) \
Logger::getInstance().log(level, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)
#define TRACE(format, ...) LOG(LogLevel::Trace, format, ##__VA_ARGS__)
#define DEBUG(format, ...) LOG(LogLevel::Debug, format, ##__VA_ARGS__)
#define INFO(format, ...) LOG(LogLevel::Info, format, ##__VA_ARGS__)
#define WARN(format, ...) LOG(LogLevel::Warning, format, ##__VA_ARGS__)
#define ERROR(format, ...) LOG(LogLevel::Error, format, ##__VA_ARGS__)
#define FATAL(format, ...) LOG(LogLevel::Fatal, format, ##__VA_ARGS__)
#define LogFile(level, file, format, ...) Logger::getInstance().log(level, __LINE__, __FUNCTION__, file, format, ##__VA_ARGS__)
#define LogToFile(level, file, format, ...) Logger::getInstance().log(level, __LINE__, __FUNCTION__, file, format, ##__VA_ARGS__)
#endif
#pragma once
#include <string>
#include <vector>
#include <regex>
class String :public std::string
{
public:
String() :std::string("") { }
String(const String& str) :std::string(str) { }
String(const std::string& str) :std::string(str) { }
String(const char* str) :std::string(str) { }
std::vector<std::string> split(const std::string& delimiter);
};
inline std::vector<std::string> String::split(const std::string& delimiter)
{
std::vector<std::string> tokens;
std::regex reg(delimiter);
std::sregex_token_iterator iter(this->begin(), this->end(), reg, -1);
std::sregex_token_iterator end;
while (iter != end)
{
tokens.push_back(*iter++);
}
return tokens;
}