easylogging++的那些事(四)源码分析(二)日志记录宏(一)CLOG宏(三)日志输出
在上一篇我们介绍了 writer 对象的创建以及初始化,今天来看看日志输出的流程。
前面我们提到了 CLOG 宏创建的是 el:: base:: Writer
类的临时对象,秘密就在于这里创建的是临时对象。我们知道, C++对象在离开它的作用域的时候会自动析构,进而调用析构函数 。
而这个临时对象的作用域仅仅是这条创建的语句(也就是使用 CLOG 宏的这条语句),当这条语句执行完后,这个临时对象离开了它的作用域,它就会被析构了,从而触发了析构函数的调用。
所有的秘密都在这个析构函数里面,现在我们来看看 el:: base:: Writer
类析构函数的实现:
Writer 析构函数
el:: base:: Writer
的析构函数的定义:virtual Writer::~Writer(void) { processDispatch(); }
析构函数本身很简单,就是调用了另外一个接口
processDispatch
。
Writer::processDispatch 接口
processDispatch
的实现如下:void Writer::processDispatch() { // 启用日志时才需要写日志 #if ELPP_LOGGING_ENABLED if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) { // 支持多日志记录器输出时 bool firstDispatched = false; base::type::string_t logMessage; std::size_t i = 0; do { if (m_proceed) { if (firstDispatched) { // 不是第一个日志记录器处理时,要讲日志写进当前日志记录器的stream当中,因为在m_logger在处理完日志后会将对应的stream清空,m_logger更新为下一个日志记录器时,需要重新填充对应的stream m_logger->stream() << logMessage; } else { firstDispatched = true; if (m_loggerIds.size() > 1) { // 第一个日志记录器处理时,真正有多个日志记录器写日志才需要把日志给临时保存起来,因为在m_logger在处理完日志后会将对应的stream清空,所以这里需要先临时保存起来,一遍清空后,还能获取到对应的日志 logMessage = m_logger->stream().str(); } } // 真正执行写日志的动作 triggerDispatch(); } else if (m_logger != nullptr) { // 当前日志记录器如果不需要处理日志时,也需要清空流 m_logger->stream().str(ELPP_LITERAL("")); // 同时释放在initializeLogger接口中初始化m_logger时加的锁 m_logger->releaseLock(); } if (i + 1 < m_loggerIds.size()) { // 还有下一个日志记录器,将更新m_logger为下一个日志记录器 initializeLogger(m_loggerIds.at(i + 1)); } // 索引i同步更新 } while (++i < m_loggerIds.size()); } else { // 不支持多日志记录器输出时 if (m_proceed) { // 真正执行写日志的动作(m_logger第一次处理日志时不管哪种情况,都为construct(int count, const char* loggerIds, ...)接口的第二个参数,即参数当中的第一个日志记录器) triggerDispatch(); } else if (m_logger != nullptr) { // 当前日志记录器如果不需要处理日志时,也需要清空流 m_logger->stream().str(ELPP_LITERAL("")); // 同时释放在initializeLogger接口中初始化m_logger时加的锁 m_logger->releaseLock(); } } #else // 不启用日志时 if (m_logger != nullptr) { // 当前日志记录器如果不需要处理日志时,也需要清空流 m_logger->stream().str(ELPP_LITERAL("")); // 同时释放在initializeLogger接口中初始化m_logger时加的锁 m_logger->releaseLock(); } #endif // ELPP_LOGGING_ENABLED }
Writer::triggerDispatch 接口
triggerDispatch
的实现如下:void Writer::triggerDispatch(void) { // 捕获所有可能的异常,不让异常抛出日志库,影响正常的业务逻辑 try { if (m_proceed) { if (m_msg == nullptr) { LogMessage msg(m_level, m_file, m_line, m_func, m_verboseLevel, m_logger); // 委托给base::LogDispatcher执行写日志的动作 base::LogDispatcher(m_proceed, &msg, m_dispatchAction).dispatch(); } else { base::LogDispatcher(m_proceed, m_msg, m_dispatchAction).dispatch(); } } if (m_logger != nullptr) { // 处理完日志后,需要清空流 m_logger->stream().str(ELPP_LITERAL("")); // 同时释放在initializeLogger接口中初始化m_logger时加的锁 m_logger->releaseLock(); } // 当前日志的级别为Level::Fatal并且 if (m_proceed && m_level == Level::Fatal并且默认记录FATAL日志时程序会abort这一默认特性没有被禁用 && !ELPP->hasFlag(LoggingFlag::DisableApplicationAbortOnFatalLog)) { // 使用默认日志记录器输出对应的警告日志 base::Writer(Level::Warning, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId) << "Aborting application. Reason: Fatal log at [" << m_file << ":" << m_line << "]; std::stringstream reasonStream; reasonStream << "Fatal log at [" << m_file << ":" << m_line << "]" << " If you wish to disable 'abort on fatal log' please use " << "el::Loggers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog)"; // 在此处终止程序 base::utils::abort(1, reasonStream.str()); } // 当前日志记录器已经处理完这条日志,恢复标志位 m_proceed = false; } catch (std::exception &ex) { // Extremely low memory situation; don't let exception be unhandled. } }
base::LogDispatcher::dispatch 接口
接下来我们看看
base:: LogDispatcher
的实现。
base:: LogDispatcher
的构造函数的定义如下:LogDispatcher(bool proceed, LogMessage* logMessage, base::DispatchAction dispatchAction) : m_proceed(proceed), m_logMessage(logMessage), m_dispatchAction(std::move(dispatchAction)) { }
base:: LogDispatcher
的构造函数仅仅就是初始化了一些成员变量。每个成员的类型的含义前面也解释过了。
接下来我们看看base:: LogDispatcher
的dispatch
接口:void LogDispatcher::dispatch(void) { // 不需要处理日志,直接返回 if (m_proceed && m_dispatchAction == base::DispatchAction::None) { m_proceed = false; } if (!m_proceed) { return; } // 默认未定义ELPP_NO_GLOBAL_LOCK宏,加了全局锁 #ifndef ELPP_NO_GLOBAL_LOCK // see https://github.com/muflihun/easyloggingpp/issues/580 // global lock is turned on by default unless // ELPP_NO_GLOBAL_LOCK is defined base::threading::ScopedLock scopedLock(ELPP->lock()); #endif base::TypedConfigurations *tc = m_logMessage->logger()->m_typedConfigurations; // 日志回旋检查 if (ELPP->hasFlag(LoggingFlag::StrictLogFileSizeCheck)) { tc->validateFileRolling(m_logMessage->level(), ELPP->preRollOutCallback()); } LogDispatchCallback *callback = nullptr; LogDispatchData data; // 依次调用全局所有注册的的日志派发回调(ELPP->m_logDispatchCallbacks)(日志库初始化时默认注册了DefaultLogDispatchCallback,其主要工作就是写日志) for (const std::pair<std::string, base::type::LogDispatchCallbackPtr> &h : ELPP->m_logDispatchCallbacks) { callback = h.second.get(); if (callback != nullptr && callback->enabled()) { data.setLogMessage(m_logMessage); data.setDispatchAction(m_dispatchAction); // LogDispatchCallback类的handle接口执行真正的回调动作,比如写文件或者控制台输出等。 callback->handle(&data); } } }
DefaultLogDispatchCallback::handle 接口
DefaultLogDispatchCallback
的handle
接口的实现:void DefaultLogDispatchCallback::handle(const LogDispatchData *data) { #if defined(ELPP_THREAD_SAFE) // 如果启用线程安全,则检查对应的日志文件是否分配了文件锁,没有分配则分配一个,然后加锁 LogDispatchCallback::handle(data); base::threading::ScopedLock scopedLock(fileHandle(data)); #endif m_data = data; // m_data->logMessage()->logger()获取当前日志对应的日志记录器 // m_data->logMessage()->logger()->logBuilder()获取日志记录器对应的日志构建器 // LogBuilder日志构建器的作用:调整日志,通过其build接口实现,将日志当中的日志格式说明符(如%logger这种)替换为实际的内容,自定义的格式说明符用指定的解析器解析得到对应的内容来替换,需要加换行的加上换行符 // 将替换后的日志调用dispatch接口进行实际的日志写入动作 dispatch(m_data->logMessage()->logger()->logBuilder()->build(m_data->logMessage(), m_data->dispatchAction() == base::DispatchAction::NormalLog)); }
DefaultLogDispatchCallback::dispatch 接口
DefaultLogDispatchCallback::dispatch
接口的实现如下:void DefaultLogDispatchCallback::dispatch(base::type::string_t &&logLine) { // 是用户日志 if (m_data->dispatchAction() == base::DispatchAction::NormalLog) { // 当前日志记录器的对应日志级别需要写文件 if (m_data->logMessage()->logger()->m_typedConfigurations->toFile(m_data->logMessage()->level())) { // 获取当前日志记录器的对应日志级别对应的日志文件的文件流 base::type::fstream_t *fs = m_data->logMessage()->logger()->m_typedConfigurations->fileStream( m_data->logMessage()->level()); if (fs != nullptr) { // 文件存在则将日志写文件 fs->write(logLine.c_str(), logLine.size()); if (fs->fail()) { // 写失败则终端输出内部错误信息 ELPP_INTERNAL_ERROR("Unable to write log to file [" << m_data->logMessage()->logger()->m_typedConfigurations->filename(m_data->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) || (m_data->logMessage()->logger()->isFlushNeeded(m_data->logMessage()->level()))) { m_data->logMessage()->logger()->flush(m_data->logMessage()->level(), fs); } } } else { // 文件不存在,则则控制台输出内部错误信息 ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(m_data->logMessage()->level()) << "] " << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: " << m_data->logMessage()->logger()->id() << "]", false); } } // 当前日志记录器的对应日志级别需要写终端 if (m_data->logMessage()->logger()->m_typedConfigurations->toStandardOutput(m_data->logMessage()->level())) { // 根据是终端否需要彩色输出,对应日志内容做相应调整,最后输出到终端并刷新终端 if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput)) m_data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, m_data->logMessage()->level()); ELPP_COUT << ELPP_COUT_LINE(logLine); } } #if defined(ELPP_SYSLOG) // 如果定义了ELPP_SYSLOG宏,则判断日志类型是否为SysLog日志 // 如果是,判断SysLog日志的级别,进行相关的SysLog日志输出 else if (m_data->dispatchAction() == base::DispatchAction::SysLog) { // Determine syslog priority int sysLogPriority = 0; if (m_data->logMessage()->level() == Level::Fatal) sysLogPriority = LOG_EMERG; else if (m_data->logMessage()->level() == Level::Error) sysLogPriority = LOG_ERR; else if (m_data->logMessage()->level() == Level::Warning) sysLogPriority = LOG_WARNING; else if (m_data->logMessage()->level() == Level::Info) sysLogPriority = LOG_INFO; else if (m_data->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) }
到这里 CLOG
宏写日志的流程就全部分析完了。
前面分析 CLOG
宏写日志的流程中少分析了重要的一块,一般我们使用日志记录宏都是这样使用的:
CLOG(INFO, "default") << "This is a CLOG!";
这日志的流式操作是如何做到的呢?下一篇将为你揭晓。
本文来自博客园,作者:节奏自由,转载请注明原文链接:https://www.cnblogs.com/DesignLife/p/16926783.html