一、什么是设计模式
在软件开发 领域,随着行业规模的扩张,开发者之间的技术水平和经验存在差异。为了避免重复踩坑并提升代码质量,前辈们针对许多经典且高频出现的软件设计场景,总结提炼出了一套对应的标准解决方案,这就是设计模式。
1.2 23种设计模式(由Gemini生成资料)
经典的 GoF(Gang of Four)设计模式共有 23 种,划分为创建型、结构型和行为型三大类 。结合实际工业界开发频次,划分如下:
1.2.1 最常用的设计模式
单例模式(Singleton):确保一个类只有一个实例,并提供全局访问点。
工厂方法模式(Factory Method):定义创建对象的接口,让子类决定实例化哪一个类。
抽象工厂模式(Abstract Factory):提供创建一系列相关或相互依赖对象的接口,无需指定具体类。
策略模式(Strategy):定义算法族分别封装,使它们可以互相替换(本篇重点)。
观察者模式(Observer):定义对象间一对多的依赖关系,状态改变时自动通知所有依赖者。
适配器模式(Adapter):将一个类的接口转换成客户希望的另一个接口。
装饰器模式(Decorator):动态地给一个对象添加额外职责,比继承更灵活。
1.2.2 一般常用的设计模式
建造者模式(Builder):将复杂对象的构建与表示分离,使同样的构建过程可创建不同的表示。
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。
外观模式(Facade):为子系统中的一组接口提供一个一致的高层界面。
模板方法模式(Template Method):定义算法骨架,将部分步骤延迟到子类实现。
组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。
命令模式(Command):将请求封装为对象,实现请求调用者与接收者的解耦。
状态模式(State):允许对象在内部状态改变时改变其行为。
1.2.3 不常用的设计模式
原型模式(Prototype):通过拷贝现有的原型对象来创建新对象。
桥接模式(Bridge):将抽象部分与实现部分分离,使它们都可以独立变化。
享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象。
责任链模式(Chain of Responsibility):将请求沿着处理链传递,直到有对象处理它为止。
迭代器模式(Iterator):顺序访问聚合对象中的元素(现代编程语言大多已在标准库层面内置实现)。
中介者模式(Mediator):用一个中介对象来封装一系列对象的交互,降低耦合度。
备忘录模式(Memento):在不破坏封装性的前提下,捕获并保存对象的内部状态用于恢复。
访问者模式(Visitor):在不改变元素类的前提下,定义作用于这些元素的新操作。
解释器模式(Interpreter):给定一个语言,定义它的文法表示及对应的解释器。
二、初步认识日志
2.1 日志的概念与作用
计算机 中的日志是记录系统和软件运行过程中发生事件的文件。它是系统维护、故障排查和安全管理的重要工具,主要作用包括:
监控运行状态:实时了解程序的执行流程与健康状况。
记录异常信息:在系统出现错误时精准捕获现场数据。
定位与修复问题:帮助程序员快速查找 Bug 根源并进行针对性修复。
2.2 日志的格式指标
设计一个标准的日志系统,需要明确其输出格式。通常包含以下指标:
核心必选指标:
时间戳:记录日志产生的具体时间。
日志等级:标注日志的严重程度(如 DEBUG、INFO、WARNING、ERROR、FATAL)。
日志内容:具体的文本描述或数据细节。
扩展可选指标:
文件名与行号:精准定位到产生日志的源码位置。
进程与线程 ID:在多线程或并发环境下区分不同执行流。
2.3 日志的实现方案
在实际开发中,市面上存在许多现成的优秀日志库解决方案,例如 spdlog、glog、Boost.Log 以及 Log4cxx 等。
为了深入理解日志系统的设计思想,本项目 将采用自定义日志的方式进行实现,并引入设计模式中的策略模式来灵活处理日志的输出行为。
三、日志的完整代码与解析
手写代码,如有错误还请指出。
3.1Log.hpp
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include "Mutex.hpp"
#include <filesystem>
#include <iostream>
#include <ctime>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <fstream>
#include <sstream>
#include <memory>
#include <cstdio>
namespace LogModule
{
const std::string CAGE = "\n";
class LogFlashStrategy
{
public:
~LogFlashStrategy() = default ;
virtual void SyncLog(const std::string& message) = 0;
};
class ConsoleLogStrategy : public LogFlashStrategy
{
public:
void SyncLog(const std::string& logmessage) override
{
MyMutex::GuardMutex _guardmutex (_mutex);
std::cout<<logmessage<<CAGE ;
}
private:
MyMutex:: Mutex _mutex ;
};
const std::string default_filename = "log.txt" ;
const std::string default_filepath = "./log" ;//./log/log.txt
class FileLogStrategy : public LogFlashStrategy
{
public:
FileLogStrategy(const std::string file = default_filename ,
const std::string path = default_filepath )
:_filename(file),
_filepath(path)
{
MyMutex::GuardMutex _guardmutex(_mutex);
if(std::filesystem :: exists(_filepath))
{
return ;
}
try
{
std::filesystem:: create_directories(_filepath);
}
catch(const std::filesystem::filesystem_error& _exception)
{
std::cout<<_exception.what()<<CAGE ;
}
}
void SyncLog(const std::string& logmessage)
{
MyMutex::GuardMutex _guardmutex(_mutex);
std::string pathfile = _filepath +
(_filepath.back() == '/' ? "" : "/") + _filename ;
std :: ofstream out (pathfile , std::ios::app);
if(!out.is_open())
{
return ;
}
out<<logmessage<<CAGE;
out.close();
}
private:
MyMutex::Mutex _mutex ;
std::string _filename ;
std::string _filepath ;
};
enum class LEVEL
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string GetCurrentTime()
{
time_t cur_time = time(nullptr);
struct tm result ;
localtime_r(&cur_time ,&result );
// std:: string current_time ;
// current_time += "[" +
// std::to_string(result.tm_year+1900) +
// std::to_string(result.tm_mon+1) +
// std::to_string(result.tm_mday) +
// std::to_string(result.tm_hour) +
// std::to_string(result.tm_min) +
// std::to_string(result.tm_sec) +
// "]";
char timebuffer[128];
snprintf(timebuffer , sizeof(timebuffer) , "%4d-%02d-%02d %02d:%02d:%02d",
result.tm_year + 1900,
result.tm_mon + 1,
result.tm_mday,
result.tm_hour,
result.tm_min,
result.tm_sec
);
return timebuffer ;
}
std::string LevelToString(LEVEL level)
{
switch (level)
{
case LEVEL::DEBUG:
return "DEBUG" ;
case LEVEL::INFO :
return "INFO" ;
case LEVEL::WARNING :
return "WARNING" ;
case LEVEL::ERROR :
return "ERROR" ;
case LEVEL::FATAL :
return "FATAL";
default :
return "UNKNOWORNING";
}
}
class Logger
{
public:
Logger()
{
EnableConsolLogStrategy();
}
void EnableConsolLogStrategy()
{
_current_flash_tsrategy = std::make_unique<ConsoleLogStrategy>();
}
void EnableFileLogStrategy()
{
_current_flash_tsrategy = std::make_unique<FileLogStrategy>();
}
class LogMessage
{
public:
LogMessage(LEVEL& level , std::string& file , size_t line , LogModule::Logger& logger)
:_current_time(GetCurrentTime()),
_level(level),
_pid(getpid()),
_src_file(file),
_line(line),
_logger(logger)
{
// std::string ret = "";
// ret += "[" + _current_time + "]";
// ret += "[" + LevelToString(_level) + "]";
// ret += "[" + std::to_string(_pid) + "]";
// ret += "[" + _src_file + "]" ;
// ret += "[" + std::to_string(_line) + "]" ;
std::stringstream ret ;
ret<<"["<<_current_time<<"]"
<<"["<<LevelToString(_level)<<"]"
<<"["<<_pid<<"]"
<<"["<<_src_file<<"]"
<<"["<<_line<<"] -";
_hole_message = ret.str() ;
}
template<class T>
LogMessage& operator<< (const T& info)
{
std::stringstream ss ;
ss <<info ;
_hole_message += ss.str() ;
return *this ;
}
~LogMessage()
{
if(_logger._current_flash_tsrategy)
_logger._current_flash_tsrategy->SyncLog(_hole_message);
}
private:
std::string _current_time ;
LEVEL _level ;
pid_t _pid ;
std::string _src_file ;
size_t _line ;
std::string _hole_message ;
LogModule::Logger& _logger ;
};
LogMessage operator()(LEVEL level , std::string filename , size_t line )
{
return LogMessage(level , filename ,line,*this);
}
private:
std::unique_ptr<LogFlashStrategy> _current_flash_tsrategy ;
};
Logger logger ;
#define LOG(level) logger(level , __FILE__ , __LINE__)
#define ENABLE_CONSOLE_LOG_STRATEGY() logger.EnableConsolLogStrategy()
#define ENABLE_FILE_LOG_STRATEGY() logger.EnableFileLogStrategy()
}
#endif
3.2Mutex.hpp
#ifndef __MUTEX_HPP__
#define __MUTEX_HPP__
#include <pthread.h>
namespace MyMutex
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_mutex,nullptr);
}
void Lock(){ pthread_mutex_lock(&_mutex);}
void UnLock(){ pthread_mutex_unlock(&_mutex);}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
private:
pthread_mutex_t _mutex ;
};
class GuardMutex
{
public:
GuardMutex(Mutex mutex)
:_mutex(mutex)
{
_mutex.Lock();
}
~GuardMutex()
{
_mutex.UnLock();
}
private:
Mutex _mutex ;
};
}
#endif
3.3 策略模式在Log.hpp中的体现
在日志模块的实际落地中,日志消息的生成与格式化和日志消息的刷新去向(如打印到终端、写入磁盘文件、发送至网络等)属于两个独立的关注点。通过策略模式,我们将“日志刷新”行为抽象为统一的策略接口,从而实现了日志系统对扩展开放、对修改封闭的核心设计原则。
3.3.1 抽象策略角色:LogFlashStrategy
LogFlashStrategy 是所有日志刷盘策略的基类(抽象接口),它规定了所有具体策略必须实现的统一刷新行为:
class LogFlashStrategy
{
public:
virtual ~LogFlashStrategy() = default;
virtual void SyncLog(const std::string& message) = 0;
};
虚析构函数:确保通过基类指针销毁派生类策略对象时,能够正确调用子类的析构函数,防止内存泄漏。
纯虚函数 SyncLog:定义了日志刷新的标准接口,接收格式化好的日志字符串,具体的刷盘逻辑延迟到子类中实现。
3.3.2 具体策略角色一:ConsoleLogStrategy
ConsoleLogStrategy 继承自 LogFlashStrategy,负责将日志同步刷新输出到终端控制台:
class ConsoleLogStrategy : public LogFlashStrategy
{
public:
void SyncLog(const std::string& logmessage) override
{
MyMutex::GuardMutex _guardmutex(_mutex);
std::cout << logmessage << CAGE;
}
private:
MyMutex::Mutex _mutex;
};
线程安全保证:多线程并发调用日志打印时,控制台输出很容易发生字符穿插混乱。这里利用 RAII 机制的锁 GuardMutex 对输出过程进行加锁保护,保证每一条日志都能完整地打印到屏幕上。
3.3.3 具体策略角色二:FileLogStrategy
FileLogStrategy 同样继承自 LogFlashStrategy,负责将日志持久化写入本地磁盘文件:
const std::string default_filename = "log.txt";
const std::string default_filepath = "./log"; // ./log/log.txt
class FileLogStrategy : public LogFlashStrategy
{
public:
FileLogStrategy(const std::string file = default_filename,
const std::string path = default_filepath)
: _filename(file), _filepath(path)
{
MyMutex::GuardMutex _guardmutex(_mutex);
if (std::filesystem::exists(_filepath))
{
return;
}
try
{
std::filesystem::create_directories(_filepath);
}
catch (const std::filesystem::filesystem_error& _exception)
{
std::cout << _exception.what() << CAGE;
}
}
void SyncLog(const std::string& logmessage) override
{
MyMutex::GuardMutex _guardmutex(_mutex);
std::string pathfile = _filepath +
(_filepath.back() == '/' ? "" : "/") + _filename;
std::ofstream out(pathfile, std::ios::app);
if (!out.is_open())
{
return;
}
out << logmessage << CAGE;
out.close();
}
private:
MyMutex::Mutex _mutex;
std::string _filename;
std::string _filepath;
};
目录自动建创:构造函数中利用 C++17 的 std::filesystem::create_directories 自动检测并创建日志存储路径,增强了代码的健壮性。
追加写入模式:在 SyncLog 中使用 std::ios::app(追加模式)打开文件,确保新日志顺次追加到文件末尾,不会覆盖已有历史记录。
并发文件保护:同样在写入文件前后加上互斥锁保护,防止多线程同时写入同一文件导致的数据竞争和内容损坏。
3.3.4 策略模式带来的设计优势
核心逻辑与刷盘细节解耦:Logger 只需要持有 LogFlashStrategy 的基类指针,在需要刷盘时直接调用 SyncLog 即可,根本不需要关心底层究竟是打印到控制台还是写入到了磁盘。
极佳的扩展性(符合开闭原则 OCP):如果后续需要新增其他的刷盘方式(例如:按日志等级拆分文件存储的 GroupLogStrategy,或者通过网络发送日志的 NetLogStrategy),只需要继承 LogFlashStrategy 并实现新的策略类即可,现有的控制台策略和文件策略代码完全无需变动。
业界最通用的五种核心日志等级如下:
日志等级 代表含义 典型应用场景 影响范围与处理建议
DEBUG 调试信息 详细的函数入参、中间变量值、执行路径等细节。 仅用于开发调试,生产环境默认关闭。
INFO 正常信息 系统启动/关闭、关键业务流程节点、成功交易通知。 记录系统正常运行轨迹,供日常运维与留痕查验。
WARN / WARNING 潜在警告 接口重试成功、配置参数缺省、资源利用率过高。 系统尚能继续运行,但存在安全隐患,需定期排查。
ERROR 局部错误 捕获到非预期异常、单次请求失败、数据库超时。 局部功能受损但未导致整体宕机,需尽快定位修复。
FATAL / CRITICAL 致命崩溃 内存溢出(OOM)、核心服务宕机、主数据库断开。 服务彻底不可用或系统崩溃,必须触发紧急告警并人工抢修。
————————————————
版权声明:本文为CSDN博主「小此方」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Z2314246476/article/details/163572793