分布式网络通信框架-rpc (八)日志类

日志类

框架在执行的过程中往往需要记录一些中间的调试信息、错误信息等方便开发人员进行调试和纠错。这时便需要一个好用的、能够输出固定格式的日志类。
需要注意的是,写日志的过程是磁盘I/O,效率很慢,我们不能将磁盘I/O花费的时间算在rpc处理请求、发出响应的过程。因此,我们可以使用一个消息队列(缓冲区队列),再单独使用一个线程作为写日志线程,这样异步的来处理所有写日志任务。如下图所示。

关键点

  1. 存放写日志任务的队列必须保证线程安全。因为可能会存在多个线程同时进行写日志。
  2. 为了满足1,可以使用一把互斥锁,但是单单使用一把互斥锁会影响我们写日志进队列这一过程的效率。因为如果队列为空,写日志进log.txt这一过程还在抢锁的话就会影响写日志进队列这一过程的效率。因此我们还要引入进程间的通信,当队列为空时就不让写日志线程参与抢锁了等待即可。(c++中的mutex, condition_variable)
  3. 为了方便用户使用,将方法包装成宏

带锁队列

为了实现一个线程安全的日志类,需要一个线程安全的队列。STL中提供的queue不是线程安全的,我们需要自己实现。
具体的,我们需要保证在push(多个woker线程都会进行写日志)和pop(一个线程读日志并写入log.txt)操作时只有一个线程对队列进行操作。在线程为空时,我们让写日志线程进入等待状态,直到有队列有日志时写日志线程再被唤醒。
具体实现如下:

// lockqueue.h
#pragma once
#include <queue>
#include <thread>
#include <mutex>              // pthread_mutex_t
#include <condition_variable> // pthread_condition_t

// 异步写日志的日志队列
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())
        {
            // 日志队列为空,线程进入wait状态
            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类

logger类需要完成的任务是:
– 设置日志级别:错误信息、日志信息
– 写日志
由于在框架中只需要使用一个logger对象,因此我们将logger类设置成单例模式。

# logger.h
#pragma once
#include "lockqueue.h"
#include <string>
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(const Logger &) = delete;
    Logger(Logger &&) = delete;
};
// 定义宏,用户使用宏会更加的方便使用
// ##__VA_ARGS__用来代表可变长参数的参数列表
#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);

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注