logger日志系统

设计logger日志系统

在这里插入图片描述
图中画圆圈的是我们实现的mprpc框架,这个框架是给别人使用的,把本地的服务发布成远程的RPC服务,框架里最重要的两个成员就是RpcProvider和RpcChannel,他们在运行的过程中会有很多正常的输出信息和错误的信息,我们不可能都cout它们到屏幕上,因为运行时间长了,屏幕上输出的信息特别多,如果万一有什么问题,我们也不好定位,真正用起来的话不方便。所以,一般出问题,我们最直接的方式就是看日志。

日志可以记录正常软件运行过程中出现的信息和错误的信息,当我们定位问题,就打开相应的日志去查看,查找。

假如我们在框架后边输出一个日志模块,我们想把框架运行中的正常的信息和错误信息都记录在日志文件,该怎么做?

左边的两个箭头表示RPC的请求和RPC的响应。 RPC请求过来的时候,我们的框架在执行的时候会产生很多日志文件,我们要写日志,写日志信息的过程是磁盘I/O,磁盘I/O速度不快,我们不能把磁盘I/O的花销算在RPC请求的业务执行部门里面(否则造成RPC请求处理的效率慢),所以,一般在我们的服务器中,我们把日志写在一个缓存队列里(这个队列相当于异步的日志写入机制),我们的框架做的就是写日志到内存的队列里面,不做磁盘I/O操作。然后我们在后面有一个专门写日志的线程,就是做磁盘I/O操作,从队列头取出日志信息,然后把日志信息写到日志文件中,这样它的磁盘I/O就不会算在我们的RPC请求的业务当中。
在这里插入图片描述
我们的mprpc框架的RpcProvier端是用muduo库实现的,采用的是epoll加多线程,很有可能RPC的处理过程是在多个线程中都会去做这个,多个线程都会去写日志,也就是多个线程都会在这个缓存队列里面添加数据,所以我们的这个缓存队列必须保证线程安全。

我们的C++中有queue这个队列,也就是C++的容器,但是C++容器只考虑使用应用,没有考虑线程安全,所以我们用的是线程互斥机制来维护入队出队的线程按照,写日志的线程也是一个独立的线程,用唯一的互斥锁实现。如果队列是空的话,也就是之前的日志都写到日志文件了,写日志的线程这时就不用抢这把互斥锁了,因为没有东西可写。

在这里插入图片描述
所以,我们还要处理的就是线程间的通信。
队列如果是空,写日志的线程就一直等待,队列不空的时候,写日志的线程才有必要去抢锁,把日志信息写到日志文件。
我们的日志文件放在当前目录下log。
我们的日志文件如果容量太大,比如说超过20M,就会产生新的!

在这里插入图片描述

实现logger日志系统

lockqueue.h

#pragma once

#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>

//用于实现异步写日志的日志队列类
template <typename T>
class LockQueue
{
public:
    //多个worker线程都会写日志queue
    void Push(const T &data)
    {
        std::lock_guard<std::mutex> lock(m_mutex); //获取锁,函数结束自动释放锁
        m_queue.push(data);                        //将数据压入队列
        m_condvariable.notify_one();               //唤醒一个线程
    }

    //一个线程读日志queue,写日志文件
    T Pop()
    {
        std::unique_lock<std::mutex> lock(m_mutex); //获取锁,函数结束自动释放锁
        while (m_queue.empty())                     //队列为空
        {
            m_condvariable.wait(lock); //线程挂起,阻塞,释放锁
        }

        T data = m_queue.front(); //取队列头
        m_queue.pop();            //弹出队列头元素
        return data;
    }

private:
    std::queue<T> m_queue;                  //队列
    std::mutex m_mutex;                     //互斥锁
    std::condition_variable m_condvariable; //条件变量
};

logger.h

#pragma

#include "lockqueue.h"
#include <string>

//定义宏函数 LOG_INFO("xxx %d %s", 20, "xxxx")
//使用gcc可变参_VA_ARGS__,提供给用户更轻松的使用logger
//snprintf(缓冲区,缓冲区的长度,写的格式化字符串, ##__VA_ARGS__)
//代表了可变参的参数列表,填到缓冲区当中,然后 logger.Log(c)
#define LOG_INFO(logmsgformat, ...)                     \
    do                                                  \
    {                                                   \
        Logger &logger = Logger::GetInstance();         \
        logger.SetLogLevel(INFO);                       \
        char c[1024] = {0};                             \
        snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \
        logger.Log(c);                                  \
    } while (0)

#define LOG_ERR(logmsgformat, ...)                      \
    do                                                  \
    {                                                   \
        Logger &logger = Logger::GetInstance();         \
        logger.SetLogLevel(ERROR);                      \
        char c[1024] = {0};                             \
        snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \
        logger.Log(c);                                  \
    } while (0)

//定义日志级别
enum LogLevel
{
    INFO,  //普通信息
    ERROR, //错误信息
};

//Mprpc框架提供的日志类,采用单例模式实现
class Logger
{
public:
    //初始化并获取日志的单例
    static Logger &GetInstance();
    //设置日志级别
    void SetLogLevel(LogLevel level);
    //写日志
    void Log(std::string msg);

private:
    //记录日志级别
    int m_loglevel;
    //日志缓冲队列
    LockQueue<std::string> m_lckque;
    //构造函数,在构造Logger类时创建一个专门写日志的后台进程
    Logger();
    Logger(const Logger &) = delete;
    Logger(Logger &&) = delete;
};

logger.cc

#include "logger.h"
#include <iostream>
#include <time.h>

//获取日志的单例
Logger &Logger::GetInstance()
{
    static Logger logger;
    return logger;
}

//构造函数,在构造Logger类时创建一个专门写日志的后台进程
Logger::Logger()
{
    std::thread WriteLogTask([&]()
                             {
                                 for (;;)
                                 {
                                     //获取当前的日期
                                     time_t now = time(nullptr);
                                     tm *nowtm = localtime(&now);

                                     //构建文件名,在该文件中写入当天的日志信息
                                     char file_name[128];
                                     sprintf(file_name, "%d-%d-%d-log.txt",
                                             nowtm->tm_year + 1900,
                                             nowtm->tm_mon + 1,
                                             nowtm->tm_mday);

                                     //以追加的方式打开日志文件
                                     FILE *pf = fopen(file_name, "a+");
                                     if (pf == nullptr)
                                     {
                                         std::cout << "logger file: " << file_name << " open error! " << std::endl;
                                         exit(EXIT_FAILURE);
                                     }

                                     //出队列,获取日志队列头中的信息
                                     std::string msg = m_lckque.Pop();

                                     //将时间信息插入到日志队列头的信息中
                                     char time_buf[128] = {0};
                                     sprintf(time_buf, "%d:%d:%d =>[%s]",
                                             nowtm->tm_hour,
                                             nowtm->tm_min,
                                             nowtm->tm_sec,
                                             (m_loglevel == INFO ? "info" : "error"));
                                     msg.insert(0, time_buf);
                                     msg.append("\n"); //添加一个换行符

                                     //将日志队列头中的信息写入到日志文件中
                                     fputs(msg.c_str(), pf);
                                     fclose(pf);
                                 }
                             });

    //将该线程设置为分离线程,相当于一个守护线程,在后台运行写日志的操作
    WriteLogTask.detach();
}

//设置日志级别
void Logger::SetLogLevel(LogLevel level)
{
    m_loglevel = level;
}

//写日志
void Logger::Log(std::string msg)
{
    m_lckque.Push(msg);
}

同步日志和异步日志的优缺点

  1. 同步的执行效率会比较低,耗费时间,但有利于我们对流程进行控制,避免很多不可掌控的意外情况。
  2. 异步的执行效率高,节省时间,但是会占用更多的资源,也不利于我们对进程进行控制。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值