池化技术
池化技术可以减少底层重复工作,例如创建进程、线程、申请内存空间时的系统调用和初始化工作。线程池是执行流级别的池化技术,STL 中的空间配置器和内存池是内存块管理级别的池化技术。
C++ 手写日志模块采用策略模式设计,支持控制台与文件两种刷新策略。通过枚举定义日志等级,利用 C++17 filesystem 管理日志目录,结合 RAII 内部类 LogMessage 实现日志拼接与自动刷新。代码包含时间戳获取、线程安全锁保护及宏定义封装,解决了日志输出格式统一与性能问题。

池化技术可以减少底层重复工作,例如创建进程、线程、申请内存空间时的系统调用和初始化工作。线程池是执行流级别的池化技术,STL 中的空间配置器和内存池是内存块管理级别的池化技术。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online
结合之前的封装进行线程池设计,需要完成以下准备:
什么是设计模式 针对一些经典的常见场景,给定了对应的解决方案。
什么是日志 计算机中的日志是记录系统和软件运行中发生事件的文件,主要作用是监控运行状态、记录异常信息,帮助快速定位问题并支持程序员进行问题修复。
日志格式必须包含以下指标:
可选指标包括:
现成的解决方案如 spdlog、glog 等,此处采用自定义日志方式。采用设计模式中的策略模式进行日志设计。
枚举类型中的枚举值底层存储为整数,需转换成字符串输出。
// Logger.hpp
enum class LogLevel {
DEBUG,
INFO, // 正常消息
WARNING, // 出现错误,但不影响程序运行
ERROR, // 导致程序退出的错误
FATAL // 重大错误
};
std::string Level_to_string(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";
}
}
利用 C++ 的多态特性,先创建一个日志刷新基类,然后根据日志具体往哪刷新写具体的派生类函数。
<filesystem>,在派生类 FileLogStrategy 的构造函数里先将指定目录文件创建好。判断在当前工作目录下指定目录文件存不存在,若存在直接返回,若存在则新建该指定目录文件。创建目录时需要 try-catch 捕获异常。// logger.hpp
// 策略模式,策略接口
class LogStrategy {
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string &logmessage) = 0;
};
// 控制台日志策略
class ConsoleLogStrategy : public LogStrategy {
public:
~ConsoleLogStrategy() {}
void SyncLog(const std::string &logmessage) override {
{ LockGuard lockguard(&_lock);
std::cout << logmessage << std::endl;
}
}
private:
Mutex _lock;
};
// 日志向文件打印
const std::string logdefaultpath = "log";
const static std::string logdefaultfilename = "test.log";
class FileLogStrategy : public LogStrategy {
public:
FileLogStrategy(const std::string &dir = logdefaultpath, const std::string &filename = logdefaultfilename)
:_dir_path_name(dir),_filename(filename) {
{ LockGuard lockguard(&_lock);
if(std::filesystem::exists(_dir_path_name)) return;
try {
std::filesystem::create_directories(_dir_path_name);
} catch(const std::filesystem::filesystem_error &e) {
std::cerr << e.what()<<'
';
}
}
}
void SyncLog(const std::string &logmessage) override {
{ LockGuard lockguard(&_lock);
std::string target = _dir_path_name; target += "/"; target += _filename;
std::ofstream out(target.c_str(), std::ios::app);
if(!out.is_open()) return;
out << logmessage <<"
";
out.close();
}
}
~FileLogStrategy() {}
private:
std::string _dir_path_name;
std::string _filename;
Mutex _lock;
};
测试代码:
// main.cc
#include "Logger.hpp"
int main() {
std::unique_ptr<LogStrategy> logger_ptr = std::make_unique<FileLogStrategy>();
logger_ptr->SyncLog("test");
return 0;
}
std::string GetCurrentTime() {
time_t curtime = time(nullptr);
struct tm currtm;
localtime_r(&curtime,&currtm);
char timebuffer[64];
snprintf(timebuffer,sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d", currtm.tm_year +1900, currtm.tm_mon +1, currtm.tm_mday, currtm.tm_hour, currtm.tm_min, currtm.tm_sec );
return timebuffer;
}
激活策略,开启什么策略,就把策略对应的派生类对象创建出来。
class Logger {
public:
Logger() {}
void EnableConsoleLogStrategy(){ _strategy = std::make_unique<ConsoleLogStrategy>(); }
void EnableFileLogStrategy(){ _strategy = std::make_unique<FileLogStrategy>(); }
~Logger() {}
private:
std::unique_ptr<LogStrategy> _strategy;
};
利用内部类 LogMessage 将各种日志信息拼接成一个字符串,在 LogMessage 的析构函数中调用 logger 成员变量_strategy 的刷新函数将字符串刷新出去,这也是 RAII 风格的设计思路。
LogMessage 的构造函数利用初始化列表初始化,然后用 stringstream 类对象 ss 将各种类型的成员变量格式化为字符串。 LogMessage 的 operator<< 重载输出运算符,参数类型设置成模板,这样任意类型参数都可以输出。 LogMessage 的析构函数中将_loginfo 的日志信息刷新出去。
//logger.hpp
#pragma once
#include<iostream>
#include<filesystem>
#include<fstream>
#include<string>
#include<sstream>
#include<ctime>
#include<memory>
#include<unistd.h>
#include"Mutex.hpp"
enum class LogLevel {
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string Level_to_string(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 GetCurrentTime() {
time_t curtime = time(nullptr);
struct tm currtm;
localtime_r(&curtime,&currtm);
char timebuffer[64];
snprintf(timebuffer,sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d", currtm.tm_year +1900, currtm.tm_mon +1, currtm.tm_mday, currtm.tm_hour, currtm.tm_min, currtm.tm_sec);
return timebuffer;
}
class LogStrategy {
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string &logmessage) = 0;
};
class ConsoleLogStrategy : public LogStrategy {
public:
~ConsoleLogStrategy() {}
void SyncLog(const std::string &logmessage) override {
{ LockGuard lockguard(&_lock);
std::cout << logmessage << std::endl;
}
}
private:
Mutex _lock;
};
const std::string logdefaultpath = "log";
const static std::string logdefaultfilename = "test.log";
class FileLogStrategy : public LogStrategy {
public:
FileLogStrategy(const std::string &dir = logdefaultpath, const std::string &filename = logdefaultfilename)
:_dir_path_name(dir),_filename(filename) {
{ LockGuard lockguard(&_lock);
if(std::filesystem::exists(_dir_path_name)) return;
try {
std::filesystem::create_directories(_dir_path_name);
} catch(const std::filesystem::filesystem_error &e) {
std::cerr << e.what()<<'
';
}
}
}
void SyncLog(const std::string &logmessage) override {
{ LockGuard lockguard(&_lock);
std::string target = _dir_path_name; target += "/"; target += _filename;
std::ofstream out(target.c_str(), std::ios::app);
if(!out.is_open()) return;
out << logmessage <<"
";
out.close();
}
}
~FileLogStrategy() {}
private:
std::string _dir_path_name;
std::string _filename;
Mutex _lock;
};
class Logger {
public:
Logger() {}
void EnableConsoleLogStrategy(){ _strategy = std::make_unique<ConsoleLogStrategy>(); }
void EnableFileLogStrategy(){ _strategy = std::make_unique<FileLogStrategy>(); }
class LogMessage {
public:
LogMessage(LogLevel level, std::string filename,int line, Logger &logger)
:_curr_time(GetCurrentTime()),_level(level),_pid(getpid()),_filename(filename),_line(line),_logger(logger) {
std::stringstream ss;
ss <<"["<< _curr_time <<"] "<<"["<<Level_to_string(_level)<<"] "<<"["<< _pid <<"] "<<"["<< _filename <<"] "<<"["<< _line <<"] ";
_loginfo = ss.str();
}
template<typenameT> LogMessage &operator<<(const T &info) {
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return*this;
}
~LogMessage() {
if(_logger._strategy){ _logger._strategy->SyncLog(_loginfo); }
}
private:
std::string _curr_time;
LogLevel _level;
pid_t _pid;
std::string _filename;
int _line;
std::string _loginfo;
Logger &_logger;
};
LogMessage operator()(LogLevel level, std::string filename,int line) {
return LogMessage(level, filename, line,*this);
}
~Logger() {}
private:
std::unique_ptr<LogStrategy> _strategy;
};
Logger logger;
#define LOG(level) logger(level,__FILE__,__LINE__)
#define EnableConsoleLogStrategy() logger.EnableConsoleLogStrategy()
#define EnableFileLogStrategy() logger.EnableFileLogStrategy()
//main.cc
#include"Logger.hpp"
#include<unistd.h>
int main() {
EnableConsoleLogStrategy();
LOG(LogLevel::ERROR)<<"hello world"<<1234<<", 3.14 "<<'c';
LOG(LogLevel::WARNING)<<"hello world"<<1234<<", 3.14 "<<'c';
return 0;
}