当前位置: 首页 > news >正文

高性能日志系统 日志输出模块逻辑

概述

该模块主要实现了一个日志系统的输出模块,通过多态、工厂模式等设计模式,构建灵活的日志输出架构。

  • 功能:格式化完成的标准日志消息,输出到指定为止
  • 拓展:支持同时将日志落地到不同的位置,也就是输出日志到不同位置
  • 输出位置
    • 标准输出:输出到屏幕上,主要用于测试日志是否正常运行
    • 指定文件:将日志信息输出到指定文件中,便于事后进行日志分析
    • 滚动文件:文件按照大小或者时间输出到指定文件中,方便按日期或者大小删除日志消息
  • 实现思想
    • 首先抽象出落地模块类
      • 根据不同落地方向,从基类派生出来即可
    • 其次工厂模式,创建与表示分离

架构设计

整体架构分析

  • 主框架定义了一个日志输出系统,包含多种日志输出方式,例如标准输出、文件输出、滚动文件输出
  • 模块代码通过基类 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, &lt);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对象
  • 实现逻辑 
    • 通过模板函数 createSinkFactory 可以创建任意类型的 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;
}

相关文章:

高性能日志系统 日志输出模块逻辑

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

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++ 岗位必备,不定时更新)

&#x1f4da; 本文主要总结了一些常见的C面试题&#xff0c;主要涉及到语法基础、STL标准库、内存相关、类相关和其他辅助技能&#xff0c;掌握这些内容&#xff0c;基本上就满足C的岗位技能&#xff08;红色标记为重点内容&#xff09;&#xff0c;欢迎大家前来学习指正&…...

LVS实验——部署DR模式集群

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

pythonUI自动化008::allure测试报告(安装及应用)

allure报告预览 1 下载jdk&#xff0c;配置jdk Path变量&#xff1a; https://www.cnblogs.com/FBGG/p/15103119.html&#xff08;这里不作阐述&#xff0c;请看该偏文章配置即可&#xff09; 2 下载allure驱动&#xff0c;配置allure Path变量&#xff1a; 下载allure驱动&a…...

常用的 git 和 linux 命令有哪些?

对于 Git 命令&#xff1a; 1. git init&#xff1a;初始化一个新的 Git 仓库。 2. git clone&#xff1a;克隆一个远程仓库到本地。 3. git add&#xff1a;将文件添加到暂存区。 4. git commit&#xff1a;提交暂存区的更改。 5. git status&#xff1a;查看工作区和暂存…...

MYSQL 删除一个字段前,判断字段是否存在

开发过程中经常需要提交可以重复执行的sql&#xff0c;当设计到需要增加字段时&#xff0c;可以参考如下办法&#xff1a; 1.如果是mysql 版本高于5.7.5 ALTER TABLE table_name DROP COLUMN IF EXISTS column_name; 2.通用方法 写一个存储过程&#xff0c;然后用存储过程取…...

vulnstack-5

环境搭建 靶场虚拟机共用两个&#xff0c;一个外网一个内网&#xff0c;用来练习红队相关内容和方向&#xff0c;主要包括常规信息收集、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微控制器系列中集成的一种通信接口&#xff0c;它允许STM32微控制器与外部设备或计算机进行高速的数据传输和通信。以下是STM32 USB接口的简要介绍&#xff1a; 1. 接口类型 STM32的USB接口通常支持USB 2.0标准&#xff0c;部分高端型号可能还支持USB 3.…...

【中等】 猿人学web第一届 第2题 js混淆 动态cookie 1

目录 调试干扰Hook Function 加密参数定位hook Cookie AST 解混淆字符串解密还原解密函数AST 配合解密函数还原字符串 ASCII 编码字符串还原字符串相加花指令(对象)剔除无用代码虚假 if剔除无引用代码剔除无引用的对象数值还原 switch 还原完整的 AST 代码代码注意 还原加密 请…...

ubuntu 22.04 安装 docker(服务器从毛胚到精装)

1、用户操作 阿里云默认是 root 用户&#xff0c;我们一般要自己创建一个用户&#xff0c;然后给该用户 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++】 特殊类设计:从构思到实现,引领设计新潮流

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;C从入门到精通 目录 &#x1f680; 前言 一&#xff1a; &#x1f525; 不能被拷贝的类 二&#xff1a; &#x1f525; 只能在堆上创建对象的类 三&#xff1a; &#x1f525; 只能在栈上创建对象的…...

性能调优 18. Tomcat整体架构及其设计精髓分析

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

【C++高阶】:特殊类设计和四种类型转换

✨ 人生如梦&#xff0c;朝露夕花&#xff0c;宛若泡影 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;C学习 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&am…...

kafka基础概念二

1.Kafka中主题和分区的概念 1.主题Topic 主题-topic在kafka中是一个逻辑的概念&#xff0c;kafka通过topic将消息进行分类。不同的topic会被订阅该topic的消费者消费 但是有一个问题&#xff0c;如果说这个topic中的消息非常非常多&#xff0c;多到需要几T来存&#xff0c;因…...

牛客-热身小游戏

题目链接&#xff1a;热身小游戏 第一种写法&#xff1a;线段树 介绍第二种写法&#xff1a;并查集 对于一些已经查询过的点&#xff0c;我们可以往后跳&#xff0c;进行路径压缩&#xff0c;他们的父亲为下一个点。 a数组记录[ l , r ] 之间的乘积&#xff0c;初始值为1。…...

Python 深度学习调试问题

Python–深度学习解决的常见问题 1.在自己写测试样例的时候&#xff0c;有时候可能将要传入的是input_size&#xff0c;不小心传入为input_dim&#xff0c;这个时候会导致出现问题&#xff0c;自定义的卷积模块或者池化等模块会提示类型问题。 解决的策略是: 1.进行assert i…...

linux恶意请求

nginx访问日志&#xff1a; 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 可视化之热力图

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 热力图&#xff08;Heatmap&#xff09;又叫热点图&#xff0c;是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;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&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

并发编程 - go版

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

elementUI点击浏览table所选行数据查看文档

项目场景&#xff1a; table按照要求特定的数据变成按钮可以点击 解决方案&#xff1a; <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

ArcPy扩展模块的使用(3)

管理工程项目 arcpy.mp模块允许用户管理布局、地图、报表、文件夹连接、视图等工程项目。例如&#xff0c;可以更新、修复或替换图层数据源&#xff0c;修改图层的符号系统&#xff0c;甚至自动在线执行共享要托管在组织中的工程项。 以下代码展示了如何更新图层的数据源&…...

Linux-进程间的通信

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