池化技术:预制,减少系统调用的次数,提高效率。
日志
完整代码:
#ifndef __LOG_HPP__ #define __LOG_HPP__ #include <iostream> #include <string> #include <filesystem> #include <fstream> #include <memory> #include <unistd.h> #include <sys/types.h> #include <ctime> #include "Mutex.hpp" namespace LogModule { using namespace MutexModule; // 日志刷新策略 class LogStrategy { public: ~LogStrategy() = default; virtual void SyncLog(const std::string &message) = 0; }; // 控制台刷新 class ConsoleLogStrategy : public LogStrategy { public: ConsoleLogStrategy() { } ~ConsoleLogStrategy() { } virtual void SyncLog(const std::string &message) override { MutexGuard mutexguard(_mutex); std::cout << message << std::endl; } private: Mutex _mutex; }; static const std::string default_path = "./log"; static const std::string default_file = "my.log"; static const std::string sep = "\r\n"; // 文件刷新 class FileLogStrategy : public LogStrategy { public: FileLogStrategy(const std::string &path = default_path, const std::string &file = default_file) : _path(path), _file(file) { { // 保证多线程时的线程安全 MutexGuard mutexguard(_mutex); // 如果已经存在目录,返回 if (std::filesystem::exists(_path)) return; try { // 创建目录结构 std::filesystem::create_directories(_path); } catch (const std::filesystem::filesystem_error &e) { std::cerr << e.what() << std::endl; } } } ~FileLogStrategy() { } virtual void SyncLog(const std::string &message) override { std::string pathname = _path + (_path.back() == '/' ? "" : "/") + _file; // 以追加方式打开文件 std::ofstream ofs(pathname, std::ios::app); ofs << message << sep; ofs.close(); } private: std::string _path; std::string _file; Mutex _mutex; }; enum class LogLevel { DEBUG, INFO, WARNING, ERROR, FATAL }; std::string GetLevel(LogLevel level) { switch (level) { case LogLevel::DEBUG: return "DEBUG"; case LogLevel::INFO: return "INFO"; case LogLevel::WARNING: return "WARNING"; case LogLevel::ERROR: return "ERROR"; case LogLevel::FATAL: return "FATAL"; default: return "UNKNOWN"; } } std::string GetTime() { time_t timestamp = time(nullptr); struct tm data; localtime_r(×tamp, &data); char buff[128]; snprintf(buff, 128, "%4d-%02d-%02d %02d:%02d:%02d", data.tm_year + 1900, data.tm_mon + 1, data.tm_mday, data.tm_hour, data.tm_min, data.tm_sec); return buff; } class Log { public: Log() { } ~Log() { } void EnableConsoleLogStrategy() { _sl = std::make_unique<ConsoleLogStrategy>(); } void EnableFileLogStrategy() { _sl = std::make_unique<FileLogStrategy>(); } // 格式化的左边消息 class LogInfo { public: LogInfo(Log *log, LogLevel level, const std::string &filename, const unsigned int line) : _time(GetTime()), _level(level), _pid(getpid()), _filename(filename), _line(line), _log(log) { std::stringstream ss; ss << "[" << _time << "] " << "[" << GetLevel(_level) << "] " << "[" << _pid << "] " << "[" << _filename << "] " << "[" << _line << "] " << "- "; _message = ss.str(); } // LogInfo销毁时,刷新 ~LogInfo() { if (_log->_sl) { _log->_sl->SyncLog(_message); } } template <typename T> LogInfo &operator<<(const T &data) { std::stringstream ss; ss << data; _message += ss.str(); return *this; } private: std::string _time; // 时间,年月日时分秒 LogLevel _level; // 日志等级 pid_t _pid; // 进程pid std::string _filename; // 打印日志对应的文件名 unsigned int _line; // 行号 std::string _message; // 整条信息,左边+右边 // Log的指针 Log *_log; }; // 重载函数调用,用仿函数的形式调用,且隐式传递自己的this指针 // 返回LogInfo的匿名对象,生命周期只有一行,一行结束就调用析构,刷新。 LogInfo operator()(LogLevel level, const std::string &filename, const unsigned int line) { return LogInfo(this, level, filename, line); } private: std::unique_ptr<LogStrategy> _sl; // 策略基类指针 }; Log log; #define Enable_Console_log_Strategy() log.EnableConsoleLogStrategy() #define Enable_File_log_Strategy() log.EnableFileLogStrategy() #define LOG(level) log(level, __FILE__, __LINE__) } #endif
效果:
调用:
Enable_Console_log_Strategy();
Enable_File_log_Strategy();
LOG(LogLevel::DEBUG) << "hello world";
细节点1:
时间相关函数<time.h>
struct tm *localtime_r(const time_t *timep, struct tm *result);
参数timep为时间戳,可通过time(nullptr);获取
参数result为输出型参数
细节点2:
多线程情况下,目录结构为临界资源,需要加锁。同样的,显示器文件也要加锁。
细节点3:
对于日志右边信息处理时,为了使用便利。重载流插入,并以模板的形式,支持stringstream流插入支持的类型。返回值返回引用,用于连续多次插入。
细节点4:
采用返回LogInfo的匿名对象,生命周期只有一行,一行结束就调用析构,析构中在设置刷新,就可以达到一行一行刷新日志的目的。
细节点5:
Log.hpp中定义一个全局的Log类型的变量。
采用宏替换和预处理的两个宏:__FILE__(文件名),__LINE__(行号)
使得使用更加便利和雅观。