muduo网络库学习笔记(十):base库之 Logging

本文详细介绍了muduo网络库中Logging的工作流程。通过使用LOG_INFO等宏,初始化Logger类并记录文件、行号等信息。日志级别包括TRACE、DEBUG、INFO、WARN、ERROR和FATAL,不同级别对应不同严重程度的事件。当使用LOG_INFO时,只有当日志级别高于INFO时才会输出。日志输出流程涉及Logger、impl、LogStream等多个组件的交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

工作流程

首先使用宏定义写日志

#define LOG_TRACE if (muduo::Logger::logLevel() <= muduo::Logger::TRACE) \
  muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream()
#define LOG_DEBUG if (muduo::Logger::logLevel() <= muduo::Logger::DEBUG) \
  muduo::Logger(__FILE__, __LINE__, muduo::Logger::DEBUG, __func__).stream()
#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \
  muduo::Logger(__FILE__, __LINE__).stream()
#define LOG_WARN muduo::Logger(__FILE__, __LINE__, muduo::Logger::WARN).stream()
#define LOG_ERROR muduo::Logger(__FILE__, __LINE__, muduo::Logger::ERROR).stream()
#define LOG_FATAL muduo::Logger(__FILE__, __LINE__, muduo::Logger::FATAL).stream()
#define LOG_SYSERR muduo::Logger(__FILE__, __LINE__, false).stream()
#define LOG_SYSFATAL muduo::Logger(__FILE__, __LINE__, true).stream()

举例来说,当我们使用 LOG_INFO << "info…" 时,

相当于用__FILE__ (文件的完整路径和文件名)与 LINE(当前行号的整数 )初始化了一个Logger类,

出错时间、线程、文件、行号初始化号,接着将"info..."输入到缓冲区,在Logger生命周期结束时调用析构函数输出错误信息。

if (muduo::Logger::logLevel() <= muduo::Logger::TRACE)

这代表我们使用该LOG_INFO宏时会先进行判断,如果级别大于INFO级别,后面那句不会被执行,也就是不会打印INFO级别的信息。

整个调用过程:Logger --> impl --> LogStream --> operator<< FilxedBuffer --> g_output --> g_flush

 

日志级别

TRACE

指出比DEBUG粒度更细的一些信息事件(开发过程中使用)

DEBUG

指出细粒度信息事件对调试应用程序是非常有帮助的。(开发过程中使用)

INFO

表明消息在粗粒度级别上突出强调应用程序的运行过程。

WARN

系统能正常运行,但可能会出现潜在错误的情形。

ERROR

指出虽然发生错误事件,但仍然不影响系统的继续运行。

FATAL

指出每个严重的错误事件将会导致应用程序的退出。

Logger.h

#ifndef MUDUO_BASE_LOGGING_H
#define MUDUO_BASE_LOGGING_H
​
#include "LogStream.h"
#include "Timestamp.h"
​
namespace muduo{
class TimeZone;
class Logger{
public:
    /*
    级别类型
    muduo使用枚举的方式指出了输出错误信息的形式。
    其中TRACE,DEBUG用于调试,INFO,WARN,ERROR,FATAL在程序运行过程中输出,警告的程度依次上升,
    出现FATAL时程序将强制退出。NUM_LOG_LEVELS是这个枚举结构体中错误形式的数目,为6。
    */
    enum LogLevel{
        TRACE,
        DEBUG,
        INFO,
        WARN,
        ERROR,
        FATAL,
        NUM_LOG_LEVELS,
    };
    /*
    SourceFile类用于在输出信息的时候指明是在具体的出错文件。
    这个类不难,是一个简单的字符封装。它的私有成员有data_和size_。
    在Source的构造函数中,data_会被出错文件的路径初始化,然后经过strrchr处理,
    指向出错文件的basename(如/muduo/base/Thread.cc到Thread.cc)。size_就是basename的长度。
    */
    class SourceFile{
    public:
        template<int N>
        SourceFile(const char (&arr)[N])
            :data_(arr),size_(N-1){
            const char* slash = strrchr(data_, '/'); // builtin function
            if (slash)
            {
                data_ = slash + 1;
                size_ -= static_cast<int>(data_ - arr);
            }
        }
        explicit SourceFile(const char* filename)
            :data_(filename){
            const char* slash=strrchr(filename,'/');
            if(slash){
                data_=slash+1;
            }
            size_=static_cast<int>(strlen(data_));
        } 
        const char* data_;
        int size_;
    };
​
    Logger(SourceFile file, int line);
    Logger(SourceFile file, int line, LogLevel level);
    Logger(SourceFile file, int line, LogLevel level, const char* func);
    Logger(SourceFile file, int line, bool toAbort);
    ~Logger();
​
    LogStream& stream() { return impl_.stream_; }
​
    static LogLevel logLevel();//返回当前日志级别
    static void setLogLevel(LogLevel level);//设置日志级别
​
    typedef void (*OutputFunc)(const char* msg, int len);
    typedef void (*FlushFunc)();
    static void setOutput(OutputFunc);//设置输出函数,用来替代默认的
    static void setFlush(FlushFunc);//用来配套你设置的输出函数的刷新方法
    static void setTimeZone(const TimeZone& tz);
private:
    //Impl类由Logger调用,输出出错信息,封装了Logger的缓冲区stream_
    class Impl{
    public:
        typedef Logger::LogLevel LogLevel;
        Impl(LogLevel level,int old_errno,const SourceFile& file,int line);
        /*formatTime函数的作用是将出错的时间格式化问年-月-日-时-分-秒-毫秒的形式,
        并将格式化的时间字符串输入到stream_的缓冲区中。主要是利用了gmtime_r函数
        */
        void formatTime();
        void finish();
​
        Timestamp time_;//当前时间
        //构造日志缓冲区,该缓冲区重载了各种<<,都是将数据格式到LogStream的内部成员缓冲区buffer里
        LogStream stream_;
        
        LogLevel level_;//级别
        int line_;//行
        SourceFile basename_;//文件
    };
​
    Impl impl_;//logger构造这个对象
};
extern Logger::LogLevel g_logLevel;
​
//返回当前日志级别
inline Logger::LogLevel Logger::logLevel()
{
  return g_logLevel;
}
​
​
//
// CAUTION: do not write:
//
// if (good)
//   LOG_INFO << "Good news";
// else
//   LOG_WARN << "Bad news";
//
// this expends to
//
// if (good)
//   if (logging_INFO)
//     logInfoStream << "Good news";
//   else
//     logWarnStream << "Bad news";
//
/*
在Logger类中定义了一系列宏来实现错误输出。我们使用Logger来输出错误信息时,只需调用这些宏即可。
举例来说,当我们使用LOG_INFO << "info…"时,
相当于用__FILE__ (文件的完整路径和文件名)与 LINE(当前行号的整数 )初始化了一个Logger类,
出错时间、线程、文件、行号初始化号,接着将"infor"输入到缓冲区,在Logger生命周期结束时调用析构函数输出错误信息。
*/
//使用if条件判断,如果当前级别大于TRACE,就相当于没有下面一行代码,不会编译,下同。
#define LOG_TRACE if (muduo::Logger::logLevel() <= muduo::Logger::TRACE) \
  muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream()
#define LOG_DEBUG if (muduo::Logger::logLevel() <= muduo::Logger::DEBUG) \
  muduo::Logger(__FILE__, __LINE__, muduo::Logger::DEBUG, __func__).stream()
#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \
  muduo::Logger(__FILE__, __LINE__).stream()
#define LOG_WARN muduo::Logger(__FILE__, __LINE__, muduo::Logger::WARN).stream()
#define LOG_ERROR muduo::Logger(__FILE__, __LINE__, muduo::Logger::ERROR).stream()
#define LOG_FATAL muduo::Logger(__FILE__, __LINE__, muduo::Logger::FATAL).stream()
#define LOG_SYSERR muduo::Logger(__FILE__, __LINE__, false).stream()
#define LOG_SYSFATAL muduo::Logger(__FILE__, __LINE__, true).stream()
​
}
​
#endif

 

Logger.cpp

#include "Logging.h"
​
#include "CurrentThread.h"
#include "Timestamp.h"
#include "TimeZone.h"
​
#include <errno.h>
#include <stdio.h>
#include <string.h>
​
#include <sstream>
​
namespace muduo
{
​
__thread char t_errnobuf[512];
__thread char t_time[64];
__thread time_t t_lastSecond;
​
const char* strerror_tl(int savedErrno)
{
    return strerror_r(savedErrno, t_errnobuf, sizeof t_errnobuf);
}
//设置日志级别
Logger::LogLevel initLogLevel()
{
    if (::getenv("MUDUO_LOG_TRACE")) //获取TRACE环境变量,如果有,返回它
        return Logger::TRACE;
    else if (::getenv("MUDUO_LOG_DEBUG"))//获取DEBUG环境变量,如果有,返回它
        return Logger::DEBUG;
    else
        return Logger::INFO;//如果它们都没有,就使用INFO级别
}
​
Logger::LogLevel g_logLevel = initLogLevel();
​
const char* LogLevelName[Logger::NUM_LOG_LEVELS] =
{
    "TRACE ",
    "DEBUG ",
    "INFO  ",
    "WARN  ",
    "ERROR ",
    "FATAL ",
};
​
// helper class for known string length at compile time
//编译时获取字符串长度的类
class T
{
 public:
    T(const char* str, unsigned len)
        :str_(str),
        len_(len)
    {
        assert(strlen(str) == len_);
    }
​
    const char* str_;
    const unsigned len_;
};
​
inline LogStream& operator<<(LogStream& s, T v)
{
    s.append(v.str_, v.len_);
    return s;
}
​
inline LogStream& operator<<(LogStream& s, const Logger::SourceFile& v)
{
    s.append(v.data_, v.size_);
    return s;
}
//默认输出内容到stdout
void defaultOutput(const char* msg, int len)
{
    size_t n = fwrite(msg, 1, len, stdout);
    //FIXME check n
    (void)n;
}
 //默认刷新方法
void defaultFlush()
{
    fflush(stdout);
}
​
Logger::OutputFunc g_output = defaultOutput;
Logger::FlushFunc g_flush = defaultFlush; 
TimeZone g_logTimeZone;
}//muduo
​
using namespace muduo;
/*
构造函数中,我们用现在的时间初始化time_,line_是出错处的文件行号,basename就是出错文件的文件名。
初始化接触后,执行formatTime函数,将当前线程号和LogLevel输入stream_的缓冲区。
`saveErrno我们一般传入errno(录系统的最后一次错误代码),若不等于0,将它错误原因输入到缓冲区。
*/
//savedErrno 错误码,没有就传0
Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line)
    : time_(Timestamp::now()),//当前时间
    stream_(),//初始化logger的四个成员
    level_(level),
    line_(line),
    basename_(file)
{
    formatTime();//格式化时间
    CurrentThread::tid();//缓存当前线程id
    //将当前线程号和LogLevel输入stream_的缓冲区
    stream_ << T(CurrentThread::tidString(), CurrentThread::tidStringLength());//格式化线程tid字符串
    stream_ << T(LogLevelName[level], 6);//格式化级别,对应成字符串,先输出到缓冲区
    //将它错误原因输入到缓冲区
    if (savedErrno != 0)
    {
        stream_ << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";
    }
}
//格式化时间
void Logger::Impl::formatTime(){
    int64_t microSecondsSinceEpoch = time_.microSecondsSinceEpoch();
    time_t seconds = static_cast<time_t>(microSecondsSinceEpoch / Timestamp::kMicroSecondsPerSecond);
    int microseconds = static_cast<int>(microSecondsSinceEpoch % Timestamp::kMicroSecondsPerSecond);
​
    if (seconds != t_lastSecond)
    {
        t_lastSecond = seconds;
        struct tm tm_time;
        if (g_logTimeZone.valid())
        {
            tm_time = g_logTimeZone.toLocalTime(seconds);
        }
        else
        {
            /*gmtime_r() 就是将seconds转化成tm_time中保存的信息
            下面一条注释是源代码中的,不知道 FIXME 是代表什么,
            这个处理应该可以用TimeZone::fromUtcTime 来实现,
            */
            ::gmtime_r(&seconds, &tm_time); // FIXME TimeZone::fromUtcTime
        }
​
        int len = snprintf(t_time, sizeof(t_time), "%4d%02d%02d %02d:%02d:%02d",
        tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
        tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
        assert(len == 17); (void)len;
    }
​
    if (g_logTimeZone.valid())
    {
        Fmt us(".%06d ", microseconds);
        assert(us.length() == 8);
        stream_ << T(t_time, 17) << T(us.data(), 8); //用stream进行输出,重载了<<
    }
    else
    {
        Fmt us(".%06dZ ", microseconds);
        assert(us.length() == 9);
        stream_ << T(t_time, 17) << T(us.data(), 9); //用stream进行输出,重载了<<
    }
}
​
void Logger::Impl::finish()   //将名字,行号行输进缓冲区
{
    stream_ << " - " << basename_ << ':' << line_ << '\n';
}
​
Logger::Logger(SourceFile file, int line)
    : impl_(INFO, 0, file, line)
{
}
​
Logger::Logger(SourceFile file, int line, LogLevel level, const char* func)
  : impl_(level, 0, file, line)
{
    impl_.stream_ << func << ' ';//输出格式化函数名称
}
​
Logger::Logger(SourceFile file, int line, LogLevel level)
    : impl_(level, 0, file, line)
{
}
/*
toAbort 是否终止,FATAL这一日志级别会导致应用程序的退出
*/
Logger::Logger(SourceFile file, int line, bool toAbort)
    : impl_(toAbort?FATAL:ERROR, errno, file, line)
{
}
//析构函数中会调用impl_的finish方法
Logger::~Logger()
{
    impl_.finish();//将名字,行数输入缓冲区
    const LogStream::Buffer& buf(stream().buffer());//将缓冲区以引用方式获得
    g_output(buf.data(), buf.length());//调用全部输出方法,输出缓冲区内容,默认是输出到stdout
    if (impl_.level_ == FATAL)
    {
        g_flush();
        abort();
    }
}
​
void Logger::setLogLevel(Logger::LogLevel level)//设置日志级别
{
    g_logLevel = level;
}
​
void Logger::setOutput(OutputFunc out)//设置输出函数,用来替代默认的
{
    g_output = out;
}
​
void Logger::setFlush(FlushFunc flush)//用来配套你设置的输出函数的刷新方法
{
    g_flush = flush;
}
​
void Logger::setTimeZone(const TimeZone& tz)
{
    g_logTimeZone = tz;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值