高性能日志系统 日志输出模块逻辑
概述
该模块主要实现了一个日志系统的输出模块,通过多态、工厂模式等设计模式,构建灵活的日志输出架构。
- 功能:格式化完成的标准日志消息,输出到指定为止
- 拓展:支持同时将日志落地到不同的位置,也就是输出日志到不同位置
- 输出位置
- 标准输出:输出到屏幕上,主要用于测试日志是否正常运行
- 指定文件:将日志信息输出到指定文件中,便于事后进行日志分析
- 滚动文件:文件按照大小或者时间输出到指定文件中,方便按日期或者大小删除日志消息
- 实现思想
- 首先抽象出落地模块类
- 根据不同落地方向,从基类派生出来即可
- 其次工厂模式,创建与表示分离
架构设计
整体架构分析
- 主框架定义了一个日志输出系统,包含多种日志输出方式,例如标准输出、文件输出、滚动文件输出
- 模块代码通过基类 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…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...
ArcPy扩展模块的使用(3)
管理工程项目 arcpy.mp模块允许用户管理布局、地图、报表、文件夹连接、视图等工程项目。例如,可以更新、修复或替换图层数据源,修改图层的符号系统,甚至自动在线执行共享要托管在组织中的工程项。 以下代码展示了如何更新图层的数据源&…...

Linux-进程间的通信
1、IPC: Inter Process Communication(进程间通信): 由于每个进程在操作系统中有独立的地址空间,它们不能像线程那样直接访问彼此的内存,所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...