[操作系统] 策略模式进行日志模块设计

news2025/5/15 8:58:13


文章目录

    • @[toc]
    • 一、什么是设计模式?
    • 二、日志系统的基本构成
    • 三、策略模式在日志系统中的落地实现
      • ✦ 1. 策略基类 LogStrategy
      • ✦ 2. 具体策略类
        • ▸ 控制台输出:ConsoleLogStrategy
        • ▸ 文件输出:FileLogStrategy
    • 四、日志等级枚举与转换函数
    • 五、日志时间戳格式化
    • 六、日志核心类 Logger 与内部类 LogMessage
      • ✦ 1. Logger 类
      • ✦ 2. LogMessage 类(Logger 内部类)
    • 七、使用宏简化调用
    • 八、完整使用示例
    • 九、总结

在当今IT行业中,程序开发已不仅仅是写代码,更重要的是“写好代码”。在系统开发中,日志系统是一个不可或缺的模块,承担着问题定位、性能分析、安全审计等重要职责。而借助于设计模式,我们可以构建一个更灵活、可拓展、维护性更强的日志系统。本篇博客将结合C++代码示例,深入讲解策略模式在日志系统中的应用


一、什么是设计模式?

在软件开发中,为了解决一些通用、重复出现的问题,业界总结出一套“最佳实践”方案,这就是设计模式。设计模式并非代码模板,而是对问题的抽象解决思路。

在本项目中,我们采用了策略模式(Strategy Pattern),它的核心思想是:定义一组算法,将每一个算法封装起来,并且使它们可以互换使用。也就是说,行为的变化不影响使用它的对象本身。


二、日志系统的基本构成

一个合格的日志系统通常需要具备以下信息:

  • 时间戳:记录事件发生的准确时间;
  • 日志等级:例如 DEBUG、INFO、WARNING、ERROR、FATAL;
  • 日志内容:需要打印的事件信息;
  • 元数据(可选):如源文件名、行号、线程ID、进程ID等,辅助定位问题。

我们希望实现的日志输出格式如下:

[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world

三、策略模式在日志系统中的落地实现

策略模式的精髓是将不同的行为封装为不同的策略类,而日志模块的“行为”就是日志的输出方式(比如输出到终端或写入文件)。

✦ 1. 策略基类 LogStrategy

这是所有具体策略类的基类,定义了统一接口:

class LogStrategy {
public:
    virtual void SyncLog(const std::string &message) = 0; // 刷新日志 ,写成纯虚函数,派生类必须强制实现该函数
    virtual ~LogStrategy() = default; // 析构函数:写成使用默认的析构函数
};

这是一个纯虚函数接口,用于实现不同的日志写入方式。


✦ 2. 具体策略类

▸ 控制台输出:ConsoleLogStrategy
// 显示器打印日志的策略 : 子类
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy()
{
}
void SyncLog(const std::string &message) override
{
    LockGuard lockguard(_mutex);
    std::cout << message << gsep;
}
~ConsoleLogStrategy()
{
}

private:
Mutex _mutex;
};

使用互斥锁保证线程安全,并将日志写入 std::cout

▸ 文件输出:FileLogStrategy
// 文件打印日志的策略 : 子类
const std::string defaultpath = "./log";
const std::string defaultfile = "my.log";
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
: _path(path),
_file(file)
{
    LockGuard lockguard(_mutex);
    if (std::filesystem::exists(_path))
    {
        return;
    }
    try
        {
            std::filesystem::create_directories(_path);
        }
    catch (const std::filesystem::filesystem_error &e)
        {
            std::cerr << e.what() << '\n';
        }
}
void SyncLog(const std::string &message) override
{
    LockGuard lockguard(_mutex);

    std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file; // "./log/" + "my.log"
    std::ofstream out(filename, std::ios::app);                              // app : 'append' 追加写入的 方式打开
    if (!out.is_open())
    {
        return;
    }
    out << message << gsep;
    out.close();
}
~FileLogStrategy()
{
}

private:
std::string _path; // 日志文件所在路径
std::string _file; // 日志文件本身
Mutex _mutex;
};
  • 构造函数会检查目录是否存在;
  • 使用 std::ofstream 追加写入日志;
  • 同样使用互斥锁保护写入过程。

四、日志等级枚举与转换函数

日志等级被定义为强类型枚举:

enum class LogLevel { DEBUG, INFO, WARNING, ERROR, FATAL };

转换函数 Level2Str(LogLevel) 用于将枚举转为字符串,便于日志输出格式化。

enum class 的枚举值是限定在其枚举类型本身的作用域内的。必须使用枚举类型名和 :: 操作符来访问枚举值,这避免了命名冲突。


五、日志时间戳格式化

时间的格式由 GetTimeStamp() 函数生成:

std::string GetTimeStamp() // 获取当前时间
{
    time_t curr = time(nullptr);
    struct tm curr_tm;
    localtime_r(&curr, &curr_tm);
    char timebuffer[128];
    snprintf(timebuffer, sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",
        curr_tm.tm_year+1900, // 操作系统默认的时间戳是 year - 1900,所以要+1900
        curr_tm.tm_mon+1, // 操作系统获取的月份是从0开始的0 ~ 11,所以要+1
        curr_tm.tm_mday,
        curr_tm.tm_hour,
        curr_tm.tm_min,
        curr_tm.tm_sec
        );
    return timebuffer;
}

使用 snprintf 以字符串形式格式化时间,保证可读性。


六、日志核心类 Logger 与内部类 LogMessage

Logger类是日志模块的核心:

✦ 1. Logger 类

  • 持有一个策略指针 _fflush_strategy,作为内部类·;
  • 提供 EnableConsoleLogStrategy()EnableFileLogStrategy() 来切换策略;
  • 使用重载函数 operator() 生成一个 LogMessage 临时对象:
LogMessage operator()(LogLevel level, std::string name, int line)

该对象负责构建一条完整的日志记录,构造完直接销毁。


✦ 2. LogMessage 类(Logger 内部类)

// 表示的是未来的一条日志
class LogMessage
{
public:
LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
    : _curr_time(GetTimeStamp()), // 获取当前时间
    _level(level), // 日志等级
    _pid(getpid()), // 进程号
    _src_name(src_name), // 源文件名
    _line_number(line_number), // 源文件行号
    _logger(logger) // 当前日志对象所属的logger
{
    /*
                std::stringstream如何使用:
                C++17 
                将格式转换成string,保存到创建的ss对象,写入stringstream流里面,将所有写入的自动转为string类型
                后面可以通过 .str()接口 存入普通的string对象中
                但是enum class不能直接转换成string 所以需要自定义转换函数 Level2Str(_level)
                */
    // 日志的左边部分,合并起来
    std::stringstream ss;
    ss << "[" << _curr_time << "] "
        << "[" << Level2Str(_level) << "] "
        << "[" << _pid << "] "
        << "[" << _src_name << "] "
        << "[" << _line_number << "] "
        << "- ";
    _loginfo = ss.str();
}
// LogMessage() << "hell world" << "XXXX" << 3.14 << 1234

// 构造函数用来打印日志头信息(结构化元数据),<< 重载函数用来构建日志具体内容
template <typename T>
LogMessage &operator<<(const T &info)
{
    // a = b = c =d;
    // 日志的右半部分,可变的  使用LogMessage & 自身的引用返回
    std::stringstream ss;
    ss << info;
    _loginfo += ss.str();
    return *this; // 返回当前对象,因为是引用,所以可以继续链式调用
}

~LogMessage()
{
    if (_logger._fflush_strategy)
    {
        /*内部类的作用:*/
        // 这就是为什么要写成内部类,因为内部类可以访问外部类的私有成员
        // 这样就可以使用logger对象中的fflush_strategy策略类,然后使用成员函数进行刷新在指定位置
        _logger._fflush_strategy->SyncLog(_loginfo);
    }
}

private:
std::string _curr_time; // 当前时间
LogLevel _level; // 日志等级    
pid_t _pid; // 进程号
std::string _src_name; // 源文件名
int _line_number; // 源文件行号
std::string _loginfo; //将以上内容合并之后,一条完整的信息

Logger &_logger; // 一个Logger对象,表示的是一个日志对象
};

这个内部类的职责是:

  • 构造函数收集日志元数据(时间、等级、文件名、行号、pid等);
  • 重载 operator<< 来拼接日志主体内容;
  • 析构函数中自动调用 SyncLog() 完成日志刷新:
~LogMessage()
{
    if (_logger._fflush_strategy)
    {
        /*内部类的作用:*/
        // 这就是为什么要写成内部类,因为内部类可以访问外部类的私有成员
        // 这样就可以使用logger对象中的fflush_strategy策略类,然后使用成员函数进行刷新在指定位置
        _logger._fflush_strategy->SyncLog(_loginfo);
    }
}

这是一种 RAII(资源获取即初始化)思想的应用,确保日志一定在对象生命周期结束时输出。


七、使用宏简化调用

定义了以下宏方便用户调用:

// 使用宏,简化用户操作,获取文件名和行号
#define LOG(level)                      logger(level, __FILE__, __LINE__) // __FILE__ __LINE__ 为预定义宏 在主函数文件中使用时会替换为主函文件的文件名和行号
#define Enable_Console_Log_Strategy()   logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy()      logger.EnableFileLogStrategy()

其中 __FILE____LINE__ 是C++预定义宏,会在宏展开时替换为当前源文件和行号。


八、完整使用示例

using namespace LogModule;

int main() {
    Enable_Console_Log_Strategy(); // 选择输出到控制台
    LOG(LogLevel::DEBUG) << "hello world";

    Enable_File_Log_Strategy();    // 切换输出到文件
    LOG(LogLevel::WARNING) << "log file output test";
    return 0;
}

输出示例:

[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world
[2024-08-04 12:27:03] [WARNING] [202938] [main.cc] [18] - log file output test

九、总结

通过这套自定义日志模块的实现,我们学习并实践了策略模式的核心思想:行为的封装与可替换性。这种设计不仅使日志系统具有更高的可拓展性(比如将来可以添加数据库日志策略、远程日志策略等),还体现了低耦合、高内聚的设计理念。

日志系统本身也利用了C++的许多优秀特性:

  • 内部类实现闭包式封装;
  • std::stringstream 实现类型安全拼接;
  • RAII 保证资源自动释放和操作完成;
  • 线程安全的日志输出策略。

这是一个非常经典且实用的C++日志系统练习案例,建议读者在理解基础上动手编码,实现自己的日志模块,加深对设计模式的掌握。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2375959.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MoonBit正式入驻GitCode!AI时代的编程语言新星,开启高性能开发新纪元

在AI与编程语言深度交融的今天&#xff0c;开发者们正见证一场技术生产力的革命。由IDEA研究院基础软件中心倾力打造的MoonBit&#xff08;月兔&#xff09;编程语言&#xff0c;自2023年横空出世以来&#xff0c;凭借高性能、低延迟、轻量化的特性&#xff0c;迅速成为全球开发…

关于vue学习的经常性错误

目录 常见问题&#xff1a; 1关于引用本地下载es6模块文件&#xff0c;报404错误 2 使用createApp函数后没有调用mount函数挂载到浏览器 3 在mount函数中&#xff0c;忘记引用插值表达式所在标签的定位符如 标签选择器&#xff0c;类选择器等 4在直接使用Vue3函数时&#…

AtCoder Beginner Contest 403

再来一场atCoder&#xff0c;这一场简直血虐&#xff0c;让你回忆起了审题的重要性 A - Odd Position Sum 思路&#xff1a;题意很简单&#xff0c;求一个数组奇数位上数字和。很简单的问题&#xff0c;但你如果不仔细审题&#xff0c;就会浪费大量的时间 /* Author Owen_Q…

关于 Golang GC 机制的一些细节:什么是根对象?GC 机制的触发时机?

文章目录 关于 Golang GC 机制的一些细节&#xff1a;什么是根对象&#xff1f;GC 机制的触发时机&#xff1f;简要回顾 Golang GC 三色标记法的工作流程什么是根对象&#xff1f;GC 的触发时机&#xff1f; 关于 Golang GC 机制的一些细节&#xff1a;什么是根对象&#xff1f…

Python笔记:c++内嵌python,c++主窗口如何传递给脚本中的QDialog,使用的是pybind11

1. 问题描述 用的是python 3.8.20, qt版本使用的是5.15.2, PySide的版本是5.15.2, pybind11的版本为2.13.6 网上说在python脚本中直接用PySide2自带的QWinWidget&#xff0c;如from PySide2.QtWinExtras import QWinWidget&#xff0c;但我用的版本中说没有QWinWidget&#x…

C++效率掌握之STL库:map set底层剖析及迭代器万字详解

文章目录 1.map、set的基本结构2.map、set模拟实现2.1 初步定义2.2 仿函数实现2.3 Find功能实现2.4 迭代器初步功能实现2.4.1 运算符重载2.4.2 --运算符重载2.4.3 *运算符重载2.4.4 ->运算符重载2.4.5 !运算符重载2.4.6 begin()2.4.7 end() 2.5 迭代器进阶功能实现2.5.1 set…

新三消示例项目《Gem Hunter》中的光照和视觉效果

《Gem Hunter》是 Unity 的全新官方示例项目&#xff0c;展示了如何在 Unity 2022 LTS 使用通用渲染管线 (URP) 打造抢眼的光效和视效&#xff0c;让 2D 益智/三消游戏在竞争中脱颖而出。 下载示例项目及其说明文档。准备潜入清澈湛蓝的海水中探寻财富吧&#xff0c;因为那里到…

单向循环链表C语言实现实现(全)

#include<stdio.h> #include<stdlib.h> #define TRUE 1 #define FASLE 0//定义宏标识判断是否成功 typedef struct Node {int data;struct Node* next; }Node;Node* InitList() {Node* list (Node*)malloc(sizeof(Node));list->data 0;//创建节点保存datalist…

【AI大模型】赋能【传统业务】

在数字化转型的浪潮下&#xff0c;传统业务流程&#xff08;如通知公告管理、文档处理等&#xff09;仍依赖人工操作&#xff0c;面临效率低、成本高、易出错等问题。以企业通知公告为例&#xff0c;从内容撰写、摘要提炼到信息分发&#xff0c;需耗费大量人力与时间&#xff0…

团结引擎开源车模 Sample 发布:光照渲染优化 动态交互全面体验升级

光照、材质与交互效果的精细控制&#xff0c;通常意味着复杂的技术挑战&#xff0c;但借助 Shader Graph 14.1.0(已内置在团结引擎官方 1.5.0 版本中)&#xff0c;这一切都变得简单易用。通过最新团结引擎官方车模 Sample&#xff0c;开发者能切身感受到全新光照优化与编辑功能…

精准测量“双雄会”:品致与麦科信光隔离探头谁更胜一筹

在电子技术飞速发展的当下&#xff0c;每一次精准测量都如同为科技大厦添砖加瓦。光隔离探头作为测量领域的关键角色&#xff0c;能有效隔绝电气干扰&#xff0c;保障测量安全与精准。在众多品牌中&#xff0c;PINTECH品致与麦科信的光隔离探头脱颖而出&#xff0c;成为工程师们…

NSSCTF [HNCTF 2022 WEEK4]

题解前的吐槽&#xff1a;紧拖慢拖还是在前段时间开始学了堆的UAF(虽然栈还没学明白&#xff0c;都好难[擦汗])&#xff0c;一直觉得学的懵懵懂懂&#xff0c;不太敢发题解&#xff0c;这题算是入堆题后一段时间的学习成果&#xff0c;有什么问题各位师傅可以提出来&#xff0c…

tornado_登录页面(案例)

目录 1.基础知识​编辑 2.脚手架&#xff08;模版&#xff09; 3.登录流程图&#xff08;processon&#xff09; 4.登录表单 4.1后&#xff08;返回值&#xff09;任何值&#xff1a;username/password &#xff08;4.1.1&#xff09;app.py &#xff08;4.1.2&#xff…

YOLOv12模型部署(保姆级)

一、下载YOLOv12源码 1.通过网盘分享的文件&#xff1a;YOLOv12 链接: https://pan.baidu.com/s/12-DEbWx1Gu7dC-ehIIaKtQ 提取码: sgqy &#xff08;网盘下载&#xff09; 2.进入github克隆YOLOv12源码包 二、安装Anaconda/pycharm 点击获取官网链接(anaconda) 点击获取…

BGP实验练习1

需求&#xff1a; 要求五台路由器的环回地址均可以相互访问 需求分析&#xff1a; 1.图中存在五个路由器 AR1、AR2、AR3、AR4、AR5&#xff0c;分属不同自治系统&#xff08;AS&#xff09;&#xff0c;AR1 在 AS 100&#xff0c;AR2 - AR4 在 AS 200&#xff0c;AR5 在 AS …

HTML、CSS 和 JavaScript 基础知识点

HTML、CSS 和 JavaScript 基础知识点 一、HTML 基础 1. HTML 文档结构 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.…

数据结构与算法分析实验12 实现二叉查找树

实现二叉查找树 1、二叉查找树介绍2.上机要求3.上机环境4.程序清单(写明运行结果及结果分析)4.1 程序清单4.1.1 头文件 TreeMap.h 内容如下&#xff1a;4.1.2 实现文件 TreeMap.cpp 文件内容如下&#xff1a;4.1.3 源文件 main.cpp 文件内容如下&#xff1a; 4.2 实现展效果示5…

使用 Semantic Kernel 调用 Qwen-VL 多模态模型

使用 Semantic Kernel 调用 Qwen-VL 多模态模型 一、引言 随着人工智能技术的不断发展&#xff0c;多模态模型逐渐成为研究的热点。Qwen-VL 是阿里云推出的大规模视觉语言模型&#xff0c;支持图像、文本等多种输入形式&#xff0c;并能够进行图像描述、视觉问答等多种任务。…

(4)python开发经验

文章目录 1 使用ctypes库调用2 使用pybind11 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;Qt开发 &#x1f448;&#x1f449;python开发 &#x1f448; 1 使用ctypes库调用 说明&#xff1a;ctypes是一个Python内置的库&#xff0c;可以提供C兼容的数据类型…

深度剖析 GpuGeek 实例:GpuGeek/Qwen3-32B 模型 API 调用实践与性能测试洞察

深度剖析 GpuGeek 实例&#xff1a;GpuGeek/Qwen3-32B 模型 API 调用实践与性能测试洞察 前言 GpuGeek专注于人工智能与高性能计算领域的云计算平台&#xff0c;致力于为开发者、科研机构及企业提供灵活、高效、低成本的GPU算力资源。平台通过整合全球分布式数据中心资源&#…