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


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

    为了解决这一个问题我们可以先将需要输出的信息,输送到一个中间件当中,再由中间件单独启动一个线程去写磁盘文件,这样不会影响整个项目的效率。这也就是设计一个缓冲队列来先接收日志信息,再由缓冲队列写到磁盘中。

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

     下面为日志模块:

    1. Logger& Logger::GetInstance()
    2. {
    3. static Logger logger;
    4. return logger;
    5. }
    6. Logger::Logger()
    7. {
    8. // 启动专门的写日志线程
    9. std::thread writeLogTask([&](){
    10. for (;;)
    11. {
    12. // 获取当前的日期,然后取日志信息,写入相应的日志文件当中 a+
    13. time_t now = time(nullptr);
    14. tm *nowtm = localtime(&now);
    15. char file_name[128];
    16. sprintf(file_name, "%d-%d-%d-log.txt", nowtm->tm_year+1900, nowtm->tm_mon+1, nowtm->tm_mday);
    17. FILE *pf = fopen(file_name, "a+");
    18. if (pf == nullptr)
    19. {
    20. std::cout << "logger file : " << file_name << " open error!" << std::endl;
    21. exit(EXIT_FAILURE);
    22. }
    23. std::string msg = m_lckQue.Pop();
    24. char time_buf[128] = {0};
    25. sprintf(time_buf, "%d:%d:%d =>[%s] ",
    26. nowtm->tm_hour,
    27. nowtm->tm_min,
    28. nowtm->tm_sec,
    29. (m_loglevel == INFO ? "info" : "error"));
    30. msg.insert(0, time_buf);
    31. msg.append("\n");
    32. fputs(msg.c_str(), pf);
    33. fclose(pf);
    34. }
    35. });
    36. // 设置分离线程,守护线程
    37. writeLogTask.detach();
    38. }
    39. // 设置日志级别
    40. void Logger::SetLogLevel(LogLevel level)
    41. {
    42. m_loglevel = level;
    43. }
    44. // 写日志, 把日志信息写入lockqueue缓冲区当中
    45. void Logger::Log(std::string msg)
    46. {
    47. m_lckQue.Push(msg);
    48. }
    1. #pragma once
    2. #include
    3. #include
    4. #include // pthread_mutex_t
    5. #include // pthread_condition_t
    6. // 异步写日志的日志队列
    7. template<typename T>
    8. class LockQueue
    9. {
    10. public:
    11. // 多个worker线程都会写日志queue
    12. void Push(const T &data)
    13. {
    14. std::lock_guard lock(m_mutex);
    15. m_queue.push(data);
    16. m_condvariable.notify_one();
    17. }
    18. // 一个线程读日志queue,写日志文件
    19. T Pop()
    20. {
    21. std::unique_lock lock(m_mutex);
    22. while (m_queue.empty())
    23. {
    24. // 日志队列为空,线程进入wait状态
    25. m_condvariable.wait(lock);
    26. }
    27. T data = m_queue.front();
    28. m_queue.pop();
    29. return data;
    30. }
    31. private:
    32. std::queue m_queue;
    33. std::mutex m_mutex;
    34. std::condition_variable m_condvariable;
    35. };

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

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

  • 相关阅读:
    免费小程序商城搭建之b2b2c o2o 多商家入驻商城 直播带货商城 电子商务b2b2c o2o 多商家入驻商城 直播带货商城 电子商务
    python re模块
    DNS域名解析轮询过程图解
    Java架构
    vue3中使用element-plus Notification通知组件内容添加点击自定义事件
    【经验之谈】关于维修电子设备的几点建议和经验
    2022.11.10 vs每次都需要重新配置环境解决方案
    递归回溯实战+思想
    HTML5的新属性
    2022腾讯云年终双十一配置表汇总-云服务器CVM+轻量应用服务器
  • 原文地址:https://blog.csdn.net/weixin_62518305/article/details/141029383