[操作系统] 策略模式进行日志模块设计
文章目录
- @[toc]
- 一、什么是设计模式?
- 二、日志系统的基本构成
- 三、策略模式在日志系统中的落地实现
- ✦ 1. 策略基类 LogStrategy
- ✦ 2. 具体策略类
- ▸ 控制台输出:ConsoleLogStrategy
- ▸ 文件输出:FileLogStrategy
- 四、日志等级枚举与转换函数
- 五、日志时间戳格式化
- 六、日志核心类 Logger 与内部类 LogMessage
- ✦ 1. Logger 类
- ✦ 2. LogMessage 类(Logger 内部类)
- 七、使用宏简化调用
- 八、完整使用示例
- 九、总结
文章目录
- @[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,所以要+1900curr_tm.tm_mon+1, // 操作系统获取的月份是从0开始的0 ~ 11,所以要+1curr_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++日志系统练习案例,建议读者在理解基础上动手编码,实现自己的日志模块,加深对设计模式的掌握。
相关文章:

[操作系统] 策略模式进行日志模块设计
文章目录 [toc]一、什么是设计模式?二、日志系统的基本构成三、策略模式在日志系统中的落地实现✦ 1. 策略基类 LogStrategy✦ 2. 具体策略类▸ 控制台输出:ConsoleLogStrategy▸ 文件输出:FileLogStrategy 四、日志等级枚举与转换函数五、日…...
掌握Docker Commit:轻松创建自定义镜像
使用 docker commit 命令可以通过对现有容器进行修改来创建新的镜像。-a 选项用于指定作者信息,-m 选项用于添加提交信息。以下是具体步骤: 启动并修改容器 启动一个容器并进行必要的修改。例如,启动一个 Ubuntu 容器并安装一些软件包&…...

MoonBit正式入驻GitCode!AI时代的编程语言新星,开启高性能开发新纪元
在AI与编程语言深度交融的今天,开发者们正见证一场技术生产力的革命。由IDEA研究院基础软件中心倾力打造的MoonBit(月兔)编程语言,自2023年横空出世以来,凭借高性能、低延迟、轻量化的特性,迅速成为全球开发…...
命令行快速上传文件到SFTP服务器(附参考示例)
一、SFTP基础命令格式 更新参数后的标准命令格式为: sftp -P [端口号] [用户名][服务器IP]:[远程路径] <<< $put [本地文件路径]二、新参数实例解析 使用新连接参数的完整命令示例: sftp -P 30033 test_jigou_sftp121.199.64.216:/download…...

关于vue学习的经常性错误
目录 常见问题: 1关于引用本地下载es6模块文件,报404错误 2 使用createApp函数后没有调用mount函数挂载到浏览器 3 在mount函数中,忘记引用插值表达式所在标签的定位符如 标签选择器,类选择器等 4在直接使用Vue3函数时&#…...
数据结构与算法-双向链表专题
目录 一. 双向链表的结构 二.双向链表的使用 2.1 创建节点 2.2 初始化 2.3 打印 2.4 尾插 2.5 头插 2.6 尾删 2.7 头删 2.8 在指定位置pos之后插入数据 2.9 查找数据 2.10 删除pos位置的节点 2.11 销毁链表 一. 双向链表的结构 在List.h的头文件中对链表的结构进行创建 #prag…...
AtCoder Beginner Contest 403
再来一场atCoder,这一场简直血虐,让你回忆起了审题的重要性 A - Odd Position Sum 思路:题意很简单,求一个数组奇数位上数字和。很简单的问题,但你如果不仔细审题,就会浪费大量的时间 /* Author Owen_Q…...

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

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,如from PySide2.QtWinExtras import QWinWidget,但我用的版本中说没有QWinWidget&#x…...
在Ubuntu24.04中配置开源直线特征提取软件DeepLSD
在Ubuntu24.04中配置开源直线特征提取软件DeepLSD 本文提供在Ubuntu24.04中配置开源直线特征提取软件DeepLSD的基础环境配置、列出需要修改的文件内容,以及报错解决方案集锦。 基础的编译安装环境 python3.8.12CUDA12gcc/g 9.5(系统自带的g-13版本太新…...

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 的全新官方示例项目,展示了如何在 Unity 2022 LTS 使用通用渲染管线 (URP) 打造抢眼的光效和视效,让 2D 益智/三消游戏在竞争中脱颖而出。 下载示例项目及其说明文档。准备潜入清澈湛蓝的海水中探寻财富吧,因为那里到…...
通用软件项目技术报告 - 导读III
现在,我们正式进入报告的第六个主要领域:6. 领域六:与第三方服务/API 集成 (含 LLM API)。 连接: 在现代软件开发中,很少有应用程序是完全孤立的。我们经常需要与各种外部的第三方服务或 API 进行集成,以利用它们提供的特定功能(如支付处理、地图服务、社交媒体登录、云…...
代码随想录训练营第二十三天| 572.另一颗树的子树 104.二叉树的最大深度 559.N叉树的最大深度 111.二叉树的最小深度
572.另一颗树的子树: 状态:已做出 思路: 这道题目当时第一时间不是想到利用100.相同的树思路来解决,而是先想到了使用kmp,不过这个题目官方题解确实是有kmp解法的,我使用的暴力解法,kmp的大致思…...

单向循环链表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大模型】赋能【传统业务】
在数字化转型的浪潮下,传统业务流程(如通知公告管理、文档处理等)仍依赖人工操作,面临效率低、成本高、易出错等问题。以企业通知公告为例,从内容撰写、摘要提炼到信息分发,需耗费大量人力与时间࿰…...
Clion内置宏$PROJECT_DIR$等
CLion 内置宏 文章目录 CLion 内置宏通用路径相关宏路径相对化宏 官方文档地址: https://www.jetbrains.com/help/clion/built-in-macros.html 通用路径相关宏 宏名称含义说明示例$WORKSPACE_DIR$当前项目所属的工作区根目录路径。/home/user/workspace$PROJECT_D…...

团结引擎开源车模 Sample 发布:光照渲染优化 动态交互全面体验升级
光照、材质与交互效果的精细控制,通常意味着复杂的技术挑战,但借助 Shader Graph 14.1.0(已内置在团结引擎官方 1.5.0 版本中),这一切都变得简单易用。通过最新团结引擎官方车模 Sample,开发者能切身感受到全新光照优化与编辑功能…...
hghac8008漏洞扫描处理
文章目录 环境文档用途详细信息相关文档 环境 系统平台:Linux x86-64 Red Hat Enterprise Linux 7 版本:4.5.10 文档用途 本文只要用于在客户提出hghac8008端口漏洞时,如何进行漏洞处理,本文章的方法已经应用于浪潮云ÿ…...
PyQt5教程:QComboBox下拉列表框的全面解析与实战应用
QComboBox概述 QComboBox是PyQt5中一个集按钮和下拉选项于一体的控件,通常被称为下拉列表框或组合框。它允许用户从预定义的选项列表中选择一个值,是GUI开发中最常用的输入控件之一。 主要特点: 紧凑的界面设计,节省屏幕空间提…...
GAN简读
Abstract 我们提出了一个通过同时训练两个模型的对抗过程来评估生成模型的新框架:一个生成模型 G G G用来捕捉数据特征,还有一个用于估计这个样本是来自训练样本还是 G G G的概率的判别模型 D D D, G G G的训练过程是最大化 D D D犯错的概率。这个框架就相当于一个minimax tw…...

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

NSSCTF [HNCTF 2022 WEEK4]
题解前的吐槽:紧拖慢拖还是在前段时间开始学了堆的UAF(虽然栈还没学明白,都好难[擦汗]),一直觉得学的懵懵懂懂,不太敢发题解,这题算是入堆题后一段时间的学习成果,有什么问题各位师傅可以提出来,…...
Step1
项目 SchedulerSim 已搭建完成 ✅ ⸻ ✅ 你现在拥有的: • 🔧 两种调度器(Round Robin SJF) • 📦 模拟进程类 Process • 🧱 清晰结构:OOP 风格 便于扩展 • ✍️ 主函数已演示调度器运行效…...

tornado_登录页面(案例)
目录 1.基础知识编辑 2.脚手架(模版) 3.登录流程图(processon) 4.登录表单 4.1后(返回值)任何值:username/password (4.1.1)app.py (4.1.2ÿ…...

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

BGP实验练习1
需求: 要求五台路由器的环回地址均可以相互访问 需求分析: 1.图中存在五个路由器 AR1、AR2、AR3、AR4、AR5,分属不同自治系统(AS),AR1 在 AS 100,AR2 - AR4 在 AS 200,AR5 在 AS …...
Three.js知识框架
一、Three.js 基础概念 1. Three.js 简介 是什么? 基于 WebGL 的 3D JavaScript 库,用于在浏览器中渲染 3D 场景。 核心优势 简化 WebGL 的复杂 API,提供高层封装。 跨平台(支持桌面和移动端)。 适用场景 3D 可视…...
AWS技术助力企业满足GDPR合规要求
GDPR(通用数据保护条例)作为欧盟严格的数据保护法规,给许多企业带来了合规挑战。本文将探讨如何利用AWS(亚马逊云服务)的相关技术来满足GDPR的核心要求,帮助企业实现数据保护合规。 一、GDPR核心要求概览 GDPR的主要目标是保护欧盟公民的个人数据和隐私权。其核心要求包括: 数…...

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.…...