IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 日志系统--c++ -> 正文阅读

[C++知识库]日志系统--c++

前言

此博客记录对于TinyWebServer项目的学习,并根据自己的理解做出些许更改。
原项目地址:https://github.com/qinguoyi/TinyWebServer

Log

日志系统是用来存储程序运行中的通知信息、warning、error等。
首要任务就是格式化输出字符串到一个文件,还应当记录消息产生的时间,此系统选择使用按天作为文件名。
以消费者-生产者模式,使用阻塞队列实现线程异步处理Log消息。
还实现了主线程同步处理Log消息。

Log.h

从头文件可以清楚看出Log类具有的功能。

#ifndef LOG_H
#define LOG_H

#include <stdio.h>
#include <iostream>
#include <string>
#include <stdarg.h>
#include <pthread.h>
#include "block_queue.h"

using namespace std;

class Log
{
public:
    //C++11以后,使用局部变量懒汉不用加锁
    static Log *get_instance();
    
    //异步写的线程
    static void *flush_log_thread(void *args)
    {
        Log::get_instance()->async_write_log();
    }
    //可选择的参数有日志文件、日志缓冲区大小、最大行数以及最长日志条队列
    bool init(const char *file_name, int close_log, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0);
    //写入消息
    void write_log(int level, const char *format, ...);
    //刷新缓冲区
    void flush(void);

private:
    Log();
    virtual ~Log();
    void *async_write_log();

private:
    char dir_name[128]; //路径名
    char log_name[128]; //log文件名
    int m_split_lines;  //日志最大行数
    int m_log_buf_size; //日志缓冲区大小
    long long m_count;  //日志行数记录
    int m_today;        //因为按天分类,记录当前时间是那一天
    FILE *m_fp;         //打开log的文件指针
    char *m_buf;
    block_queue<string> *m_log_queue; //阻塞队列
    bool m_is_async;                  //是否同步标志位
    locker m_mutex;
    int m_close_log; //关闭日志
};
//使用宏定义,便于调用, 使用##__VA_ARGS__,支持format后面可有0到多个参数
#define LOG_DEBUG(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(0, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_INFO(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(1, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_WARN(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(2, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_ERROR(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(3, format, ##__VA_ARGS__); Log::get_instance()->flush();}

#endif

init

//异步需要设置阻塞队列的长度,同步不需要设置
bool Log::init(const char *file_name, int close_log, int log_buf_size, int split_lines, int max_queue_size)
{
    //如果设置了max_queue_size,则设置为异步
    if (max_queue_size >= 1)
    {
        m_is_async = true;
        m_log_queue = new block_queue<string>(max_queue_size);
        pthread_t tid;
        //flush_log_thread为回调函数,这里表示创建线程异步写日志
        pthread_create(&tid, NULL, flush_log_thread, NULL);
    }
    
    m_close_log = close_log;
    m_log_buf_size = log_buf_size;
    m_buf = new char[m_log_buf_size];
    memset(m_buf, '\0', m_log_buf_size);
    m_split_lines = split_lines;

    time_t t = time(NULL);
    struct tm *sys_tm = localtime(&t);
    struct tm my_tm = *sys_tm;

    //‘/’最后出现的位置
    const char *p = strrchr(file_name, '/');
    char log_full_name[256] = {0};

    if (p == NULL)
    {
        //没有'/',直接用时间+file_name作为文件名
        snprintf(log_full_name, 255, "%d_%02d_%02d_%s", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, file_name);
    }
    else
    {
        //有‘/’,把前面的路径提取出来作为文件路径
        strcpy(log_name, p + 1);
        strncpy(dir_name, file_name, p - file_name + 1);
        snprintf(log_full_name, 255, "%s%d_%02d_%02d_%s", dir_name, my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, log_name);
    }

    m_today = my_tm.tm_mday;
    
    m_fp = fopen(log_full_name, "a");
    if (m_fp == NULL)
    {
        return false;
    }

    return true;
}

write_log实现

这里主要是用到对字符操作的知识,因为要格式化输出,这里简单介绍两个函数:
snprintf()函数用于实现将多个参数格式化输入到一个字符串;
例如

int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ",
                     my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday,
                     my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s);

vsnprintf()函数同样用于实现将多个参数格式化输入到一个字符串,使用va_list结构体获取指定参数后面的参数
例如

int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst);

fomat可以为写入的格式,valst为格式对应的参数,valst也可以为空,不包含参数。
两个函数的返回值都是后面的字符串的长度,字符串长度大于写入size的时候会被截断写入,但是返回值依旧是字符串的长度而不是写入的长度。

void Log::write_log(int level, const char *format, ...)
{
    struct timeval now = {0, 0};
    gettimeofday(&now, NULL);
    time_t t = now.tv_sec;
    struct tm *sys_tm = localtime(&t);
    struct tm my_tm = *sys_tm;
    char s[16] = {0};
    switch (level)
    {
    case 0:
        strcpy(s, "[debug]:");
        break;
    case 1:
        strcpy(s, "[info]:");
        break;
    case 2:
        strcpy(s, "[warn]:");
        break;
    case 3:
        strcpy(s, "[erro]:");
        break;
    default:
        strcpy(s, "[info]:");
        break;
    }
    //写入一个log,对m_count++, m_split_lines最大行数
    m_mutex.lock();
    m_count++;
    //日期改变(不是同一天),或者达到最大行,新建log文件
    if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0) //everyday log
    {
        
        char new_log[256] = {0};
        fflush(m_fp);
        fclose(m_fp);
        char tail[16] = {0};
       
        snprintf(tail, 16, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday);
           
        if (m_today != my_tm.tm_mday)
        {
            snprintf(new_log, 255, "%s%s%s", dir_name, tail, log_name);
            m_today = my_tm.tm_mday;
            m_count = 0;
        }
        else
        {
            snprintf(new_log, 255, "%s%s%s.%lld", dir_name, tail, log_name, m_count / m_split_lines);
        }
        m_fp = fopen(new_log, "a");
    }
 
    m_mutex.unlock();

    va_list valst;
    //获取可变参数,传入的format后的参数
    va_start(valst, format);

    string log_str;
    m_mutex.lock();

    //写入的具体时间内容格式
    int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ",
                     my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday,
                     my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s);
    //这里m是输入字符串的长度,vsnprintf最后一位默认为'\0'
    int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst);
    //这里m_log_buf_size如果小于输入n+m,会发生数组越界
    //m_buf[n + m] = '\n';
    //m_buf[n + m + 1] = '\0';
    //修改如下
    if(n + m + 1 > m_log_buf_size - 1)
    {
        m_buf[m_log_buf_size - 2] = '\n';
        m_buf[m_log_buf_size - 1] = '\0';
    }
    else{
        m_buf[n + m] = '\n';
        m_buf[n + m + 1] = '\0';
    }
    log_str = m_buf;

    m_mutex.unlock();

    if (m_is_async && !m_log_queue->full())
    {
        //异步模式放到队列里等待处理
        m_log_queue->push(log_str);
    }
    else
    {
        //同步模式直接写入
        m_mutex.lock();
        fputs(log_str.c_str(), m_fp);
        m_mutex.unlock();
    }

    va_end(valst);
}

其中异步模式,是将处理好的字符串放到了阻塞队列中,等待处理线程进行处理,处理线程只需要取出该字符串,将其写入文件

void* Log::async_write_log()
{
    string single_log;
    //从阻塞队列中取出一个日志string,写入文件流
    while (m_log_queue->pop(single_log))
    {
        m_mutex.lock();
        //写入文件
        fputs(single_log.c_str(), m_fp);
        m_mutex.unlock();
    }
}

测试

同步模式

#include "log.h"
int main()
{
    int m_close_log = 0;
    Log::get_instance()->init("log_test", 0, 60);
    LOG_DEBUG("debug test");
    LOG_INFO("%d, %s\n", 22, "abc");
    return 0;
}

在这里插入图片描述

异步模式

设置两个写入线程,分别写入INFO和WARN

#include "log.h"
#include <unistd.h>
int m_close_log = 0;
static int count = 0;
locker mutex;
void* log_info(void *arg)
{
    while (1)
    {
        usleep(1000);
        mutex.lock();
        LOG_INFO("INFO: %d", ++count);
        mutex.unlock();
    }
}
void* log_warn(void *arg)
{
    while (1)
    {
        usleep(1000);
        mutex.lock();
        LOG_WARN("WARN: %d", ++count);
        mutex.unlock();
    }
}

int main()
{
    Log::get_instance()->init("../log_info", 0, 60, 800, 20);

    pthread_t info, warn;
    pthread_create(&info, NULL, log_info, NULL);
    pthread_create(&warn, NULL, log_warn, NULL);
    sleep(1);
    pthread_cancel(info);
    pthread_cancel(warn);

    return 0;
}

因为最大行设置的为800,所以log信息被保存为两个文件,内容如下图所示。
在这里插入图片描述
在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-30 11:45:05  更:2021-09-30 11:47:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 4:14:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计