📝前言:
这篇文章我们来讲讲Linux——基于策略模式的简单日志设计
🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏
这里写目录标题
一,认识日志
的日志是记录系统和软件运行中发生事件的文件,主要作用是监控运行状态、记录异常信息,帮助快速定位问题并支持程序员进行问题修复。它是系统维护、故障排查和安全管理的重要工具。
日志格式中通常包括:时间戳、日志等级、日志内容
还可能包括:、文件名、行号、进程,线程相关id信息等
尽管复制已经有了大佬写好的现成的东西,但是本文还是采用设计模式- 略模式来进行一个简单日志的设计
格式要求:
[时间] [⽇志等级] [进程pid] [对应⽇志的⽂件名][⾏号] - 消息内容(⽀持可变参数)
二,日志设计
1. 总体概述
我们的日志的关键设计包括以下两点:
- 根据不同的策略,把日志内容输出到不同的“文件”
- 显示器文件
log.txt
日志文件
- 形成一条完整的日志内容
- 时间的获取
- 日志等级的设计
- 进程PID
- 日志文件名和行号
- 消息内容的“插入”(插入日志信息的string里),同时要支持可变参数的插入
<<
2. Mylog.hpp
我们主要设计以下几个类:
LogStrategy
策略模式基类,里面提供刷新方式SyncLog
的“标准”,需要子类继承并重新给刷新方法实现多态- 子类1
ScreenLogStrategy
:往显示器上刷新 - 子类2
FileLogStrategy
:往log
文件里面刷新
- 子类1
Log
日志主体,我们要实现的就是以后log << "日志内容"
就能写入日志- 内部类
LogMessage
,采用RAII的设计思想,通过生命周期来控制日志内容的“写入”(构造)和“刷新”(析构)
- 内部类
以下是具体的代码:
#pragma once
#include <sstream>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <memory>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include <filesystem> // C++17的文件库, 需要⾼版本编译器和-std=c++17
#define FILEPATH "./log/"
#define FILENAME "log.txt"
namespace tr
{
// 枚举类型,设置日志等级
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string Level2String(LogLevel loglevel)
{
switch (loglevel)
{
// C++11后枚举类有严格的作用域,这里要指明是LogLevel::
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(std::string &message) = 0;
};
// 策略 1: 刷新到屏幕上
class ScreenLogStrategy : public LogStrategy
{
public:
void SyncLog(std::string &message) override
{
_mutex.lock();
std::cerr << message << std::endl; // 打印到 cerr 上可以立即刷新
_mutex.unlock();
}
~ScreenLogStrategy()
{
std::cout << "~ScreenLogStrategy()" << '\n';
}
private:
std::mutex _mutex; // 用 C++ 的锁对象
};
// 策略 2: 刷新到日志文件中
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &logpath = "./", const std::string logname = "log.txt")
: _logpath(logpath),
_logname(logname)
{
if (std::filesystem::exists(_logpath))
return;
try
{
std::filesystem::create_directories(_logpath); // 如果抛异常抛的是:const std::exception 类型的
}
catch (const std::exception &e) // 用基类捕获所有异常
{
std::cerr << e.what() << '\n';
}
}
void SyncLog(std::string &message) override
{
_mutex.lock();
std::string logfile = _logpath + _logname;
std::ofstream outfile(logfile, std::ios_base::out | std::ios_base::app); // 文件不存在会创建
if (!outfile.is_open())
return;
outfile << message << '\n';
_mutex.unlock();
}
private:
std::string _logpath;
std::string _logname;
std::mutex _mutex;
};
// 日志主体
class Log
{
public:
Log()
{
UseScreenLogStrategy(); // 默认使用策略 1
}
~Log()
{
}
void UseScreenLogStrategy()
{
_logstrategy = std::make_unique<ScreenLogStrategy>();
}
void UseFileLogStrategy()
{
_logstrategy = std::make_unique<FileLogStrategy>();
}
// 日志信息(内置类)
// 为了后续实现 Mylog 的 operator() 重载的时候,返回临时变量
// 然后利用临时变量的每行生命周期来实现 logmessage 的刷新
class LogMessage
{
public:
LogMessage(LogLevel type, std::string &filename, int line, Log& loger)
: _level(type),
_pid(getpid()),
_filename(filename),
_time(GetTime()),
_line(line),
_loger(loger)
{
std::stringstream ss;
ss << "[" << _time << "]"
<< "[" << Level2String(_level) << "]"
<< "[" << _pid << "]"
<< "[" << _filename << "]"
<< "[" << _line << "]";
_loginfo = ss.str(); // 日志的左半部分
}
~LogMessage() // 生命周期结束,刷新日志
{
if(_loger._logstrategy)
_loger._logstrategy->SyncLog(_loginfo);
}
std::string GetTime()
{
time_t tm = time(nullptr); // 时间戳
struct tm curr;
localtime_r(&tm, &curr); // 传入时间戳,会输出一个 struct tm 里面记录着时间
char timebuffer[64]; // 用来保存格式化后的时间信息
snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",
curr.tm_year + 1900,
curr.tm_mon,
curr.tm_mday,
curr.tm_hour,
curr.tm_min,
curr.tm_sec);
return timebuffer;
}
// 重载流插入,为了让 Log 能支持<<
// 底层是将插入的日志的右半部分信息添加到 _loginfo
template <typename T>
LogMessage &operator<<(const T &info)
{
std::string ss = info;
_loginfo += ss; //
return *this; // 返回自己,实现多次 <<
}
private:
LogLevel _level;
pid_t _pid;
std::string _filename;
std::string _time;
int _line;
std::string _loginfo; // 整条日志信息
Log &_loger; // 外部类对象,用来调用刷新
};
// Log 的仿函数,特意返回临时变量
// 利用 RAII 的设计特点,创建一个LogMessage临时对象
// 在构造的时候,准备好左半部分, 在 << 的时候准备好 右半部分,最后在该行结束时,生命周期结束,刷新日志
LogMessage operator()(LogLevel level, std::string filename, int line)
{
return LogMessage(level, filename, line, *this);
}
private:
std::unique_ptr<LogStrategy> _logstrategy;
};
Log logger; // 定义全局对象
// 使⽤宏,可以进⾏代码插⼊,⽅便随时获取⽂件名和⾏号
#define LOG(type) logger(type, __FILE__, __LINE__) // __FILE__ 和 __LINE__ 可以自动获取文件名和行号
// 提供选择使⽤何种日志策略的⽅法
#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseScreenLogStrategy()
#define ENABLE_FILE_LOG_STRATEGY() logger.UseFileLogStrategy()
}
3. Main.cpp
测试代码
#include "Mylog.hpp"
using namespace tr;
int main()
{
// ENABLE_CONSOLE_LOG_STRATEGY();
ENABLE_FILE_LOG_STRATEGY();
LOG(LogLevel::DEBUG) << "hello world";
LOG(LogLevel::ERROR) << "hello world";
LOG(LogLevel::FATAL) << "hello world";
LOG(LogLevel::INFO) << "hello world";
LOG(LogLevel::INFO) << "hello world";
return 0;
}
ENABLE_CONSOLE_LOG_STRATEGY()
:选择往屏幕刷新的策略ENABLE_FILE_LOG_STRATEGY()
:选择往文件里面刷新
4. 运行效果
🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!