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:: 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 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 接口

    DefaultLogDispatchCallbackhandle 接口的实现:

    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!";

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

posted @ 2022-11-26 00:45  节奏自由  阅读(223)  评论(0编辑  收藏  举报