高性能日志系统 日志输出模块逻辑
概述
该模块主要实现了一个日志系统的输出模块,通过多态、工厂模式等设计模式,构建灵活的日志输出架构。
- 功能:格式化完成的标准日志消息,输出到指定为止
- 拓展:支持同时将日志落地到不同的位置,也就是输出日志到不同位置
- 输出位置
- 标准输出:输出到屏幕上,主要用于测试日志是否正常运行
- 指定文件:将日志信息输出到指定文件中,便于事后进行日志分析
- 滚动文件:文件按照大小或者时间输出到指定文件中,方便按日期或者大小删除日志消息
- 实现思想
- 首先抽象出落地模块类
- 根据不同落地方向,从基类派生出来即可
- 其次工厂模式,创建与表示分离
架构设计
整体架构分析
- 主框架定义了一个日志输出系统,包含多种日志输出方式,例如标准输出、文件输出、滚动文件输出
- 模块代码通过基类 LogSink定义了一个统一的接口,并且通过派生类 StdoutSink 、 FileSink 、RollSink实现具体的日志输出方式
- SinkFactory工厂类则是通过模版方法提供创建不同类型日志输出对象的功能
- 保证单一职责原则,一个类派生类只负责一种日志输出方式
具体实现
LogSink基类
- 定义所有日志输出的基类,参数分别接收日志的内容以及长度
class LogSink {
public:using ptr = std::shared_ptr<LogSink>;LogSink() {}virtual ~LogSink() {}virtual void log(const char *data, size_t len) = 0;
};
StdoutSink
- 作用:实现将日志输出到显示器(标准输出)的功能
- 实现逻辑: 使用std::cout::write标准库函数,输出一段指定长度的字符数据,一般用于二进制数据或者特定格式的字符串
class StdoutSink : public LogSink {
public:using ptr = std::shared_ptr<StdoutSink>;StdoutSink() = default;void log(const char *data, size_t len) {std::cout.write(data, len);}
};
FileSink
- 功能:实现将日志写入到指定文件中
- 实现逻辑
- 构造函数接收一个文件名filename,用于指定日志输出的指定文件
- util::file::create_directory函数则是确保日志文件目录是否存在,如果不存在则对该目录进行创建
- _ofs.oen:表示以二进制追加模式打开文件,如果文件不存在则会创建它
- log函数则是通过write逻辑实现数据写入
- _ofs.good():检查写操作是否成功,如果失败则输出错误信息
class FileSink : public LogSink {
public:using ptr = std::shared_ptr<FileSink>;FileSink(const std::string &filename): _filename(filename) {util::file::create_directory(util::file::path(filename));_ofs.open(_filename, std::ios::binary | std::ios::app);assert(_ofs.is_open());}const std::string &file() { return _filename; }void log(const char *data, size_t len) {_ofs.write((const char*)data, len);if (_ofs.good() == false) {std::cout << "日志输出文件失败!\n";}}
private:std::string _filename;std::ofstream _ofs;
};
RollSink
- 功能:滚动日志功能的实现,也就是当日志文件达到一定大小的时候,自动生成新的日志文件继续写入
- 实现逻辑
- 构造函数:初始化日志文件基本信息,_basename文件名(基础文件名,后面可以加上拓展文件名),_max_fsize最大文件大小
- initLogFile:检查当前日志文件的大小,如果文件已经达到设定的最大文件大小或是文件没有打开,则关闭当前文件并创建新的日志文件
- createFilename函数:根据当前的时间,生成新的日志文件名,文件名中包含的有日期和时间,同时于要保证每个文件名的唯一性
- Log函数;首先初始化日志文件(调用initLogFile函数)确保日志已经准备好,然后将日志数据写入到文件中,最后更新当前文件大小_cur_fsize
class RollSink : public LogSink {
public:using ptr = std::shared_ptr<RollSink>;RollSink(const std::string &basename, size_t max_fsize):_basename(basename), _max_fsize(max_fsize), _cur_fsize(0) {util::file::create_directory(util::file::path(basename));}void log(const char *data, size_t len) {initLogFile();_ofs.write(data, len);if (_ofs.good() == false) {std::cout << "日志输出文件失败!\n";}_cur_fsize += len;}
private:void initLogFile() {if (_ofs.is_open() == false || _cur_fsize >= _max_fsize) {_ofs.close();std::string name = createFilename();_ofs.open(name, std::ios::binary | std::ios::app);assert(_ofs.is_open());_cur_fsize = 0;return;}return;}std::string createFilename() {time_t t = time(NULL);struct tm lt;localtime_r(&t, <);std::stringstream ss;ss << _basename;ss << lt.tm_year + 1900;ss << lt.tm_mon + 1;ss << lt.tm_mday;ss << lt.tm_hour;ss << lt.tm_min;ss << lt.tm_sec;ss << ".log";return ss.str();}
private:std::string _basename;std::ofstream _ofs;size_t _max_fsize;size_t _cur_fsize;
};
SinkFactory
- 作用:工厂类,主要用于创建各种LogSInk对象
- 实现逻辑
- 通过模板函数
create
,SinkFactory
可以创建任意类型的LogSink
派生类对象- 使用
std::make_shared<SinkType>
创建对象,std::forward<Args>(args)...
用于完美转发构造函数的参数,使得对象的创建既高效又灵活
class SinkFactory {
public:template<typename SinkType, typename ...Args>static LogSink::ptr create(Args &&...args) {return std::make_shared<SinkType>(std::forward<Args>(args)...);}
};
单元测试
功能测试1:简单测试其功能
#include "sink.hpp"
#include <iostream>
#include <string>
#include <cstring> int main() {// 创建并测试 StdoutSink(标准输出日志){bitlog::LogSink::ptr stdoutSink = bitlog::SinkFactory::create<bitlog::StdoutSink>();const char* message = "这是一个用于测试 StdoutSink 的消息。\n";stdoutSink->log(message, strlen(message));}// 创建并测试 FileSink(文件输出日志){std::string filename = "test_log.txt";bitlog::LogSink::ptr fileSink = bitlog::SinkFactory::create<bitlog::FileSink>(filename);const char* message = "这是一个用于测试 FileSink 的消息。\n";fileSink->log(message, strlen(message));// 检查文件是否生成并输出文件内容std::ifstream ifs(filename);if (ifs.is_open()) {std::cout << "FileSink 测试通过。日志文件内容:\n";std::string line;while (std::getline(ifs, line)) {std::cout << line << std::endl;}ifs.close();} else {std::cout << "FileSink 测试失败。无法打开日志文件。\n";}}// 创建并测试 RollSink(滚动文件日志){std::string basename = "test_roll_log";size_t max_fsize = 50; // 小文件大小以便快速测试滚动bitlog::LogSink::ptr rollSink = bitlog::SinkFactory::create<bitlog::RollSink>(basename, max_fsize);const char* message1 = "这是用于测试 RollSink 的第一条日志消息。\n";const char* message2 = "这是用于测试 RollSink 的第二条日志消息。这条消息较长,用于触发滚动功能。\n";rollSink->log(message1, strlen(message1));rollSink->log(message2, strlen(message2));// 检查是否生成多个日志文件for (int i = 0; i < 2; ++i) {std::string rollFilename = basename + std::to_string(1900 + localtime(nullptr)->tm_year)+ std::to_string(1 + localtime(nullptr)->tm_mon)+ std::to_string(localtime(nullptr)->tm_mday)+ std::to_string(localtime(nullptr)->tm_hour)+ std::to_string(localtime(nullptr)->tm_min)+ std::to_string(localtime(nullptr)->tm_sec + i)+ ".log";std::ifstream ifs(rollFilename);if (ifs.is_open()) {std::cout << "RollSink 测试通过。日志文件 " << rollFilename << " 内容:\n";std::string line;while (std::getline(ifs, line)) {std::cout << line << std::endl;}ifs.close();} else {std::cout << "RollSink 测试失败。无法打开日志文件 " << rollFilename << "。\n";}}}return 0;
}
复杂测试,并发场景下多种日志格式以及滚动日志
#include "sink.hpp"
#include <iostream>
#include <string>
#include <cstring>
#include <thread>
#include <vector>
#include <chrono>
#include <atomic>// 模拟多线程日志写入的函数
void thread_log(bitlog::LogSink::ptr sink, const std::string& prefix, int num_messages, std::atomic<int>& counter) {for (int i = 0; i < num_messages; ++i) {std::string message = prefix + " 线程 " + std::to_string(counter++) + ": 日志消息 " + std::to_string(i + 1) + "\n";sink->log(message.c_str(), message.size());std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 模拟写入间隔}
}int main() {std::atomic<int> counter(1); // 用于标记每条日志的编号// 测试并发写入到 StdoutSink{bitlog::LogSink::ptr stdoutSink = bitlog::SinkFactory::create<bitlog::StdoutSink>();std::vector<std::thread> threads;// 启动多个线程进行并发日志写入for (int i = 0; i < 5; ++i) {threads.emplace_back(thread_log, stdoutSink, "StdoutSink", 10, std::ref(counter));}for (auto& t : threads) {t.join(); // 等待所有线程完成}std::cout << "StdoutSink 并发测试完成。\n";}// 测试多线程写入到 FileSink{std::string filename = "complex_test_log.txt";bitlog::LogSink::ptr fileSink = bitlog::SinkFactory::create<bitlog::FileSink>(filename);std::vector<std::thread> threads;// 启动多个线程进行并发日志写入for (int i = 0; i < 5; ++i) {threads.emplace_back(thread_log, fileSink, "FileSink", 10, std::ref(counter));}for (auto& t : threads) {t.join(); // 等待所有线程完成}std::cout << "FileSink 并发测试完成。日志文件:" << filename << "\n";}// 测试复杂滚动日志{std::string basename = "complex_roll_test_log";size_t max_fsize = 100; // 设置较小的文件大小以触发滚动bitlog::LogSink::ptr rollSink = bitlog::SinkFactory::create<bitlog::RollSink>(basename, max_fsize);std::vector<std::thread> threads;// 启动多个线程进行并发日志写入for (int i = 0; i < 5; ++i) {threads.emplace_back(thread_log, rollSink, "RollSink", 10, std::ref(counter));}for (auto& t : threads) {t.join(); // 等待所有线程完成}std::cout << "RollSink 并发测试完成。\n";// 检查生成的滚动日志文件for (int i = 0; i < 10; ++i) {std::string rollFilename = basename + std::to_string(1900 + localtime(nullptr)->tm_year)+ std::to_string(1 + localtime(nullptr)->tm_mon)+ std::to_string(localtime(nullptr)->tm_mday)+ std::to_string(localtime(nullptr)->tm_hour)+ std::to_string(localtime(nullptr)->tm_min)+ std::to_string(localtime(nullptr)->tm_sec + i)+ ".log";std::ifstream ifs(rollFilename);if (ifs.is_open()) {std::cout << "RollSink 测试通过。日志文件 " << rollFilename << " 内容:\n";std::string line;while (std::getline(ifs, line)) {std::cout << line << std::endl;}ifs.close();} else {std::cout << "无法打开日志文件 " << rollFilename << "(可能文件数过多已删除)。\n";}}}return 0;
}
相关文章:

高性能日志系统 日志输出模块逻辑
概述 该模块主要实现了一个日志系统的输出模块,通过多态、工厂模式等设计模式,构建灵活的日志输出架构。 功能:格式化完成的标准日志消息,输出到指定为止拓展:支持同时将日志落地到不同的位置,也就是输出日…...

haproxy基础
目录 1 HAProxy介绍 1.1 版本对比 1.2 HAProxy功能 2 参数介绍与实践 2.1 global参数说明 2.2 真实代码格式实例 2.3 常用全局参数 2.3.1 nbproc -- 开启几个进程 2.3.2 cpu-map(CUP绑定) 2.3.3 nbthread 2 --开启2个线程 3 Proxies配置 3.1 Proxies配置-defaults 3.2 Proxi…...

C++ 面试题常用总结 详解(满足c++ 岗位必备,不定时更新)
📚 本文主要总结了一些常见的C面试题,主要涉及到语法基础、STL标准库、内存相关、类相关和其他辅助技能,掌握这些内容,基本上就满足C的岗位技能(红色标记为重点内容),欢迎大家前来学习指正&…...

LVS实验——部署DR模式集群
目录 一、实验环境 二、配置 1、LVS 2、router 3、client 4、RS 三、配置策略 四、测试 1.Director服务器采用双IP桥接网络,一个是VPP,一个DIP 2.Web服务器采用和DIP相同的网段和Director连接 3.每个Web服务器配置VIP 4.每个web服务器可以出外网…...

pythonUI自动化008::allure测试报告(安装及应用)
allure报告预览 1 下载jdk,配置jdk Path变量: https://www.cnblogs.com/FBGG/p/15103119.html(这里不作阐述,请看该偏文章配置即可) 2 下载allure驱动,配置allure Path变量: 下载allure驱动&a…...
常用的 git 和 linux 命令有哪些?
对于 Git 命令: 1. git init:初始化一个新的 Git 仓库。 2. git clone:克隆一个远程仓库到本地。 3. git add:将文件添加到暂存区。 4. git commit:提交暂存区的更改。 5. git status:查看工作区和暂存…...
MYSQL 删除一个字段前,判断字段是否存在
开发过程中经常需要提交可以重复执行的sql,当设计到需要增加字段时,可以参考如下办法: 1.如果是mysql 版本高于5.7.5 ALTER TABLE table_name DROP COLUMN IF EXISTS column_name; 2.通用方法 写一个存储过程,然后用存储过程取…...

vulnstack-5
环境搭建 靶场虚拟机共用两个,一个外网一个内网,用来练习红队相关内容和方向,主要包括常规信息收集、Web攻防、代码审计、漏洞利用、内网渗透以及域渗透等相关内容学习。 虚拟机密码 win7 sun\heart 123.com sun\Administrator dc123.com # …...

回归预测|基于灰狼优化GWO-Transformer-BiLSTM组合模型的数据回归预测Matlab程序 多特征输入单输出
回归预测|基于灰狼优化GWO-Transformer-LSTM组合模型的数据回归预测Matlab程序 多特征输入单输出 文章目录 前言回归预测|基于灰狼优化GWO-Transformer-BiLSTM组合模型的数据回归预测Matlab程序 多特征输入单输出GWO-Transformer-BiLSTM 一、GWO-Transformer-BiLSTM模型二、实验…...

STM32的USB接口介绍
STM32 USB接口是STM32微控制器系列中集成的一种通信接口,它允许STM32微控制器与外部设备或计算机进行高速的数据传输和通信。以下是STM32 USB接口的简要介绍: 1. 接口类型 STM32的USB接口通常支持USB 2.0标准,部分高端型号可能还支持USB 3.…...

【中等】 猿人学web第一届 第2题 js混淆 动态cookie 1
目录 调试干扰Hook Function 加密参数定位hook Cookie AST 解混淆字符串解密还原解密函数AST 配合解密函数还原字符串 ASCII 编码字符串还原字符串相加花指令(对象)剔除无用代码虚假 if剔除无引用代码剔除无引用的对象数值还原 switch 还原完整的 AST 代码代码注意 还原加密 请…...

ubuntu 22.04 安装 docker(服务器从毛胚到精装)
1、用户操作 阿里云默认是 root 用户,我们一般要自己创建一个用户,然后给该用户 sudo 权限 添加用户 sudo adduser newUserName赋予sudo权限 sudo usermod -aG sudo newUserName删除用户 sudo deluser --remove-home --remove-all-files newUserNam…...

Vue3从零开始——如何巧妙使用setup语法糖、computed函数和watch函数
文章目录 一、setup语法糖二、computed函数2.1 computed的基本用法2.2 computed vs methods2.3 注意事项 三、watch函数3.1 watch的基本用法3.2 immediate和deep选项 四、综合小Demo五、总结 一、setup语法糖 之前我们在编写代码时每次都要编写setup() ,默认导出配置&#x…...

【C++】 特殊类设计:从构思到实现,引领设计新潮流
🌈 个人主页:Zfox_ 🔥 系列专栏:C从入门到精通 目录 🚀 前言 一: 🔥 不能被拷贝的类 二: 🔥 只能在堆上创建对象的类 三: 🔥 只能在栈上创建对象的…...

性能调优 18. Tomcat整体架构及其设计精髓分析
1. Tomcat介绍 1.1. 介绍 这边使用的是Tomcat9来做说明,本章节先对Tomcat架构和设计有个整体认识。后续章节会对Tomcat性能调优做说明。 官方文档介绍 https://tomcat.apache.org/tomcat-9.0-doc/index.html1.2. Tomcat概念 …...

【C++高阶】:特殊类设计和四种类型转换
✨ 人生如梦,朝露夕花,宛若泡影 🌏 📃个人主页:island1314 🔥个人专栏:C学习 ⛺️ 欢迎关注:👍点赞 👂&am…...

kafka基础概念二
1.Kafka中主题和分区的概念 1.主题Topic 主题-topic在kafka中是一个逻辑的概念,kafka通过topic将消息进行分类。不同的topic会被订阅该topic的消费者消费 但是有一个问题,如果说这个topic中的消息非常非常多,多到需要几T来存,因…...
牛客-热身小游戏
题目链接:热身小游戏 第一种写法:线段树 介绍第二种写法:并查集 对于一些已经查询过的点,我们可以往后跳,进行路径压缩,他们的父亲为下一个点。 a数组记录[ l , r ] 之间的乘积,初始值为1。…...
Python 深度学习调试问题
Python–深度学习解决的常见问题 1.在自己写测试样例的时候,有时候可能将要传入的是input_size,不小心传入为input_dim,这个时候会导致出现问题,自定义的卷积模块或者池化等模块会提示类型问题。 解决的策略是: 1.进行assert i…...
linux恶意请求
nginx访问日志: 162.243.135.29 - - [05/Jan/2024:00:12:07 0800] "GET /autodiscover/autodiscover.json?zdi/Powershell HTTP/1.1" 404 153 "-" "Mozilla/5.0 zgrab/0.x"107.151.182.54 - - [04/Mar/2024:11:30:06 0800] "G…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章 摘要: 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言,受限于 C 语言本身的内存安全和并发安全问题,开发复杂模块极易引入难以…...

大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...