日志输出

  • Writer 析构函数
  • Writer::processDispatch 接口
  • Writer::triggerDispatch 接口
  • base::LogDispatcher::dispatch 接口
  • DefaultLogDispatchCallback::handle 接口
  • DefaultLogDispatchCallback::dispatch 接口

在上一篇我们介绍了 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_ENABLEDif (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更新为下一个日志记录器时,需要重新填充对应的streamm_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:: LogDispatcherdispatch 接口:

    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 definedbase::threading::ScopedLock scopedLock(ELPP->lock());#endifbase::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 接口

    DefaultLogDispatchCallbackhandle 接口的实现:

    void DefaultLogDispatchCallback::handle(const LogDispatchData *data){#if defined(ELPP_THREAD_SAFE)// 如果启用线程安全,则检查对应的日志文件是否分配了文件锁,没有分配则分配一个,然后加锁LogDispatchCallback::handle(data);base::threading::ScopedLock scopedLock(fileHandle(data));#endifm_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 priorityint 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;elsesysLogPriority = LOG_NOTICE;#if defined(ELPP_UNICODE)char *line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str());syslog(sysLogPriority, "%s", line);free(line);#elsesyslog(sysLogPriority, "%s", logLine.c_str());#endif}#endif // defined(ELPP_SYSLOG)}

到这里 CLOG 宏写日志的流程就全部分析完了。
前面分析 CLOG 宏写日志的流程中少分析了重要的一块,一般我们使用日志记录宏都是这样使用的:

    CLOG(INFO, "default") << "This is a CLOG!";

这日志的流式操作是如何做到的呢?下一篇将为你揭晓。

easylogging++的那些事(四)源码分析(二)日志记录宏(一)CLOG宏(三)日志输出相关推荐

  1. easylogging++的那些事(四)源码分析(二)日志记录宏(一)CLOG宏(一)宏展开

    CLOG 宏展开 Info 日志宏 CLOG(INFO, xxx) Trace 日志宏 CLOG(TRACE, XXX) Debug 日志宏 CLOG(DEBUG, XXX) Fatal 日志宏 CL ...

  2. 【投屏】Scrcpy源码分析二(Client篇-连接阶段)

    Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...

  3. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  4. gSOAP 源码分析(二)

    gSOAP 源码分析(二) 2012-5-24 flyfish 一 gSOAP XML介绍 Xml的全称是EXtensible Markup Language.可扩展标记语言.仅仅是一个纯文本.适合用 ...

  5. Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现

    写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...

  6. SpringBoot源码分析(二)之自动装配demo

    SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...

  7. Nouveau源码分析(二):Nouveau结构体的基本框架

    Nouveau源码分析(二) 在讨论Nouveau对Nvidia设备的初始化前,我准备先说一下Nouveau结构体的基本框架 Nouveau的很多结构体都可以看作是C++中的类,之间有很多相似的东西, ...

  8. ENS最新合约源码分析二

    ENS(以太坊域名服务)智能合约源码分析二 0.简介 ​ 本次分享直接使用线上实际注册流程来分析最新注册以太坊域名的相关代码.本次主要分析最新的关于普通域名注册合约和普通域名迁移合约,短域名竞拍合约不 ...

  9. 【转】ABP源码分析二十四:Notification

    NotificationDefinition: 用于封装Notification Definnition 的信息.注意和Notification 的区别,如果把Notification看成是具体的消息 ...

最新文章

  1. 520,花一夜给女神写走迷宫游戏
  2. 基于visual Studio2013解决C语言竞赛题之1070删除相同节点
  3. 前端学习(1529):钩子函数--文档分析
  4. 并发无锁队列学习(单生产者单消费者模型)
  5. 深度学习《CGAN新认识》
  6. 喝什么汤对肝脏有好处?
  7. [转]Oracle字符串拼接的方法
  8. java统计计数_java – 使用LongAdder计算统计计数器的最大值?
  9. Word排版的正确姿势!(Word论文排版教学)
  10. Ubuntu 最好用的CHM阅读器KchmViewer
  11. win7网络不显示共享计算机,Win7电脑已开启共享却找不到设备 局域网显示空白该怎么解决...
  12. xiecheng_spider携程民宿爬虫
  13. HDUOJ 4513 吉哥系列故事——完美队形II
  14. php 计算工龄,excel如何计算工龄
  15. 【C语言】——计算单词个数
  16. 面试连环炮之Mysql
  17. CISAW证书发证机构是哪里?权威性如何?
  18. 静态网页项目部署到云服务器上
  19. linux驱动开发篇(三)—— 总线设备驱动模型
  20. Xilinx_RAM_IP核的使用

热门文章

  1. Push推送的评估方法
  2. SAP MM 特殊库存标记J
  3. 南京邮电大学操作系统复试
  4. 30而立的程序员思想
  5. 智策网要慎重对待长期抱团已经涨幅巨大的高位医疗股
  6. 2021年度总结:学编程的第一年
  7. 东南大学计算机考研资料汇总
  8. 2022年全球市场维生素贴片总体规模、主要生产商、主要地区、产品和应用细分研究报告
  9. Speedup:专为项目下Library project过多所设计的加速插件
  10. ParseError: Unrecognised input. Possibly missing something