编辑
2024-01-31
学习
00
请注意,本文编写于 325 天前,最后修改于 322 天前,其中某些信息可能已经过时。

image.png

模仿 fmtlib 的格式化方式,使用 {} 作为占位符 待改进:

  • 占位符的更多功能尚未扩展
  • {{}} 这种括号包括号会出错
  • 性能待提升

代码如下

CPP
// Copyright (c) RealCoolEngineer. 2024. All rights reserved. // Author: beiklive // Date: 2024-01-31 #ifndef INC_LOG_HH_ #define INC_LOG_HH_ #include <iostream> #include <sstream> #include <iomanip> #include <mutex> #include <fstream> #include <sys/stat.h> #include <cstdlib> #include <memory> #ifdef _WIN32 #include <direct.h> #else #include <unistd.h> // For Unix/Linux #endif namespace beiklive { namespace LOG { class FileLogger; enum class LOGLEVEL { ERROR, WARNING, INFO, DEBUG }; enum class OUTPUT { NONE, CONSOLE, FILE, ALL }; enum class ColorCode { RESET = 0, DEBUG = 34, // Blue INFO = 32, // Green WARNING = 33, // Yellow ERROR = 31 // Red }; class FileLogger { public: FileLogger() : logFile(nullptr) {} ~FileLogger() { if (logFile && logFile->is_open()) { // 刷新并关闭文件 flush(); logFile->close(); } } void initializeLogFile(const std::string& filePath) { logFile.reset(new std::ofstream(filePath, std::ios::app)); if (!logFile->is_open()) { std::cerr << "Error opening log file: " << filePath << std::endl; } else { currentFilePath = filePath; // 设置缓冲区大小 logFile->rdbuf()->pubsetbuf(0, 1024); } } void logMessage(const std::string& message) { if (logFile && logFile->is_open()) { (*logFile) << message << std::endl; } } void switchLogFile(const std::string& newFilePath) { if (logFile && logFile->is_open()) { // 刷新并关闭当前文件 flush(); logFile->close(); // 初始化新文件 initializeLogFile(newFilePath); } } private: std::unique_ptr<std::ofstream> logFile; std::string currentFilePath; void flush() { if (logFile && logFile->is_open()) { logFile->flush(); } } }; namespace { LOGLEVEL loglevel_ = LOGLEVEL::INFO; OUTPUT output_ = OUTPUT::CONSOLE; std::string logFilePath_ = "./log"; std::string CurLogFile_; std::string CurCycleLogDirName_; long long MaxSingleLogFileSize_ = 1024 * 1024 * 10; // 10MB FileLogger filelogger; std::mutex logMutex; } //*FILE *************************************************************** std::string generateLogFileName() { auto now = std::chrono::system_clock::now(); auto millisec = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch() ).count() % 1000; // 获取当前日期和时间 auto timeNow = std::chrono::system_clock::to_time_t(now); std::tm tmNow = *std::localtime(&timeNow); // 格式化日期和时间 std::stringstream ss; ss << std::put_time(&tmNow, "%Y%m%d_%H_%M_%S_") << std::setw(3) << std::setfill('0') << millisec; return ss.str(); } bool createDirectory(const std::string& directoryPath) { // 检查目录是否已存在 if (access(directoryPath.c_str(), 0) == 0) { std::cout << "Directory already exists: " << directoryPath << std::endl; return true; } #ifdef _WIN32 int result = _mkdir(directoryPath.c_str()); #else mode_t mode = 0755; // 权限设置为rwxr-xr-x int result = mkdir(directoryPath.c_str(), mode); #endif if (result == 0) { std::cout << "Directory created successfully: " << directoryPath << std::endl; return true; } else { std::perror(std::string("Error creating directory: " + directoryPath).c_str()); return false; } } bool isFileSizeOverSize(const std::string& filename) { std::lock_guard<std::mutex> lock(logMutex); std::ifstream file(filename, std::ios::binary | std::ios::ate); if (!file.is_open()) { std::cerr << "Error opening file: " << filename << std::endl; return false; // Unable to open the file } std::streamsize fileSize = file.tellg(); // Get the file size file.close(); // Close the file return fileSize > MaxSingleLogFileSize_; } void endsWithSlash(std::string& str) { if (!str.empty()) { char lastChar = str.back(); if (lastChar == '/') { return; } else { str += '/'; } } } void LogFileRotation(const std::string& msg) { // 目录初始化 if (CurCycleLogDirName_.empty()) { createDirectory(logFilePath_); CurCycleLogDirName_ = generateLogFileName(); endsWithSlash(logFilePath_); createDirectory(logFilePath_ + CurCycleLogDirName_); } if (CurLogFile_.empty()) { CurLogFile_ = generateLogFileName() + ".log"; std::cout << "New logfile : " << CurLogFile_ << std::endl; filelogger.initializeLogFile(logFilePath_ + CurCycleLogDirName_ + "/" + CurLogFile_); } if (isFileSizeOverSize(logFilePath_ + CurCycleLogDirName_ + "/" + CurLogFile_)) { CurLogFile_ = generateLogFileName() + ".log"; std::cout << "Switch to new logfile : " << CurLogFile_ << std::endl; filelogger.switchLogFile(logFilePath_ + CurCycleLogDirName_ + "/" + CurLogFile_); } filelogger.logMessage(msg); } void LogFileSizeSet(const long& maxSize = 1024 * 1024 * 10) { MaxSingleLogFileSize_ = maxSize; } void LogFilePathSet(const std::string& dirPath) { if(createDirectory(dirPath)) { logFilePath_ = dirPath; } std::cout << "Use log path : " << logFilePath_ << std::endl; } //*************************************************************** void LoggerOutputSet(const OUTPUT set) { std::lock_guard<std::mutex> lock(logMutex); output_ = set; } void LoggerLevelSet(const LOGLEVEL set) { std::lock_guard<std::mutex> lock(logMutex); loglevel_ = set; } void LoggerStop() { std::lock_guard<std::mutex> lock(logMutex); output_ = OUTPUT::NONE; } bool isEnableOutput() { return !(output_ == OUTPUT::NONE); } bool isConsoleOutput() { return (output_ == OUTPUT::CONSOLE || output_ == OUTPUT::ALL); } bool isFileOutput() { return (output_ == OUTPUT::FILE || output_ == OUTPUT::ALL); } #define COLOR(level, message) \ (std::string("\033[") + std::to_string(static_cast<int>(level)) + "m" + message + "\033[0m") #define GET_FUNCTION_NAME() (std::string(__PRETTY_FUNCTION__) + ":" + std::to_string(__LINE__)) std::string getCurrentTimestamp() { auto now = std::chrono::system_clock::now(); auto currentTime = std::chrono::system_clock::to_time_t(now); auto millisec = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch() ).count() % 1000; std::tm tmNow = *std::localtime(&currentTime); std::stringstream timestamp; timestamp << std::put_time(&tmNow, "%Y-%m-%d %H:%M:%S.") << std::setw(3) << std::setfill('0') << millisec;; return timestamp.str(); } std::string format(const std::string& pattern) { return pattern; } template <typename T, typename... Args> std::string format(const std::string& pattern, T first, Args &&...args) { std::stringstream ss; size_t pos = pattern.find("{"); size_t pos2 = pattern.find("}", pos); if (pos != std::string::npos && pos2 != std::string::npos) { ss << pattern.substr(0, pos) << first << pattern.substr(pos2 + 1); } else { ss << pattern; } return format(ss.str(), args...); } template <typename T, typename K, typename... Args> void MACRO_LOG_OUTPUT(const LOGLEVEL level, T first, K pattern, Args &&...args) { if (isEnableOutput()) { if (loglevel_ >= level) { std::stringstream ss; ss << "["; ss << first; ss << "] "; ss << pattern; LOG_OUTPUT(level, ss.str(), args...); } } } template <typename... Args> void LOG_OUTPUT(const LOGLEVEL level, const std::string& pattern, Args &&...args) { std::string&& s = format(pattern, args...); std::stringstream ss; ss << "[" << getCurrentTimestamp() << "]"; ss << " "; if (isConsoleOutput()) { std::stringstream sss; sss << ss.str(); switch (level) { case LOGLEVEL::INFO: sss << "[" << COLOR(ColorCode::INFO, "I") << "]"; break; case LOGLEVEL::WARNING: sss << "[" << COLOR(ColorCode::WARNING, "W") << "]"; break; case LOGLEVEL::ERROR: sss << "[" << COLOR(ColorCode::ERROR, "E") << "]"; break; case LOGLEVEL::DEBUG: sss << "[" << COLOR(ColorCode::DEBUG, "D") << "]"; break; } sss << " "; sss << s; std::cout << sss.str() << std::endl; } if (isFileOutput()) { std::stringstream sss; sss << ss.str(); switch (level) { case LOGLEVEL::INFO: sss << "[I]"; break; case LOGLEVEL::WARNING: sss << "[W]"; break; case LOGLEVEL::ERROR: sss << "[E]"; break; case LOGLEVEL::DEBUG: sss << "[D]"; break; } sss << " "; sss << s; LogFileRotation(sss.str()); } } template <typename T, typename... Args> void info(T pattern, Args &&...args) { if (loglevel_ >= LOGLEVEL::INFO && isEnableOutput()) LOG_OUTPUT(LOGLEVEL::INFO, pattern, args...); } template <typename T, typename... Args> void warning(T pattern, Args &&...args) { if (loglevel_ >= LOGLEVEL::WARNING && isEnableOutput()) LOG_OUTPUT(LOGLEVEL::WARNING, pattern, args...); } template <typename T, typename... Args> void error(T pattern, Args &&...args) { if (loglevel_ >= LOGLEVEL::ERROR && isEnableOutput()) LOG_OUTPUT(LOGLEVEL::ERROR, pattern, args...); } template <typename T, typename... Args> void debug(T pattern, Args &&...args) { if (loglevel_ >= LOGLEVEL::DEBUG && isEnableOutput()) LOG_OUTPUT(LOGLEVEL::DEBUG, pattern, args...); } } // namespace log #define LOG_INFO(...) LOGGER_INFO(__VA_ARGS__) #define LOG_WARNING(...) LOGGER_WARNING(__VA_ARGS__) #define LOG_ERROR(...) LOGGER_ERROR(__VA_ARGS__) #define LOG_DEBUG(...) LOGGER_DEBUG(__VA_ARGS__) #define LOGGER_INFO(...) MACRO_LOG_OUTPUT(beiklive::LOG::LOGLEVEL::INFO, GET_FUNCTION_NAME(), __VA_ARGS__) #define LOGGER_WARNING(...) MACRO_LOG_OUTPUT(beiklive::LOG::LOGLEVEL::WARNING, GET_FUNCTION_NAME(), __VA_ARGS__) #define LOGGER_ERROR(...) MACRO_LOG_OUTPUT(beiklive::LOG::LOGLEVEL::ERROR, GET_FUNCTION_NAME(), __VA_ARGS__) #define LOGGER_DEBUG(...) MACRO_LOG_OUTPUT(beiklive::LOG::LOGLEVEL::DEBUG, GET_FUNCTION_NAME(), __VA_ARGS__) } // namespace beiklive #endif // INC_LOG_HH_

main.cpp

cpp
#include <iostream> #include "log.hh" int main() { beiklive::LOG::LoggerOutputSet(beiklive::LOG::OUTPUT::ALL); beiklive::LOG::LogFileSizeSet(1024*1024*10); // 10MB beiklive::LOG::LogFilePathSet("/home/beiklive/WorkSpace/MyCode/clearn"); int i = 0; beiklive::LOG::info("this is info {}", i); beiklive::LOG::warning("this is warning {}", i); beiklive::LOG::error("this is error {}", i); beiklive::LOG::debug("this is debug {}", i); i++; beiklive::LOG::LoggerLevelSet(beiklive::LOG::LOGLEVEL::DEBUG); beiklive::LOG::info("this is info {}", i); beiklive::LOG::warning("this is warning {}", i); beiklive::LOG::error("this is error {}", i); beiklive::LOG::debug("this is debug {}", i); i++; beiklive::LOG::info("this is info {}", i); beiklive::LOG::warning("this is warning {}", i); beiklive::LOG::error("this is error {}", i); beiklive::LOG::debug("this is debug {}", i); return 0; }

本文作者:beiklive

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!