为RPC服务增加异步日志模块

在实际开发过程中我们不可能将一些错误信息或是提示信息全部打印到屏幕上,而是将这些信息输送到日志中去。但是存在着一个问题,写日志是在磁盘中写日志,是磁盘的io操作,效率会很慢,致使项目的效率降低。

为了解决这一个问题我们可以先将需要输出的信息,输送到一个中间件当中,再由中间件单独启动一个线程去写磁盘文件,这样不会影响整个项目的效率。这也就是设计一个缓冲队列来先接收日志信息,再由缓冲队列写到磁盘中。
同样,即使这样在很大程度上提高了效率,但是仍有很大的问题,我们的设计是基于muduo库,他具有epoll+多线程的特点。因此在往消息队列写日志的时候一定要考虑线程安全问题,在前面的文章的也提到了为什么要保证线程安全(每个线程分配的CPU时间片是有限的,假设第一个线程读到了数据,但时间片已经到了,第二个线程拿到了CPU的时间片进行了文件的写操作,当再次到第一个线程的时间片时,他会将读到的数据写入,将覆盖第二个线程写的数据,导致了数据的错误),所以在日志模块中我们要加锁,来保证线程的安全。即在往日志队列中放数据时我们要保证线程安全,并且在将日志队列中的数据pop出来时同样需要拿到锁 ,因为这涉及到对同一资源的访问。并且当队列为空时我们不能取数据,这涉及到了线程间的通信,在C++中我们使用条件变量来进行通信,即在队列为空时,需要进行等待操作。

下面为日志模块:

cpp 复制代码
Logger& Logger::GetInstance()
{
    static Logger logger;
    return logger;
}

Logger::Logger()
{
    // 启动专门的写日志线程
    std::thread writeLogTask([&](){
        for (;;)
        {
            // 获取当前的日期,然后取日志信息,写入相应的日志文件当中 a+
            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;
}

// 写日志, 把日志信息写入lockqueue缓冲区当中
void Logger::Log(std::string msg)
{
    m_lckQue.Push(msg);
}
cpp 复制代码
#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;
};

我们可以通过定义宏来简化使用日志的操作:

#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) \

相关推荐
幽兰的天空9 小时前
介绍 HTTP 请求如何实现跨域
网络·网络协议·http
lisenustc9 小时前
HTTP post请求工具类
网络·网络协议·http
心平气和️9 小时前
HTTP 配置与应用(不同网段)
网络·网络协议·计算机网络·http
心平气和️9 小时前
HTTP 配置与应用(局域网)
网络·计算机网络·http·智能路由器
Gworg9 小时前
网站HTTP改成HTTPS
网络协议·http·https
Mbblovey10 小时前
Picsart美易照片编辑器和视频编辑器
网络·windows·软件构建·需求分析·软件需求
北顾南栀倾寒10 小时前
[Qt]系统相关-网络编程-TCP、UDP、HTTP协议
开发语言·网络·c++·qt·tcp/ip·http·udp
GZ_TOGOGO11 小时前
PIM原理与配置
网络·华为·智能路由器
7ACE11 小时前
Wireshark TS | 虚假的 TCP Spurious Retransmission
网络·网络协议·tcp/ip·wireshark·tcpdump
大丈夫立于天地间12 小时前
ISIS基础知识
网络·网络协议·学习·智能路由器·信息与通信