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;
}

浙公网安备 33010602011771号