Linux加餐(一):藏在Linux中的设计模式(一)策略模式与日志


◆ 博主名称: 小此方-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 日志的实现方案

  在实际开发中,市面上存在许多现成的优秀日志库解决方案,例如 spdlogglogBoost.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 策略模式带来的设计优势

  1. 核心逻辑与刷盘细节解耦Logger 只需要持有 LogFlashStrategy 的基类指针,在需要刷盘时直接调用 SyncLog 即可,根本不需要关心底层究竟是打印到控制台还是写入到了磁盘。
  2. 极佳的扩展性(符合开闭原则 OCP) :如果后续需要新增其他的刷盘方式(例如:按日志等级拆分文件存储的 GroupLogStrategy ,或者通过网络发送日志的 NetLogStrategy ),只需要继承 LogFlashStrategy 并实现新的策略类即可,现有的控制台策略和文件策略代码完全无需变动。

业界最通用的五种核心日志等级如下:

日志等级 代表含义 典型应用场景 影响范围与处理建议
DEBUG 调试信息 详细的函数入参、中间变量值、执行路径等细节。 仅用于开发调试,生产环境默认关闭。
INFO 正常信息 系统启动/关闭、关键业务流程节点、成功交易通知。 记录系统正常运行轨迹,供日常运维与留痕查验。
WARN / WARNING 潜在警告 接口重试成功、配置参数缺省、资源利用率过高。 系统尚能继续运行,但存在安全隐患,需定期排查。
ERROR 局部错误 捕获到非预期异常、单次请求失败、数据库超时。 局部功能受损但未导致整体宕机,需尽快定位修复。
FATAL / CRITICAL 致命崩溃 内存溢出(OOM)、核心服务宕机、主数据库断开。 服务彻底不可用或系统崩溃,必须触发紧急告警并人工抢修。

好的本期内容就到这里,如果对你有帮助,还不要忘记点赞三联支持。我是此方,我们下期再见。bye! Linux、C++、算法持续连载中,欢迎关注WeChat Official Account 【此方的技术栈】。

相关推荐
邪修king1 小时前
Re:Linux系统篇(十):从零上手 Git + GitHub(Ubuntu 环境实操完整版|个人代码归档必备)
linux·git·github
wuminyu1 小时前
JDK21中FFM api的upcall回调机制解析
java·linux·c语言·jvm·c++
蜀道山老天师1 小时前
Zabbix监控MySQL与Redis应用实践完整指南
linux·运维·redis·mysql·zabbix
OsDepK2 小时前
在安卓Termux终端中部署openssh
linux·服务器
To_OC10 小时前
从一行入口代码啃透 NestJS:工厂模式、装饰器和依赖注入是怎么串起来的
后端·设计模式·nestjs
动力 continue10 小时前
Linux 基础篇 · 压缩打包与用户管理
linux·服务器·基础命令linux
NJCloud12 小时前
Ansible(三)——Zabbix 监控系统部署与敏感信息加密
linux·运维·chrome·信息可视化·ansible·zabbix
东方小月12 小时前
从零开发一个 Coding Agent(十二):实现版本化 JSONL 与真实 CLI 入口
前端·设计模式·前端框架
ltl12 小时前
Cgroups v2:为什么内存限制设了,宿主机数据库还是被 OOM
linux