24.策略模式实现日志
日志的介绍
计算机中的日志是记录系统和软件运行中发送事件的文件,主要作用是监控运行状态、记录异常信息,帮助快速定位问题并支持程序员进行问题修复。它是系统维护、故障排查和安全管理的重要工具。
日志格式以下几个指标是必须得有的:
• 时间戳
• 日志等级
• 日志内容
以下几个指标是可选的:
• 文件名
• 行号
• 进程与线程相关id信息
日志有现成的解决方案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依旧采用自定义日志的方式。
这里我们采用设计模式中的策略模式来进行日志的设计,我们想要的日志格式如下:
#[可读性很好的时间] [⽇志等级] [进程pid] [打印对应⽇志的文件名][行号] - 消息内容,支持可变参数
[2025-2-16 9:13:13] [DEBUG] [2422165] [Log.cc] [12] - hello world
[2025-2-16 9:13:13] [DEBUG] [2422165] [Log.cc] [13] - hello world
[2025-2-16 9:13:13] [DEBUG] [2422165] [Log.cc] [14] - hello world
[2025-2-16 9:13:13] [DEBUG] [2422165] [Log.cc] [16] - hello world
[2025-2-16 9:13:13] [DEBUG] [2422165] [Log.cc] [17] - hello world
所以我们想要实现的日志需要实现两个部分:日志信息的生成与落盘方式的构建(显示器or文件)。这里我们采用策略模式生成落盘方式,例用内部类生成信息,最终完成日志的输出。
日志的实现
#include <string>
#include <iostream>
#include <fstream>
#include <memory>
#include <ctime>
#include <sstream>
#include <filesystem>
#include <unistd.h>
#include "mutex.hpp"
namespace LogMudual
{
using namespace std;
using namespace MutexModel;
// 默认路径和日志名称
const string defaultpath = "./log/";
const string defaultname = "log.txt";
// 日志等级
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
// 根据时间戳,获取可读性较强的时间信息
string GetCurTime()
{
time_t tm = time(nullptr);
struct tm curr;
localtime_r(&tm, &curr);
stringstream ss;
ss << curr.tm_year + 1900 << "-"
<< curr.tm_mon << "-"
<< curr.tm_mday << " "
<< curr.tm_hour << ":"
<< curr.tm_min << ":"
<< curr.tm_sec;
return ss.str();
}
//将日志等级转成字符串
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 "UNKNOWN";
}
}
// 策略模式构建落盘方式
//基类
class LogStrategy
{
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(const string &message) = 0; // 不同模式的刷新方式
};
//落盘方式为显示器
class ConsoleLogStrategy : public LogStrategy
{
public:
virtual ~ConsoleLogStrategy()
{
}
//重写虚函数
void SyncLog(const string &message)
{
LockGuard lock(_mutex);
cout << message << endl;
}
private:
Mutex _mutex;
};
//落盘方式为文件
class FileLogStrategy : public LogStrategy
{
public:
~FileLogStrategy()
{
}
FileLogStrategy(const string &logpath = defaultpath, const string &logname = defaultname)
: _logpath(logpath), _logname(logname)
{
LockGuard lock(_mutex);
//应用C++17的接口完成文件操作
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 string &message)
{
LockGuard lock(_mutex);
string log = _logpath + _logname;
ofstream out(log.c_str(), ios::app); // 追加⽅式
if (!out.is_open())
return;
out << message << "\n";
out.close();
}
private:
Mutex _mutex;
const string &_logpath;
const string &_logname;
};
//日志类
class Logger
{
public:
Logger()
{
//默认落盘显示器
UseConsoleStrategy();
}
void UseConsoleStrategy()
{
_strategy = make_unique<ConsoleLogStrategy>();
}
void UseFileLogStrategy()
{
_strategy = make_unique<FileLogStrategy>();
}
//内部类:日志信息
class LogMessage
{
public:
LogMessage(LogLevel type, string filename, int line, Logger &logger) : _type(type),
_curr_time(GetCurTime()),
_pid(getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
//字符串流
std::stringstream ssbuffer;
ssbuffer << "[" << _curr_time << "] "
<< "[" << LogLevelToString(type) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "]"
<< " - ";
_loginfo = ssbuffer.str();
}
//重载<<,完成自定义信息输出部分
template <typename T>
LogMessage &operator<<(const T &info)
{
stringstream ssbuffer;
ssbuffer << info;
_loginfo += ssbuffer.str();
return *this;
}
//刷新日志
~LogMessage()
{
if (_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
private:
LogLevel _type; // 日志等级
std::string _curr_time; // 日志时间
pid_t _pid; // 写入日志的时间
std::string _filename; // 对应的文件名
int _line; // 对应的文件⾏号
Logger &_logger; // 引用外部logger类, ⽅便使用策略进行刷新
std::string _loginfo; // 一条合并完成的,完整的日志信息
};
//故意不带&,目的就是为了生成LogMessage对象,让它在析构时将日志信息刷新
LogMessage operator()(LogLevel type, string filename, int line)
{
return LogMessage(type, filename, line, *this);
}
~Logger()
{
}
private:
std::unique_ptr<LogStrategy> _strategy;//日志刷新的策略
};
Logger logger;//定义全局对象
// 使用宏,可以进行代码插⼊,⽅便随时获取文件名和行号
#define LOG(type) logger(type, __FILE__, __LINE__)
// 提供选择使⽤何种日志策略的方法
#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy()
#define ENABLE_FILE_LOG_STRATEGY() logger.UseFileStrategy()
}
//test.cc
#include <iostream>
#include "Log.hpp"
using namespace LogMudual;
int main()
{
ENABLE_CONSOLE_LOG_STRATEGY();
LOG(LogLevel::DEBUG) << "hello world";
LOG(LogLevel::DEBUG) << "hello world";
LOG(LogLevel::DEBUG) << "hello world";
ENABLE_FILE_LOG_STRATEGY();
LOG(LogLevel::DEBUG) << "hello world";
LOG(LogLevel::DEBUG) << "hello world";
LOG(LogLevel::WARNING) << "hello world";
return 0;
}