为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 c1024 = {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 c1024 = {0}; \

snprintf(c, 1024, logmsgformat, ##VA_ARGS); \

logger.Log(c); \

} while(0) \

相关推荐
黑桃小柒712 小时前
小,最终显示为TCP WINDOW FULL,TCP ZeroWindow。 仔细分析了下LWIP源码,还以为是内存管理出了问题,跟 ...
网络·网络协议·tcp/ip
网络研究院13 小时前
LastPass 发布紧急安全预警:针对主密码的活跃网络钓鱼攻击正在进行中
网络·安全·黑客·攻击·漏洞·风险·钓鱼
便利店102414 小时前
TCP 为什么有时故意跑不快?拥塞控制与滑动窗口一次讲清
服务器·网络协议·tcp·拥塞控制
向夏威夷 梦断明暄15 小时前
【高效管理token成本】OpenClaw精细化分库管理memory以减少token成本的方案的可行性研究
网络
酷可达拉斯17 小时前
Linux操作系统-免密登录远程操作系统
linux·运维·服务器·网络·云计算
aixingkong92118 小时前
AI超节点Scale Up域总线各层优化设计
linux·服务器·网络
组合缺一20 小时前
Solon 的 10 种 HTTP 服务器:改一行依赖,换一个引擎
java·服务器·网络协议·http·solon
瓦学妹20 小时前
为什么您的AI总是显示“不支持的区域”?如何解决?
大数据·网络·人工智能
优化Henry21 小时前
5G站点软件错误告警与资源激活超时两类典型告警分析与处理
网络·笔记·学习·5g·信息与通信
coward9121 小时前
千兆以太网卡(Ethernet)驱动初始化完成,但是无法udhcpc获取ip
服务器·网络·嵌入式硬件·tcp/ip