1-1⽇志与策略模式
什么是设计模式
IT⾏业这么⽕,涌⼊的⼈很多.俗话说林⼦⼤了啥⻦都有.⼤佬和菜鸡们两极分化的越来越严重.为了让菜鸡们不太拖⼤佬的后腿,于是⼤佬们针对⼀些经典的常⻅的场景,给定了⼀些对应的解决⽅案,这个就是设计模式
⽇志认识
计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件,主要作⽤是监控运⾏状态、记录异常信息,帮助快速定位问题并⽀持程序员进⾏问题修复。它是系统维护、故障排查和安全管理的重要⼯具。
⽇志格式以下⼏个指标是必须得有的
• 时间戳
• ⽇志等级
• ⽇志内容
以下⼏个指标是可选的
• ⽂件名⾏号
• 进程,线程相关id信息等
⽇志有现成的解决⽅案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依旧采⽤⾃定义⽇志的⽅式。
这⾥我们采⽤设计模式-策略模式来进⾏⽇志的设计,具体策略模式介绍,详情看代码和课程。
我们想要的⽇志格式如下:

cpp
#include<iostream>
#include<string>
#include <fstream>
#include <memory>
#include <ctime>
#include <sstream>
#include <filesystem> // C++17, 需要⾼版本编译器和-std=c++17
#include <unistd.h>
#include "Lock.hpp"
namespace LogModule
{
//默认路径与日志名称
const std::string defaultpath = "./log/";
const std::string defaultname = "log.txt";
//日志等级
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
//日志转换成字符串
std::string LogLevelToString(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 "UNKNOW";
}
}
//根据时间戳,获取可读性较强的时间信息
std::string GetCurrTime()
{
time_t tm = time(nullptr);
struct tm curr;
localtime_r(&tm,&curr);
char timebuffer[64];
snprintf(timebuffer,sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",
curr.tm_year + 1900,
curr.tm_mon + 1,
curr.tm_mday,
curr.tm_hour,
curr.tm_min,
curr.tm_sec);
return timebuffer;
}
//策略模式,策略接口
class LogStrategy
{
public:
//触发子类析构 → 打印日志(核心功能)再触发基类默认析构 → 空,安全回收
virtual ~LogStrategy() = default;//如果基类析构不是虚函数,只会调用基类析构,派生类析构不执行 → 内存泄漏、文件不关闭、日志没刷新!
virtual void SyncLog(const std::string &message) = 0;//不同模式核心刷新方式不同
};
//显示器打印
class ConsoleLogStrategy:public LogStrategy
{
public:
void SyncLog(const std::string &message) override
{
LockGuard lock(_mutex);
std::cerr<<message<<std::endl;
}
private:
Mutex _mutex;//显示器也是临界资源
};
//文件日志策略
class FileLogStrategy : public LogStrategy
{
public:
//构造函数,建立出指定的目录结构和文件结构
FileLogStrategy(const std::string& logpath = defaultpath,const std::string& logfilename = defaultname)
:_logpath(logpath),
_logfilename(logfilename)
{
LockGuard lockguard(_mutex);
if(std::filesystem::exists(_logpath))
return;
try
{
std::filesystem::create_directories(_logpath);
}
catch(const std::filesystem::filesystem_error &e)
{
std::cerr<<e.what()<<'\n';
}
}
//将一条日志信息写入文件中
void SyncLog(const std::string &message)override
{
LockGuard lockguard(_mutex);
std::string log = _logpath+_logfilename;
std::ofstream out(log.c_str(),std::ios::app);//追加模式
if(!out.is_open())
{
return;
}
out<<message<<'\n';
out.close();
}
private:
std::string _logpath;
std::string _logfilename;
Mutex _mutex;
};
// 具体的日志类
class Logger
{
public:
Logger()
{
// 默认使用显示器策略
UseConsoleStrategy();
}
~Logger() = default;
void UseConsoleStrategy()
{
_strategy = std::make_unique<ConsoleLogStrategy>();
}
void UseFileStrategy()
{
// 修复:正确的类名 + 正确的 make_unique 写法
_strategy = std::make_unique<FileLogStrategy>();
}
// 内部类 RAII 风格
class LogMessage
{
private:
LogLevel _type; // 日志等级
std::string _curr_time; // 日志时间
pid_t _pid; // 进程ID
std::string _filename; // 写入文件名
int _line; // 文件行号
Logger& _logger; // 引用外部logger类,方便使用策略进行刷新
std::string _loginfo; // 完整日志信息
public:
// RAII风格,构造的时候构建好日志头部信息
LogMessage(LogLevel type, const std::string& filename, int line, Logger& logger)
: _type(type),
_curr_time(GetCurrTime()),
_pid(getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
std::stringstream ssbuffer;
ssbuffer << "[" << _curr_time << "]"
<< "[" << LogLevelToString(type) << "]"
<< "[" << _pid << "]"
<< "[" << _filename << "]"
<< "[" << _line << "]"
<< " - ";
_loginfo = ssbuffer.str();
}
// 重载<<支持c++风格的日志输入,使用模版
template <typename T>
LogMessage& operator<<(const T& info)
{
std::stringstream ssbuffer;
ssbuffer << info;
_loginfo += ssbuffer.str();
return *this; // 返回当前LogMessage对象,方便下次继续进行<<
}
// RAII风格,析构的时候进行日志持久化
~LogMessage()
{
if (_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
};
// 故意拷贝,形成LogMessage临时对象,后续再<<时,会持续引用
// 直到完成输入,才会自动析构临时LogMessage,完成日志刷新
LogMessage operator()(LogLevel type, const std::string& filename, int line)
{
return LogMessage(type, filename, line, *this);
}
private:
std::unique_ptr<LogStrategy> _strategy;
};
inline Logger logger;
#define Log(type) logger(LogModule::LogLevel::type, __FILE__, __LINE__)
// 提供选择使用何种日志策略的方法
#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy()
#define ENABLE_FILE_LOG_STRATEGY() logger.UseFileStrategy()
}
cpp
#include "Log.hpp"
using namespace LogModule;
int main()
{
// 默认打印到控制台
Log(INFO) << "服务器启动成功!";
Log(DEBUG) << "加载配置完成";
Log(WARNING) << "磁盘空间不足";
Log(ERROR) << "网络连接失败";
Log(FATAL) << "程序崩溃";
// 切换成文件输出
ENABLE_FILE_LOG_STRATEGY();
Log(INFO) << "这条日志写入文件";
return 0;
}


调用operator():创建临时LogMessage对象 A,返回 A 的值(临时对象)
第一次<<:给 A 追加"服务器启动",返回 A 的引用(A 还活着)
第二次<<:给 A 追加"用户登录",返回 A 的引用(A 还活着)
第三次<<:给 A 追加endl,返回 A 的引用(A 还活着)
语句结束(分号):A 的生命周期结束,自动调用~A(),把完整日志写入
A 被销毁,内存完全回收,无泄漏
执行流程:
Log(INFO) → 调用 operator(),返回一个 临时 LogMessage 对象
接着调用 operator<<("服务器启动"),给这个临时对象追加内容
语句结束(遇到;) → 临时对象 作用域结束 → 编译器 自动调用~LogMessage ()