TinyWebserver的复现与改进(7):日志系统

news2024/12/22 20:59:07

本项目中,使用单例模式创建日志系统,对服务器运行状态、错误信息和访问数据进行记录,该系统可以实现按天分类,超行分类功能,为了简单,将使用异步写入的方式。(后续再添加同步写入)

异步写入方式,就是将生产者-消费者模型封装为阻塞队列,创建一个写线程,工作线程只需要将要写的内容push进队列,写线程从队列中取出内容,写入日志文件即可。

系统流程

image-20240825112035668

为了确保对同一日志文件的统一访问,我们采用get_instance()方法来获取唯一的日志文件指针pget_instance()作为Log类的静态成员函数,它的作用是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

  1. 在程序的入口点main函数中,我们需要首先对Log类进行初始化。
  2. 每当有新的信息需要记录到日志文件中时,系统会执行一个文件有效性检查。如果发现当前没有可用的日志文件、现有日志文件已达到存储上限,或者上一个日志文件不是在当前日期创建的,系统将自动生成一个新的日志文件。
  3. 随后,主线程将负责将信息进行格式化,并将信息添加到阻塞队列中。完成这一操作后,主线程便可以继续执行其他任务,而无需等待日志写入操作的完成。
  4. 与此同时,一旦阻塞队列中有待处理的日志记录任务,专门的写线程(一个子进程)将被唤醒。它会调用async_write_log()函数,将队列中的日志信息异步写入到磁盘中。

在这里,循环队列实际上是一个连续数组+2个移动指针组成的数据结构

循环队列 的图像结果

日志类定义

日志类包括但不限于如下方法,

  • 公有的实例获取方法
  • 初始化日志文件方法
  • 异步日志写入方法,内部调用私有异步方法
  • 内容格式化方法
  • 刷新缓冲区
class Log
{
public:
    // 返回log对象的指针
    static Log *get_instance()
    {
        /* 
            instance只能初始化一次,所以即便调用 get_instance() 函数多次,
            代码static Log instance;只执行一次,即 instance 的地址不会被改变 
        */
        static Log instance;
        return &instance;
    }

    static void *flush_log_thread(void* arg)
    {
        Log::get_instance()->async_write_log();
    }

    bool init(const char *file_name, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0);
    void write_log(int level, const char* format, ...);
    void flush();

private:
    long long m_count;
    int m_today;
    int m_log_buf_size;
    bool m_is_async;
    char *m_buf;
    int m_split_lines;
    char log_name[128]; // log文件名
    char dir_name[128]; // 路径名
    locker m_mutex;
    FILE *m_fp;         //打开log的文件指针
    block_queue<string> *m_log_queue; //阻塞队列

private:
    void *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();
        }
    }

    Log()
    {
        m_count = 0;
        m_is_async = false;
    }

    ~Log()
    {
        if(m_fp != NULL)
        {
            fclose(m_fp);
        }
    }
};

日志类中的方法都不会被其他程序直接调用,末尾的四个可变参数宏提供了其他程序的调用方法。

前述方法对日志等级进行分类,包括DEBUG,INFO,WARN和ERROR四种级别的日志。

#define LOG_DEBUG(format, ...)  Log::get_instance()->write_log(0, format, ##__VA_ARGS__)
#define LOG_INFO(format, ...)  Log::get_instance()->write_log(0, format, ##__VA_ARGS__)
#define LOG_WARN(format, ...)  Log::get_instance()->write_log(0, format, ##__VA_ARGS__)
#define LOG_ERROR(format, ...)  Log::get_instance()->write_log(0, format, ##__VA_ARGS__)

日志分文件判断

  • 日志写入前会判断当前day是否为创建日志的时间,行数是否超过最大行限制
    • 若为创建日志时间,写入日志,否则按当前时间创建新log,更新创建时间和行数
    • 若行数超过最大行限制,在当前日志的末尾加count/max_lines为后缀创建新log
// 一天一个log,或者当文件行数满了,就新开一个log
if(m_today != my_tm.tm_mday || m_count % m_split_lines == 0)
{
    char new_log[256] = {0};
    // 刷新由 fopen 打开的输出流(如文件或控制台)的缓冲区。当使用 fflush 刷新流时,所有缓冲中的数据将被写入到流所关联的文件或设备中。
    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");
}

异步写入日志

void Log::write_log(int level, const char* format, ...)
{
    /*
        struct timeval {
            time_t      tv_sec;     // 秒
            suseconds_t  tv_usec;    // 微秒
        }
    */
   struct timeval now = {0,0};
    // 获取当前的日期和时间, 存储在now中
   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,或者当文件行数满了,就新开一个log
   if(m_today != my_tm.tm_mday || m_count % m_split_lines == 0)
   {
        char new_log[256] = {0};
        // 刷新由 fopen 打开的输出流(如文件或控制台)的缓冲区。当使用 fflush 刷新流时,所有缓冲中的数据将被写入到流所关联的文件或设备中。
        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;
    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);
    // ?
    int m = vsnprintf(m_buf+n, m_log_buf_size-1, format, valst);
    m_buf[n+m] = '\n';
    // 加 \0 表示前面的字符组成一句话
    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);
}

系列文章

GitHub - yzfzzz/MyWebServer: Linux高并发服务器项目,参考了TinyWebServer,将在此基础上进行性能改进与功能增加。为方便读者学习,附带详细注释和博客!

TinyWebserver的复现与改进(1):服务器环境的搭建与测试-CSDN博客

TinyWebserver的复现与改进(2):项目的整体框架-CSDN博客

TinyWebserver的复现与改进(3):线程同步机制类封装及线程池实现-CSDN博客

TinyWebserver的复现与改进(4):主线程的具体实现-CSDN博客

TinyWebserver的复现与改进(5):HTTP报文的解析与响应-CSDN博客

TinyWebserver的复现与改进(6):定时器处理非活动连接-CSDN博客

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

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

相关文章

声音之旅:2024四大必备音乐剪辑软件盘点!

音乐剪辑&#xff0c;作为一种艺术形式&#xff0c;让每个人都能够成为自己音乐故事的导演。今天&#xff0c;我们将探索几款优秀的音乐剪辑工具&#xff0c;它们分别是福昕音频剪辑、Audio Trimmer、Ocenaudio和闪电音频剪辑。 福昕音频剪辑 直达链接&#xff1a;www.pdf365…

去拼多多闭着眼涨薪80%,但。。。

大家好&#xff0c;我是鸭鸭。 如果给你月薪涨 80%&#xff0c;总包涨 35%&#xff0c;但是新工作需要 11116&#xff0c;你会接这个 offer 吗&#xff1f; 起因是鸭鸭今天刷到了这样一个帖子&#xff1a; 楼主表示“吓人啊”&#xff0c;可见这样的涨幅确实超乎预料。 当然…

走进低代码表单开发(二):高效表单设计新利器

前面我们已经介绍了勤研低代码开发平台的报表数据源设计相关的内容&#xff0c;当数据源设计完成后&#xff0c;我们将继续进行表单的页面开发&#xff0c;接下来&#xff0c;我们一起走进勤研低代码开发平台高效便捷的表单设计&#xff0c;来看看勤研低代码平台如何为用户带来…

OJ在线评测系统 前端开发设计优化通用菜单组件 初始化JS全局项目入口

通用菜单组件的开发一 今天完善前端通用项目的模版 我们的前端初始化先用Vue cli脚手架跑页面 然后用arco组件库 我们要完善前端通用项目模版 先改几个bug 优化页面布局 这个footer没有一直处于底部 我们在原生css里去修改 把 position 属性改为 sticky 粘性 #basicLayo…

基于Spring搭建SpringMvc框架

SpringMvc Spring MVC 是 Spring 框架的一部分&#xff0c;它是一个设计用来构建 web 应用的框架。Spring MVC 实现了 Model-View-Controller&#xff08;MVC&#xff09;设计模式&#xff0c;帮助开发者将业务逻辑、用户界面以及输入处理等职责分离&#xff0c;从而提高代码的…

论文速读|Neural MP:一种通用神经运动规划器

论文地址&#xff1a;https://mihdalal.github.io/neuralmotionplanner/resources/paper.pdf 这篇论文提出的Neural MP方法通过大规模数据生成、通用神经策略和测试时优化&#xff0c;显著提高了运动规划的效率和成功率。Neural MP在真实世界中的表现优于现有的基于采样、优化和…

transform: rotate 旋转中心在左上角

问题 发现旋转中心在左上角&#xff0c;通过transform-origin修改无效。下面的代码默认一直围绕左上角黑色方块旋转 解决 控制旋转的位置出错了&#xff0c;本来应该是围绕content的中心进行旋转&#xff0c;但是content没有长宽&#xff0c;所以content默认在左上角&#…

华为OD机试 - 寻找最优的路测线路 - Dijkstra算法(Java 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加…

【GNSS】PPPH软件源码解析

注&#xff1a; 1&#xff09;本人在学习PPP过程中&#xff0c;对PPPH软件内所有源码进行了注释&#xff0c;相关理论进行了解析&#xff0c;并通过本文记录&#xff0c;由于是学习记录有些地方注释在了源码上&#xff0c;所以部分理论可能不够详细&#xff0c;请见谅。 2&…

Facebook的虚拟现实计划:未来社交的全新视角

随着科技的不断进步&#xff0c;虚拟现实&#xff08;VR&#xff09;正逐步成为我们日常生活的一部分。作为全球领先的社交平台&#xff0c;Facebook正在大力投入虚拟现实技术&#xff0c;以重新定义社交互动的方式。本文将深入探讨Facebook的虚拟现实计划&#xff0c;分析其如…

在IDEA中如何创建web项目?——不使用Archetype

二、不使用Archetype 1、创建Maven项目 &#xff08;1&#xff09;首先打开Project Structure&#xff1a;File——>Project Structure或者快捷键crtlaltshifts &#xff08;2&#xff09;Module——>New Module&#xff1a; &#xff08;3&#xff09;在新打开的页面下…

三数之和--力扣15

这里写目录标题 题目思路代码 题目 思路 题目要求三元组不能重复&#xff0c;如果使用哈希表来做&#xff0c;去重很复杂&#xff0c;而且需要额外的空间&#xff0c;我们这里使用双指针法直接针对数组操作。注意题目要求返回的是二维数组&#xff01; 最重要的是&#xff0c;…

完整指南:CNStream流处理多路并发框架适配到NVIDIA Jetson Orin (四) 运行、调试、各种问题解决

目录 1 调试jetson-mpeg视频解码模块 1.1 修改config.json 1.2 Picture size 0x0 is invalid 1.3 Process(): Send package failed. Maximum number of attempts reached 1.4 Picture size 2239821608x65535 is invalid 1.5 保存h264文件解码之后的测试图片 1.6 保存RTS…

跨境电商热卖季:选品攻略与实战指南

下半年是跨境电商的旺季 促销节点接踵而至。从感恩节、万圣节、到黑色星期五、网络星期一&#xff0c;再到圣诞节、新年促销等&#xff0c;这些节日不仅激发了消费者的购买欲望&#xff0c;也为跨境电商卖家提供了巨大的市场机遇。那么在这些有望实现销量飞跃的黄金时期&#x…

【SLAM】稀疏矩阵的乘法优化小结

1. 思路小结 要优化你提供的稀疏矩阵乘法代码&#xff0c;我们可以引入CSR&#xff08;压缩稀疏行&#xff09;格式来避免遍历零元素&#xff0c;从而提高效率。CSR格式通过仅存储非零元素以及它们的行和列索引&#xff0c;可以有效减少稀疏矩阵计算时的时间复杂度。下面是对代…

讲解GPU 训练大模型步骤

GPU在训练大模型的工作过程中&#xff0c;扮演着至关重要的角色&#xff0c;其强大的并行计算能力能够显著提升训练速度和效率。以下是GPU训练大模型的详细步骤&#xff1a; 选择合适的GPU和云平台 1. 考虑计算能力 计算能力需求&#xff1a;大模型训练通常需要强大的计算能…

Qt实现登录界面

本文基于Qt实现一个简单的登录界面&#xff0c;主要使用到Widget、button、edit等控件&#xff0c;基于自定义的信号槽实现界面的跳转&#xff0c;使用绘图设备添加背景图等。 1. 创建主界面 设计主界面的样式&#xff0c;并添加相关的控件。如下显示&#xff1a; 代码如下&…

搜索功能技术方案

1. 背景与需求分析 门户平台需要实现对服务信息的高效查询&#xff0c;包括通过关键字搜索服务以及基于地理位置进行服务搜索。面对未来可能的数据增长和性能需求&#xff0c;选择使用 Elasticsearch 来替代 MySQL 的全文检索功能。这一选择的背景与需求可以总结为以下几点&am…

对标世界一流!望繁信科技受邀参加2023企业财务数智化转型论坛

2023年7月21日&#xff0c;由中国CFO发展中心联合浙江省总会计师协会、南京审计大学会计学院、安徽财经大学会计学院举办的“2023企业财务数智化转型论坛&#xff08;长三角站&#xff09;”在上海隆重举办。论坛现场座无虚席&#xff0c;全天候、多维度的话题探讨为广大CFO呈现…

[WEBPWN]BaseCTF week1 题解(新手友好教程版)

WEB A Dark Room 这道题的考点是查看网页源代码 网页源代码这里看到的是网页的html css js在用户浏览器上执行的代码 有时候很多铭感信息&#xff0c;或者关键信息。 查看网页源代码的几种方式 1 右键点击查看网页源代码 2 F12 3 Ctrl U 快捷键 HTTP是什么 HTTP&#x…