
◆ 博主名称: 小此方-CSDN博客 大家好,欢迎来到小此方的博客。
⭐️Linux系列个人专栏: 【主题曲】Linux
⭐️此方的GitHub: github_此方
⭐️ Re系列专栏:我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)
文章目录
- 概要&序論
- 一、什么是设计模式
-
- [1.2 23种设计模式(由Gemini生成资料)](#1.2 23种设计模式(由Gemini生成资料))
-
- [1.2.1 最常用的设计模式](#1.2.1 最常用的设计模式)
- [1.2.2 一般常用的设计模式](#1.2.2 一般常用的设计模式)
- [1.2.3 不常用的设计模式](#1.2.3 不常用的设计模式)
- 二、初步认识日志
-
- [2.1 日志的概念与作用](#2.1 日志的概念与作用)
- [2.2 日志的格式指标](#2.2 日志的格式指标)
- [2.3 日志的实现方案](#2.3 日志的实现方案)
- 三、日志的完整代码与解析
-
- 3.1Log.hpp
- 3.2Mutex.hpp
- [3.3 策略模式在Log.hpp中的体现](#3.3 策略模式在Log.hpp中的体现)
-
- [3.3.1 抽象策略角色:LogFlashStrategy](#3.3.1 抽象策略角色:LogFlashStrategy)
- [3.3.2 具体策略角色一:ConsoleLogStrategy](#3.3.2 具体策略角色一:ConsoleLogStrategy)
- [3.3.3 具体策略角色二:FileLogStrategy](#3.3.3 具体策略角色二:FileLogStrategy)
- [3.3.4 策略模式带来的设计优势](#3.3.4 策略模式带来的设计优势)
概要&序論
Hello大家好,我是此方。 本文介绍设计模式的基本概念及23种常见设计模式,并以日志系统为例,讲解日志的作用、格式与实现方案。在代码实践中,重点分析策略模式在日志组件中的应用,以及其带来的解耦与扩展优势。
一、什么是设计模式
在软件开发领域,随着行业规模的扩张,开发者之间的技术水平和经验存在差异。为了避免重复踩坑并提升代码质量,前辈们针对许多经典且高频出现的软件设计场景 ,总结提炼出了一套对应的标准解决方案,这就是设计模式。
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
cpp
#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
cpp
#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 是所有日志刷盘策略的基类(抽象接口),它规定了所有具体策略必须实现的统一刷新行为:
cpp
class LogFlashStrategy
{
public:
virtual ~LogFlashStrategy() = default;
virtual void SyncLog(const std::string& message) = 0;
};
- 虚析构函数:确保通过基类指针销毁派生类策略对象时,能够正确调用子类的析构函数,防止内存泄漏。
- 纯虚函数 SyncLog:定义了日志刷新的标准接口,接收格式化好的日志字符串,具体的刷盘逻辑延迟到子类中实现。
3.3.2 具体策略角色一:ConsoleLogStrategy
ConsoleLogStrategy 继承自 LogFlashStrategy,负责将日志同步刷新输出到终端控制台:
cpp
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,负责将日志持久化写入本地磁盘文件:
cpp
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)、核心服务宕机、主数据库断开。 | 服务彻底不可用或系统崩溃,必须触发紧急告警并人工抢修。 |
好的本期内容就到这里,如果对你有帮助,还不要忘记点赞三联支持。我是此方,我们下期再见。bye! Linux、C++、算法持续连载中,欢迎关注WeChat Official Account 【此方的技术栈】。