目录
在上一篇我们介绍了 easylogging++的 崩溃处理相关 的内容。今天我们开始分析 easylogging++异步日志的实现。
目前异步日志在 easylogging++当中是实验性功能,不建议在生产环境中使用,而且经过测试,由同步日志直接切换为异步日志,程序会出现崩溃的情况。
今天我们仅仅看看 easylogging++异步日志目前的实现机制。
异步日志是什么?
前面我们介绍的写日志的方式在未定义
ELPP_EXPERIMENTAL_ASYNC
宏的情况下,都是同步日志。
1)同步日志
:当需要写出一条日志消息时,只有等到这条日志消息完全写出时才能执行后续的流程,其问题在于可能会阻塞在磁盘写操作上;
2)异步日志
:当需要写日志消息时,只是将日志消息进行存储,当积累到一定量时或者达到时间间隔后,由后台线程自动将存储的所有日志进行实际的磁盘写操作;
综上所述,异步日志的好处是前台线程不会阻塞在写日志上,后台线程真正写日志时,日志消息往往已经积累了很多,此时只需一次 IO 操作( easylogging++这里还是一条条写日志 ),从而减少了 IO 函数的调用次数,提高了效率。
在 easylogging++的 总体设计 和 主流程 中我们简单介绍过异步日志相关的内容。
异步日志相关的类
AsyncLogItem 类
AsyncLogItem
表示异步日志队列 AsyncLogQueue 中的一条日志,AsyncLogQueue
类后面会进行介绍。
AsyncLogItem
的实现如下:class AsyncLogItem { public: explicit AsyncLogItem(const LogMessage &logMessage, const LogDispatchData &data, const base::type::string_t &logLine) : m_logMessage(logMessage), m_dispatchData(data), m_logLine(logLine) {} virtual ~AsyncLogItem() {} inline LogMessage *logMessage(void) { return &m_logMessage; } inline LogDispatchData *data(void) { return &m_dispatchData; } inline base::type::string_t logLine(void) { return m_logLine; } private: LogMessage m_logMessage; LogDispatchData m_dispatchData; base::type::string_t m_logLine; // 经过LogBuilder替换日志格式指示器为实际的内容后的日志内容 };
LogMessage
在CLOG
宏 其他相关类 中已经介绍过了。
AsyncLogItem
类的实现并不复杂,这里就不多说了。
AsyncLogQueue 类
AsyncLogQueue
类表示异步日志队列。
AsyncLogQueue
的实现如下:class AsyncLogQueue : public base::threading::ThreadSafe { public: virtual ~AsyncLogQueue() { ELPP_INTERNAL_INFO(6, "~AsyncLogQueue"); } inline AsyncLogItem next(void) { base::threading::ScopedLock scopedLock(lock()); AsyncLogItem result = m_queue.front(); m_queue.pop(); return result; } inline void push(const AsyncLogItem &item) { base::threading::ScopedLock scopedLock(lock()); m_queue.push(item); } inline void pop(void) { base::threading::ScopedLock scopedLock(lock()); m_queue.pop(); } inline AsyncLogItem front(void) { base::threading::ScopedLock scopedLock(lock()); return m_queue.front(); } inline bool empty(void) { base::threading::ScopedLock scopedLock(lock()); return m_queue.empty(); } private: std::queue<AsyncLogItem> m_queue; };
AsyncLogQueue
就是个线程安全的std::queue
。
AsyncLogDispatchCallback 类
启用异步日志时,默认的
LogDispatchCallback
为AsyncLogDispatchCallback
,主要工作:日志需要写终端则写终端,日志需要写文件,则放入异步日志队列。
AsyncLogDispatchCallback
类的实现如下:class AsyncLogDispatchCallback : public LogDispatchCallback { protected: void handle(const LogDispatchData *data); }; void AsyncLogDispatchCallback::handle(const LogDispatchData *data) { // logLine:经过LogBuilder替换日志格式指示器为实际的内容后的日志内容 base::type::string_t logLine = data->logMessage()->logger()->logBuilder()->build(data->logMessage(), data->dispatchAction() == base::DispatchAction::NormalLog); // 先判断是否需要终端输出,如果需要,则根据需要(调整彩色显示),然后终端输出 if (data->dispatchAction() == base::DispatchAction::NormalLog && data->logMessage()->logger()->typedConfigurations()->toStandardOutput(data->logMessage()->level())) { if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput)) data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, data->logMessage()->level()); ELPP_COUT << ELPP_COUT_LINE(logLine); } // 需要写文件,则放进队列 // Save resources and only queue if we want to write to file otherwise just ignore handler if (data->logMessage()->logger()->typedConfigurations()->toFile(data->logMessage()->level())) { ELPP->asyncLogQueue()->push(AsyncLogItem(*(data->logMessage()), *data, logLine)); } }
LogBuilder
在CLOG
宏 日志输出 中已经介绍过了。
TypedConfigurations
类在 日志格式配置管理类 中已经介绍过了。
IWorker 类
异步日志调度器的抽象类,只能被继承,派生类需要实现
start
接口。
IWorker
的实现如下:class IWorker { public: virtual ~IWorker() {} virtual void start() = 0; };
AsyncDispatchWorker 类
AsyncDispatchWorker
类是 easylogging++默认的异步日志调度器,主要职责:从异步日志队列中取出日志,执行真正的写日志动作。
AsyncDispatchWorker
的声明如下:class AsyncDispatchWorker : public base::IWorker, public base::threading::ThreadSafe { public: AsyncDispatchWorker(); virtual ~AsyncDispatchWorker(); bool clean(void); void emptyQueue(void); virtual void start(void); void handle(AsyncLogItem *logItem); void run(void); void setContinueRunning(bool value) { base::threading::ScopedLock scopedLock(m_continueRunningLock); m_continueRunning = value; } bool continueRunning(void) const { return m_continueRunning; } private: std::condition_variable cv; bool m_continueRunning; // 控制轮询清空日志队列的线程是否退出的标志 base::threading::Mutex m_continueRunningLock; };
构造函数
AsyncDispatchWorker::AsyncDispatchWorker() { setContinueRunning(false); }
析构函数
AsyncDispatchWorker::~AsyncDispatchWorker() { setContinueRunning(false); ELPP_INTERNAL_INFO(6, "Stopping dispatch worker - Cleaning log queue"); clean(); ELPP_INTERNAL_INFO(6, "Log queue cleaned"); }
启动异步日志
// 开启一个线程(线程处理函数run)处理日志队列 void AsyncDispatchWorker::start(void) { base::threading::msleep(5000); // 5s (why?) setContinueRunning(true); std::thread t1(&AsyncDispatchWorker::run, this); t1.join(); } // 线程处理函数 void AsyncDispatchWorker::run(void) { while (continueRunning()) { emptyQueue(); base::threading::msleep(10); // 10ms } } // 依次取出每条日志,调用handle接口处理 void AsyncDispatchWorker::emptyQueue(void) { while (!ELPP->asyncLogQueue()->empty()) { AsyncLogItem data = ELPP->asyncLogQueue()->next(); handle(&data); base::threading::msleep(100); } }
handle
接口在后面会详细分析。写日志
// 处理一条日志 void AsyncDispatchWorker::handle(AsyncLogItem *logItem) { LogDispatchData *data = logItem->data(); LogMessage *logMessage = logItem->logMessage(); Logger *logger = logMessage->logger(); base::TypedConfigurations *conf = logger->typedConfigurations(); // logLine:经过LogBuilder替换日志格式指示器为实际的内容后的日志内容 base::type::string_t logLine = logItem->logLine(); if (data->dispatchAction() == base::DispatchAction::NormalLog) { // 当前日志记录器的对应日志级别需要写文件 if (conf->toFile(logMessage->level())) { // 获取当前日志记录器的对应日志级别对应的日志文件的文件流 base::type::fstream_t *fs = conf->fileStream(logMessage->level()); if (fs != nullptr) { // 文件存在则将日志写文件 fs->write(logLine.c_str(), logLine.size()); if (fs->fail()) { // 写失败则终端输出内部错误信息 ELPP_INTERNAL_ERROR("Unable to write log to file [" << conf->filename(logMessage->level()) << "].\n" << "Few possible reasons (could be something else):\n" << " * Permission denied\n" << " * Disk full\n" << " * Disk is not writable", true); } else { // 写成功时,检查是否需要立即刷新(配置了LoggingFlag::ImmediateFlush或者当前日志记录器的对应级别的未刷新次数达到刷新的阈值) if (ELPP->hasFlag(LoggingFlag::ImmediateFlush) || (logger->isFlushNeeded(logMessage->level()))) { logger->flush(logMessage->level(), fs); } } } else { // 文件不存在,则控制台输出内部错误信息 ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(logMessage->level()) << "] " << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: " << logger->id() << "]", false); } } } #if defined(ELPP_SYSLOG) // 如果定义了ELPP_SYSLOG宏,则判断日志类型是否为SysLog日志 // 如果是,判断SysLog日志的级别,进行相关的SysLog日志输出 else if (data->dispatchAction() == base::DispatchAction::SysLog) { // Determine syslog priority int sysLogPriority = 0; if (logMessage->level() == Level::Fatal) sysLogPriority = LOG_EMERG; else if (logMessage->level() == Level::Error) sysLogPriority = LOG_ERR; else if (logMessage->level() == Level::Warning) sysLogPriority = LOG_WARNING; else if (logMessage->level() == Level::Info) sysLogPriority = LOG_INFO; else if (logMessage->level() == Level::Debug) sysLogPriority = LOG_DEBUG; else sysLogPriority = LOG_NOTICE; #if defined(ELPP_UNICODE) char *line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str()); syslog(sysLogPriority, "%s", line); free(line); #else syslog(sysLogPriority, "%s", logLine.c_str()); #endif } #endif // defined(ELPP_SYSLOG) }
handle
接口处理日志的流程与同步日志真正写日志的流程类似。
同步日志真正写日志的流程已经在CLOG
宏 日志输出 中DefaultLogDispatchCallback::dispatch
接口详细分析过了,这里就不多说了。清空异步日志队列
// 带条件变量判断是否满足日志队列非空,条件满足则调用emptyQueue清空队列,然后发出通知 bool AsyncDispatchWorker::clean(void) { std::mutex m; std::unique_lock<std::mutex> lk(m); cv.wait(lk, [] { return !ELPP->asyncLogQueue()->empty(); }); emptyQueue(); lk.unlock(); cv.notify_one(); return ELPP->asyncLogQueue()->empty(); }
至此,异步日志相关的内容就介绍完了。
前面提过 easylogging++默认的日志回滚只能备份文件,但总是会清空当前的日志文件。
日常项目中的日志回滚一般是当日志文件达到一定的条件后,比如文件大小达到阈值或者超过了一定的时间,比如 24 小时,这时候我们会重新生成一个新的日志文件,原日志文件一般保持不变。
这样 easylogging++的默认实现显然不符合真实项目的需求,因此日志回滚的功能我们就需要根据实际的项目需求定制一下了。下一篇我们就来看看如何定制日志回滚以满足真实项目的需求。
标签:异步,++,void,easylogging,源码,base,logMessage,日志,data From: https://www.cnblogs.com/DesignLife/p/16964781.html