同步+异步日志系统(C++实现)

news2024/12/26 3:06:48

对于一个服务器而言,不论是在调试中还是在运行中,都需要通过打日志的方式来记录程序的运行情况。本文设计的日志系统实现了同步与异步两种功能,原理见下图:

 同步日志:日志写入函数与工作线程串行执行,由于涉及到I/O操作,当单条日志比较大的时候,同步模式会阻塞整个处理流程,服务器所能处理的并发能力将有所下降,尤其是在峰值的时候,写日志可能成为系统的瓶颈。

异步日志:将所写的日志内容先存入阻塞队列中,写线程从阻塞队列中取出内容,写入日志。

日志的运行流程:

1、使用单例模式(局部静态变量方法)获取实例Log::getInstance()。

2、通过实例调用Log::getInstance()->init()函数完成初始化,若设置阻塞队列大小大于0则选择异步日志,等于0则选择同步日志,更新isAysnc变量。

3、通过实例调用write_log()函数写日志,首先根据当前时刻创建日志(前缀为时间,后缀为".log",并更新日期today和当前行数lineCount。

4、在write_log()函数内部,通过isAsync变量判断写日志的方法:如果是异步,工作线程将要写的内容放进阻塞队列中,由写线程在阻塞队列中取出数据,然后写入日志;如果是同步,直接写入日志文件中。

日志的分级与分文件:

分级情况:

  • Debug,调试代码时的输出,在系统实际运行时,一般不使用。
  • Warn,这种警告与调试时终端的warning类似,同样是调试代码时使用。
  • Info,报告系统当前的状态,当前执行的流程或接收的信息等。
  • Erro,输出系统的错误信息

分文件情况:

  1. 按天分,日志写入前会判断当前today是否为创建日志的时间,若为创建日志时间,则写入日志,否则按当前时间创建新的log文件,更新创建时间和行数。
  2. 按行分,日志写入前会判断行数是否超过最大行限制,若超过,则在当前日志的末尾加lineCount / MAX_LOG_LINES为后缀创建新的log文件。

Log.h

 #ifndef LOG_H
 #define LOG_H

#include "blockqueue.h"
#include <mutex>
#include <thread>
#include "buffer.h"
#include <string>
#include <stdarg.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <assert.h>

 class Log
 {
 public:
    static Log* getInstance()
    {
        static Log instance;
        return &instance;
    }
    //初始化日志实例(阻塞队列最大容量、日志保存路径、日志文件后缀)
    void init(int maxQueueCapacity = 1024,
            const char* path_="./log",
            const char* suffix_=".log");

    //异步写日志公有方法,调用私有方法asyncWrite
    static void flushLogThread()
    {
        Log::getInstance()->asyncWrite();
    }

    //将输出内容按照标准格式整理
   void writeLog(int level, const char* format, ...);

private:
    Log();
    ~Log();
    //异步写日志方法
    void asyncWrite();

private:
    const int LOG_NAME_LEN=256;   //日志文件最长文件名
    const int MAX_LOG_LINES=50000;//日志文件内的最长日志条数

    const char* path;      //路径名
    const char* suffix;    //后缀名  
    int lineCount;   //日志行数记录
    int today;             //按当天日期区分文件
    FILE* fp;              //打开log的文件指针
    Buffer buff;           //输出的内容
    std::unique_ptr<BlockQueue<std::string>> deque;  //阻塞队列
    std::unique_ptr<std::thread> writeThread;        //写线程
    bool isAsync;          //是否开启异步日志
    std::mutex mtx;        //同步日志必需的互斥量
};


//四个宏定义,主要用于不同类型的日志输出
#define LOG_DEBUG(format, ...) Log::getInstance()->writeLog(0, format, ##__VA_ARGS__)
#define LOG_INFO(format, ...)  Log::getInstance()->writeLog(1, format, ##__VA_ARGS__)
#define LOG_WARN(format, ...)  Log::getInstance()->writeLog(2, format, ##__VA_ARGS__)
#define LOG_ERROR(format, ...) Log::getInstance()->writeLog(3, format, ##__VA_ARGS__)

#endif // !LOG_H

Log.cpp

#include "log.h"

Log::Log():lineCount(0),
            today(0),
            fp(nullptr),
            deque(nullptr),
            writeThread(nullptr),
            isAsync(false){}

Log::~Log()
{
    if(writeThread&&writeThread->joinable())
    {
        while(!deque->empty())//清空阻塞队列中的全部任务
        {
            deque->flush();
        }
        deque->close();
        writeThread->join();//等待当前线程完成手中的任务
    }
    if(fp)//冲洗文件缓冲区,关闭文件描述符
    {
        std::lock_guard<std::mutex> lock(mtx);
        fflush(fp);
        fclose(fp);
    }
}

void Log::init(int maxQueueCapacity,const char* path_,const char* suffix_)
{
    if(maxQueueCapacity>0)//异步方式
    {
        isAsync=true;
        if(!deque)
        {
            std::unique_ptr<BlockQueue<std::string>> newDeque(new BlockQueue<std::string>(maxQueueCapacity));
            deque=std::move(newDeque);
            std::unique_ptr<std::thread> newThread(new std::thread(flushLogThread));
            writeThread=std::move(newThread);
        }
    }
    else//同步方式
    {
        isAsync=false;
    }
    lineCount=0;
    //生成日志文件名
    time_t timer=time(nullptr);
    struct tm* sysTime=localtime(&timer);
    struct tm t=*sysTime;
    path=path_;
    suffix=suffix_;
    char filename[LOG_NAME_LEN]={0};
    snprintf(filename,LOG_NAME_LEN-1,"%s/%04d_%02d_%02d%s",
            path,t.tm_year+1900,t.tm_mon+1,t.tm_mday,suffix);
    today=t.tm_mday;
    {
        std::lock_guard<std::mutex> lock(mtx);
        buff.retrieveAll();
        if(fp)
        {
            fflush(fp);
            fclose(fp);
        }
        fp=fopen(filename,"a");
        if(fp==nullptr)
        {
            mkdir(path,0777);//先生成目录文件(最大权限)
            fp=fopen(filename,"a");
        }
        assert(fp!=nullptr);
    }
}

void Log::writeLog(int level, const char* format, ...)
{
    struct timeval now={0,0};
    gettimeofday(&now,nullptr);
    time_t tSec=now.tv_sec;
    struct tm* sysTime=localtime(&tSec);
    struct tm t=*sysTime;
    va_list vaList;

    if(today!=t.tm_mday||(lineCount&&(lineCount%MAX_LOG_LINES==0)))
    {
        //生成最新的日志文件名
        char newFile[LOG_NAME_LEN];
        char tail[36]={0};
        snprintf(tail,35,"%04d_%02d_%02d",t.tm_year+1900,t.tm_mon+1,t.tm_mday);
        if(today!=t.tm_mday)//时间不匹配,则替换为最新的日志文件名
        {
            snprintf(newFile,LOG_NAME_LEN-1,"%s/%s%s",path,tail,suffix);
            today=t.tm_mday;
            lineCount=0;
        }
        else//长度超过日志最长行数,则生成xxx-1、xxx-2文件
        {
            snprintf(newFile,LOG_NAME_LEN-1,"%s/%s-%d%s",path,tail,(lineCount/MAX_LOG_LINES),suffix);
        }

        if(fp)
        {
            std::lock_guard<std::mutex> lock(mtx);
            fflush(fp);
            fclose(fp);
        }
        fp=fopen(newFile,"a");
        assert(fp!=nullptr);
    }
    
    //在buffer内生成一条对应的日志信息
    {
        std::lock_guard<std::mutex> lock(mtx);
        lineCount++;
        //添加年月日时分秒微秒———"2022-12-29 19:08:23.406500"
        int n=snprintf(buff.beginWrite(),128,"%04d-%02d-%02d %02d:%02d:%02d.%06ld ",
                        t.tm_year+1900,t.tm_mon+1,t.tm_mday,
                        t.tm_hour,t.tm_min,t.tm_sec,now.tv_usec);
        buff.hasWritten(n);
        //添加日志等级———"2022-12-29 19:08:23.406539 [debug]: "
        switch(level) 
        {
        case 0:
            buff.append("[debug]: ", 9);
            break;
        case 1:
            buff.append("[info] : ", 9);
            break;
        case 2:
            buff.append("[warn] : ", 9);
            break;
        case 3:
            buff.append("[error]: ", 9);
            break;
        default:
            buff.append("[info] : ", 9);
            break;
        }
        //添加使用日志时的格式化输入———"2022-12-29 19:08:23.535531 [debug]: Test 222222222 8 ============= "
        va_start(vaList, format);
        int m = vsnprintf(buff.beginWrite(), buff.writableBytes(), format, vaList);
        va_end(vaList);
        buff.hasWritten(m);
        //添加换行符与字符串结尾
        buff.append("\n\0", 2);
    }
    
    if(isAsync&&deque&&!deque->full())//异步方式(加入阻塞队列中,等待写线程读取日志信息)
    {
        deque->push_back(buff.retrieveAllAsString());
    }
    else//同步方式(直接向文件中写入日志信息)
    {
        std::lock_guard<std::mutex> lock(mtx);
        fputs(buff.peek(),fp);
    }
    {//清理buffer缓冲区
        std::lock_guard<std::mutex> lock(mtx);
        buff.retrieveAll();
    }
}

void Log::asyncWrite()
{
    std::string str="";
    while (deque->pop(str))
    {
        std::lock_guard<std::mutex> lock(mtx);
        fputs(str.c_str(),fp);
    }
}

测试程序:test.cpp

分别采用同步和异步方式,各写60000(15000*4)条日志信息。

#include "log.h"

void TestLog() 
{
    int cnt = 0;
    Log::getInstance()->init(0,"./testlog1");//同步日志
    for(int j = 0; j < 15000; j++ )
    {
        LOG_DEBUG("%s 111111111 %d ============= ", "Test", cnt++);
        LOG_INFO ("%s 111111111 %d ============= ", "Test", cnt++);
        LOG_WARN ("%s 111111111 %d ============= ", "Test", cnt++);
        LOG_ERROR("%s 111111111 %d ============= ", "Test", cnt++);
    }

    cnt = 0;
    Log::getInstance()->init(1024,"./testlog2");//异步日志
    for(int j = 0; j < 15000; j++ )
    {
        LOG_DEBUG("%s 222222222 %d ============= ", "Test", cnt++);
        LOG_INFO ("%s 222222222 %d ============= ", "Test", cnt++);
        LOG_WARN ("%s 222222222 %d ============= ", "Test", cnt++);
        LOG_ERROR("%s 222222222 %d ============= ", "Test", cnt++);
    }
}

int main() 
{
    TestLog();
}

实验结果:

同步日志:

由于共写60000条日志,一份日志文件设置最大行数为50000,所以分为两个文件

 

 

 异步日志:

 

参考资料:

最新版Web服务器项目详解 - 09 日志系统(上)

最新版Web服务器项目详解 - 10 日志系统(下)

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

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

相关文章

人脸识别与美颜算法实战-基于机器学习的人脸识别

机器学习根据输出的类型一般分为两类,分类和回归。分类的输出一般是离散值,回归输出的值一般是连续的。比如,人脸识别这种就属于分类问题,房价预测一般是一个回归问题。 鸢尾花分类 # -*- coding: UTF-8 -*- # 导入数据集 from sklearn.datasets import load_iris iris =…

InnoDB事务原理理解(redo log,undo log,锁,MVCC的理解)

目录事务事务的四大特性ACID事务相关SQL语句事务原理事务如何解决隔离性隔离性总结事务如何解决原子性、一致性、持久性redo log 重做日志CheckPoint 检查点机制Double Writer 双写磁盘undo log 回滚日志锁表级锁表读锁、表写锁元数据锁MDL意向锁行级锁行读锁&#xff0c;行写锁…

coremail邮件安全网关产品详细学习笔记(上)

声明 本文是学习中国企业邮箱安全性研究报告. 下载地址而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 钓鱼邮件 钓鱼邮件的规模 在本章内容中&#xff0c;钓鱼邮件是指含有恶意欺诈信息的邮件&#xff0c;包括OA钓鱼邮件、鱼叉邮件、钓鲸邮件、CEO仿…

PHP代码审计

1. "" 与 “” 在进行比较的时候&#xff0c;会先将字符串类型转化成相同&#xff0c;如果整型跟字符型比较字符或从左往右提取整型直到遇到字符结束&#xff0c;再比较。 在进行比较的时候&#xff0c;会先判断两种字符串的类型是否相等&#xff0c;当等号两边类…

nodejs调用matlab的.m文件

1、问题的提出&#xff1a; 在一些web服务中&#xff0c;后台采用nodejs轻量化服务器接口&#xff0c;而matlab的.m文件编写了算法模块&#xff0c;两者调用时&#xff0c;官方没有提供相应的文档&#xff08;当然也可能我没找到&#xff09;。因此&#xff0c;本文提出了matl…

上岸学姐的浙大MPA复试流程和内容经验介绍

管理类考研笔试落下帷幕&#xff0c;大家有没有因为周末不上课&#xff0c;平时不刷题而感到生活好像缺了些啥呢&#xff1f;没关系&#xff0c;复试已经可以开始准备啦&#xff0c;尤其是对于我们报考MPA项目的同学们来说。 预计二月份下旬笔试成绩会先出来了&#xff0c…

逻辑回归-sklearn

1.概述 逻辑回归&#xff0c;是一种线性分类器。其本质是由线性回归变化而来的&#xff0c;一种广泛使用于分类问题中的广义回归算法。 最小二乘法就是用来求解线性回归中参数的数学方法。 2.sklearn中的逻辑回归 &#xff08;1&#xff09;逻辑回归分类器&#xff08;又叫l…

Eth08-EthCtrlConfig:以太网控制器的硬件操作的timeout值配置

文章目录 1 EthCtrlConfig:以太网控制器的硬件操作的timeout值配置传送门 ==>> AutoSAR入门和实战系列总目录 1 EthCtrlConfig:以太网控制器的硬件操作的timeout值配置 /MICROSAR/Eth_Enet/Eth/EthConfigSet/EthCtrlConfig: Configuration of the individual control…

【Python应用】tkinter简介

简介 tkinter是Python自带的GUI库。 tkinter的全称是Tk Interface。 其中Tk是开发桌面应用的GUI工具库&#xff0c;它是Tcl的标准GUI&#xff0c;而Tcl全称Tool Command Language&#xff0c;是一种动态编程语言&#xff0c;可用于桌面应用开发。关于Tk和Tcl&#xff0c;可以…

顺序表中基本操作的具体思路与实现(C语言版)

顺序表中基本操作的具体思路与实现&#xff08;C语言版&#xff09;一、操作前的准备工作&#xff08;1&#xff09;定义操作算法中用到的预定义常量和类型&#xff08;2&#xff09;定义线性表中可能拥有的元素的最大个数&#xff08;3&#xff09;定义顺序表类型二、具体操作…

ZC706P+ADRV9009连接RADIOVERSE详解之一

第一步是&#xff1a;安装WINDOWS软件。 从下面网页里面下载所需要的软件和文件&#xff1a; https://www.analog.com/en/design-center/landing-pages/001/transceiver-evaluation-software.html 下载下图中的软件&#xff0c;这是运行在WINDOWS系统上的&#xff0c;通过网…

寒假本科创新学习——机器学习(一)

绪论1.1什么是机器学习1.1.1 Arthur Samuel给出的定义1.1.2 显著式编程和非显著式编程1.1.3 Tom Mitshell给出的定义1.2基本术语1.1什么是机器学习 1.1.1 Arthur Samuel给出的定义 Arthur Samuel是机器学习领域的先驱之一&#xff0c; 他编写了世界上第一个棋类游戏的人工智能…

LaTeX公式与MathType公式如何快速转换

目录 一、官网下载链接 二、将MathType公式转换为LaTex公式&#xff1a; 三、将LaTex公式转换为MathType公式&#xff1a; 现在越来越多的人选择使用MathType来编辑公式&#xff0c;有时在MathType公式之间要与LaTex公式之间相互转换。如果公式比较少时&#xff0c;可以直接…

ORB-SLAM2 --- MapPoint::ComputeDistinctiveDescriptors 函数

目录 一、函数作用 二、函数步骤 三、code 四、函数解析 一、函数作用 计算地图点最具代表性的描述子。 由于一个地图点会被许多相机观测到&#xff0c;因此在插入关键帧后&#xff0c;需要判断是否更新代表当前点的描述子&#xff0c;先获得当前点的所有描述子&#xff…

毕业设计-微博评论文本情感分析,SVM+朴素贝叶斯+AdaBoost,含完整项目文档

基于AdaBoost算法的情感分析研究 完整代码及文档下载地址&#xff1a;毕业设计-微博评论文本情感分析 此项目为本科毕业设计项目&#xff0c;大家借鉴一下思路就好 大学时没有好好学算法&#xff0c;毕竟那些树、图实在提不起兴趣&#xff0c;好在毕业设计选择了个机器学习算…

rabbitmq基础9——流控、镜像队列

文章目录一、流控1.1 流控机制1.2 流控原理1.3 流控状态显示1.4 流控对象1.5 性能提升二、镜像队列2.1 机制原理2.1.1 集群结构2.2 镜像结构2.2.1 组播GM2.2.1.1 实现原理2.2.1.2 加入新节点2.2.1.3 节点宕机的影响2.3 配置镜像队列2.3.1 定义参数2.3.2 命令配置2.3.4 相关命令…

数字验证学习笔记——SystemVerilog芯片验证20 ——线程间的通信

一、线程间的通信 测试平台中的所有线程都需要同步并交换数据。一个线程需要等待另一个。多个线程可能同时访问同一个资源。线程之间可能需要交换数据。所有这些数据交换和同步称之为线程间的通信&#xff08;IPC&#xff09;。 1.1 event 事件 Verilog 中&#xff0c;一个线…

【好书推荐】车载以太网权威指南

20年后&#xff0c;会令你失望的不是做过的事&#xff0c;而是你没做过的&#xff0c;所以解开帆索&#xff0c;从安全的港湾出发&#xff0c;乘风而行&#xff0c;去探索、去梦想、去发现&#xff01; Twenty years from now you will be more disappointed by the things tha…

Linux系统 Ubuntu18.04安装的详细教程(提供18.04ubuntu镜像)

文章目录一、镜像安装二、vim更新 gcc ifconfig下载三、共享文件夹设置设置使用&#xff08;测试共享文件夹是否能使用&#xff0c;这步可以省略&#xff09;四、另外虚拟机名称全名、用户名镜像文件下载&#xff1a;链接&#xff1a;https://pan.baidu.com/s/12bEdRBwO1YbLt23…

数学杂谈:圆上随机落点问题(一)

数学杂谈&#xff1a;圆上随机落点问题&#xff08;一&#xff09; 1. 问题描述2. 问题解答 1. 解法一&#xff1a;递推2. 解法二&#xff1a;受限制的均匀分布3. 数值模拟验证 3. 讨论 & 扩展 1. 问题描述 这道题其实很早之前自己做过一遍&#xff0c;然后前阵子发现苏神…