EOL总结:日志系统

1、可变变量的宏定义

#define logInfo(format, ...) Log::instance().info(format, ##__VA_ARGS__);
  • logInfo(format, ...)...是可变参数列表,可以传递任意数量的额外参数;
  • info(format, ##__VA_ARGS__)##__VA_ARGS__ 用于展开可变参数列表。
    ## 操作符用于连接 2 个标记(token),当 __VA_ARGS__为空时,##会删除前面的逗号,以避免语法错误。
    logInfo("Hello, %s", "world") => Log::instance().info("Hello, %s", "world")logInfo("Just a message") => Log::instance().info("Just a message")

2、通过时钟库的系统时钟函数获取当前时间

#include <iostream>  
#include <chrono>  
#include <ctime>  
int main() {  
    auto now = std::chrono::system_clock::now(); // 获取当前时间  
    std::time_t tt = std::chrono::system_clock::to_time_t(now); // 转换为 std::time_t  
    std::cout << std::ctime(&tt) << std::endl; // 打印可读的日期和时间  
    return 0;  
}

3、文件对象 file_ex

  • file_ex.h: 重点是用到了文件流对象 std::fstream
#include <fstream>
class FileEx
{
public:
  FileEx(const std::string &pathName, std::ios_base::openmode openFlag);
public:
  std::int64_t writeFile(const std::string &data);
  void flushFile();
private:
  std::string m_path_name;
  std::fstream m_fstream;
}
  • file_ex.cpp
    • 打开文件: m_fstream.open(pathName, openFlag);
    • 查询文件打开状态:return m_fstream.is_open();
    • 关闭文件:m_fstream.close();
    • 变量数据写入文件流:m_fstream << data;
FileEx::FileEx(const std::string &pathName, std::ios_base::openmode openFlag)
{
  static_cast<void>(openFile(pathName, openFlag));
}
std::int64_t writeFile(const std::string &data)
{
  m_fstream << data;
  return static_cas<std::int64_t>(data.size());
}

void FileEx::flushFile()
{
  static_cast<void>(m_fstream.flush());
}

4、LogImplement

实现异步写日志机制,设置文件最大大小,及保存日期,文件保存个数。异步写日志,需要依赖存储日志消息的消息缓冲队列、互斥量、条件变量。
1)同步写日志:将日志直接输出到控制台或文件;
2)异步写日志:日志放到输出线程,线程再把日志输出到文件

  • log_implement.h
struct LogConfig
{
  // true:将日志直接输出到控制台或文件;
  // false:日志放到输出线程,线程再把日志输出到文件
  bool synchro_write = false;  
  std::int32_t max_file_size = 5 * 1024 * 1024;
  std::int32_t remain_days = 90;
}

class LogImplement final
{
...
private:
  std::mutex m_mutex;
  std::condition_variable m_condition;
  std::vector<std::shared_ptr<LogMessage>> m_log_queue;
}
  • log_implement.cpp
  • 对于异步写日志系统,写动作是将日志消息保存到消息缓冲队列,然后通过条件变量通知线程将日志消息写入文件。
void LogImplement::put(const std::string &level, const char *format, va_list &list)
{
  // 写日志到消息缓冲队列,需要加锁
  std::unique_lock<std::mutex> lock(m_mutex);
  ...
  if (m_config.synchro_write)
  {
    writeFile(buffer, MAX_ONE_LOG_LENGTH, logMessagePtr);
  }
  // 异步写
  else
  {
    m_log_queue.push_back(logMessagePtr);
    m_condition.notify_one();
  }
}
  • 创建多线程,由线程将日志消息写入文件。

参考链接:https://blog.csdn.net/sjc_0910/article/details/118861539 # C++11 多线程(std::thread)详解

1)std::thread(&LogImplement::run, this):线程的入口函数为 run(),将当前 LogImplement 对象作为参数传给线程,若变量 m_run 为 false,线程退出循环并结束线程。
2)th.detach():将子线程与父线程分离,父线程无需管理子线程生命周期,若子线程退出运行函数,会自动销毁。

void LogImplement::startThread()
{
  m_run = true;
  std::thread th = std::thread(&LogImplement::run, this);
  th.detach();
}
  • 若父线程有日志需写,会将待写日志放到消息缓冲队列,并通过条件变量通知子线程将日志消息写到文件中。
    • m_condition.wait_for(lock, std::chrono::milliseconds(100)):若子线程获得锁,即对消息缓冲队列有访问权,或等待超过一定时间,子线程将会被唤醒。
void LogImplement::run()
{
  ...
  while (m_run)
  {
    {
       std::unique_lock<std::mutex> lock(m_mutex);
       m_condition.wait_for(lock, std::chrno::milliseconds(100));
      
       while (!m_log_queue.empty())
       {
         queue.swap(m_log_queue);
       }
    }
    ...
  }
}

5、日志系统核心点梳理

参考链接1:https://mp.weixin.qq.com/s/IWAlPzVDkR2ZRI5iirEfCg # 最新版Web服务器项目详解 - 09 日志系统(上)

5.1 基础知识

  • 生产者-消费者模型:生-消模型是并发编程中常见的同步模型,为了实现数据同步,生产者与消费者共享一个缓冲区,生产者往缓冲队列写,消费者往缓冲队列读;
  • 同步日志:每次处理日志写操作,程序需等待日志写入文件才能往下执行。若单条日志较大时,同步模式会阻塞整个日志处理流程;
  • 异步日志:将待写日志内容写入消息缓冲队列,由另一线程从阻塞队列中取出内容,写入日志;
  • 单例模式:保证一个类只有一个实例。
5.1.1 单例模式

单例模式有 2 种实现方式,分别是懒汉模式饿汉模式

  • 懒汉模式是在创建类时不创建对象,运行的时候才创建对象。优点是加载类时比较快,缺点是线程不安全,为了实现线程安全,需要用局部静态变量实现;
  • 饿汉模式是在创建类时就创建对象,缺点是加载类时较慢,优点是天然线程安全。

懒汉模式要是用线程安全的懒汉模式,即使用局部静态变量,为了线程安全,返回的是静态对象的引用或指针。

class Log final
{
public:
  static Log &instance();
  ... 
};

Log& Log::instance()
{
  static Log log;
  return &log;
}
posted @ 2024-05-30 11:27  MasterBean  阅读(30)  评论(0)    收藏  举报