【项目】多设计模式下的同步&&异步日志系统(一)

继完成仿RabbitMq后,日志消息的不规范在,导致在调试的时候非常的麻烦。吸取了之前的经验后,以后要好好的打日志。博主在学习了设计模式后,做了这个日志系统项目。

总体来说,相对简易RabbitMq的实现更加简单。错误也明显少了很多。

项目介绍

本项目主要实现一个日志系统,支持如下的基本功能:

  • 支持多级别打印日志消息
  • 自定义消息格式
  • 支持同步日志和异步日志
  • 支持日志往不同位置写入:标准输出、文件、按大小滚动文件、按时间滚动文件
  • 支持多线程并发写

开发环境

  • Ubantu
  • VsCode
  • g++/gdb
  • Makefile

核心技术

  • 继承和多态
  • 双缓冲区
  • 生产者消费者模型
  • 同步线程池&&异步线程池
  • C++11(多线程、万能引用、完美转发、智能指针、锁)
  • 设计模式(单例、工厂模式、代理模式、建造者模式)

为什么需要日志系统?

  1. 排查问题,光靠gdb不方便
  2. 小概率发生的问题,需要专门的保存起来
  3. 问题出现在客户端,难以模拟出问题,需要将问题上传到服务器,供技术人员排查

本项目的设计思路

  1. 关于日志的内容,希望能包含日志等级、时间、所在文件、行号、日志器名称(将来希望通过日志器名称来快速查找是哪一个组发生的问题)、线程ID(存在多线程并发访问)以及有效载荷(用户输入的消息),将来会设计默认日志等级,低于这个等级的日志都不会被写入。
  2. 同时希望提供一个用户自定义格式化选项,比如用户只想要日志等级、错误原因。也就只输出这俩项。所以本项目将会自定义格式
  3. 消息的输出形式可以由用户自主控制。日志消息可以直接打印到标准输出,也可以存储在文件中。但是存储在文件中,必然导致文件的内容庞大。本项目的设置是利用滚动文件,支持按照文件的大小和文件日期重新创建新文件写入数据
  4. 写数据需要进行IO,如果是数据库IO那么就需要更多的时间。考虑进行异步写数据,将数据放到缓冲区中。有数据就唤醒线程进行IO写。

设计日志等级模块

此模块是枚举等级类型,并且将整形的等级转化为字符串形式

设计的日志等级,后续会维护一个默认日志等级,只有大于默认等级的消息才会被输出。

  • Debug
  • Info
  • Warning
  • Error
  • Fatal
  • OFF


日志消息类

消息类的设计是一个结构体,会整合所有的数据信息。

如果需要的时候,就往消息类中去取出对应的数据。

整合消息的内容

  • 时间
  • 等级
  • 文件名和行号
  • 日志器的名称(方便快速查找是哪个)
  • 用户输入的信息


格式化消息类

常见的日志是按照等级时间文件行号body一系列顺序显示的。但是有时候消息过于详细反而影响我们的需求。

因此本文的设计是对外提供一个格式化器,对格式化器传入你需要的格式就能将消息组织成对应的格式。比如[%d{%H:%M:%S}]%p%m 对应的解析后的格式就是[时间][日志等级][用户输入的消息]。

  • 设计的思路:先对输入的格式字符串进行解析,解析成k v
    •                 比如 %d对应的key就是 d val就是{%H:%M:%S} 。
  • 根据对应的key,找到子格式化类型(放入val)。
  • 子格式化的类型一开始是未知的,这里利用多态,将子格式化指针放到vector中。
  • 等到真正需要格式化输出的时候,调用vector子格式化输出。

抽象子格式化基类

具体某一个类的format,无非就是有参数从msg中取出对应的数据,放到os中

比如等级和线程ID子格式化类

 Formatter格式化器

格式化器首先要进行对输入patten的解析。

解析的规则:

先判断是否为%,否则就是普通字符,直接输出。

遇到%就往后判断是否为 %d %m %t 不为这些 判断是否为 %%如果是也视为普通字符%

再往后解析:是否遇到{ } 如果遇到花括号 ,花括号中的值就是VALUE

同时必须对解析判断正确,如果错误就直接退出

举例:

        bool parsePatten()
        {
            int pos = 0;
            std::vector<PSS> ret;
            while (pos < _patten.size())
            {
                std::string key, val;
                // 普通字符
                if (_patten[pos] != '%')
                {
                    ret.push_back(std::make_pair(key, std::string(1, _patten[pos])));
                    ++pos;
                    continue;
                }
                ++pos;
                // %%
                if (pos < _patten.size() && _patten[pos] == '%')
                {
                    ret.push_back(std::make_pair(key, std::string(1, _patten[pos])));
                    ++pos;
                    continue;
                }

                // 到这里就是%d{} %p的形式
                key += _patten[pos];
                pos++;
                if (_patten[pos] == '{')
                {
                    ++pos;
                    // 不加入左括号
                    while (pos < _patten.size() && _patten[pos] != '}')
                    {
                        val += _patten[pos++];
                    }
                    if (_patten[pos] != '}')
                    {
                        std::cout << "没有匹配的}" << std::endl;
                        return false;
                    }
                    ++pos;
                }
                ret.push_back(std::make_pair(key, val));
            }
            //Debug 用来观察数据的切分
            // for (auto &it : ret)
            // {
            //     if (std::get<0>(it) != "")
            //         std::cout << " key:" << std::get<0>(it) << " ";
            //     if (std::get<1>(it) != "")
            //         std::cout << " val:" << std::get<1>(it) << " ";
            //     std::cout << std::endl;
            // }
            // 遍历ret,创建子格式化
            for (auto &it : ret)
            {
                // 创建子格式化
                if (std::get<0>(it) == "")
                {
                    FormatItemPtr fi(new OtherFormatItem(std::get<1>(it)));
                    _item.push_back(fi);
                }
                else if (std::get<0>(it) != "" && std::get<1>(it) == "")
                {
                    FormatItemPtr fi = GetItem(std::get<0>(it), std::string());
                    _item.push_back(fi);
                }
                else
                {
                    FormatItemPtr fi = GetItem(std::get<0>(it), std::get<1>(it));
                    _item.push_back(fi);
                }
            }
            return true;
        }

格式化的代码

本文主要是对项目的介绍,项目的优点等等。并且实现了简单的类。

下文,将设计日志系统的落地方式、以及组织日志消息,会涉及到多种设计模式。

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深度搜索

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值