• muduo库的高性能日志库(二)——Logging文件


    接下看一下Logging文件,该文件主要负责全局日志级别,输出目的地设置
    Logger内有两个内部类
    SourceFile,主要负责获取文件名
    Impl,实际用于日志消息的处理类

    SourceFile内部类

    该类就负责获取基文件名
    eg:
    /home/gty/muduo/muduo/base/test.cc
    对应的基文件名就是
    test.cc

    实现很简单,调用了c语言库函数strrchr

    C 库函数char *strrchr(const char *str, int c)
    在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置

      class SourceFile
      {
        //找到基文件的文件名,并将data_指向基文件名的开始,size_存文件名长度
       public:
        template<int N>
        SourceFile(const char (&arr)[N])  //数组引用
          : data_(arr),
            size_(N-1)
        {
          //C 库函数 char *strrchr(const char *str, int c) 
          //在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置
    
          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_;
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    Impl内部类

    该类就是指定了一条日志的具现化

    |----日志打印时间
    |----日志缓存(LogStream)
    |----日志的level
    |----行号
    |----文件名称
    
    • 1
    • 2
    • 3
    • 4
    • 5

    具体如下

    //内部类,实际用于日志消息的处理类
    class Impl
    {
     public:
      typedef Logger::LogLevel LogLevel;
      Impl(LogLevel level, int old_errno, const SourceFile& file, int line);
      //格式化时间戳
      void formatTime();
      void finish();
      Timestamp time_;   //日志时间戳
      LogStream stream_; //日志缓存流
      LogLevel level_;   //日志级别
      int line_;         //当前记录日志宏的源代码名称
      SourceFile basename_;  //当前记录日志宏的源代码名称
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    下面看看该类的实现细节

    内部实现细节

    __thread char t_errnobuf[512];
    __thread char t_time[64];  //当前线程的时间字符串“年:月:日:时:分:秒”
    __thread time_t t_lastSecond;  //当前线程上一次日志记录时的秒数
    
    //Impl的构造函数
    Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line)
      : time_(Timestamp::now()),//登记当前时间
        stream_(),   //Logstream类
        level_(level),  //日志级别
        line_(line),   //行号
        basename_(file)  //文件名称
    {
      formatTime();  //格式化时间
      CurrentThread::tid(); //缓存当前线程tid
    
      //将当前线程id和日志等级名称输出到buf当中
      stream_ << T(CurrentThread::tidString(), CurrentThread::tidStringLength());
      stream_ << T(LogLevelName[level], 6);
      if (savedErrno != 0)  //如果savedErrno不为0,saveErrno为错误号
      {
        //将错误码对应的错误信息存入t_errnobuf当中,并且输入到stream_的buf当中
        stream_ << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";
      }
    }
    
    
    //格式化时间
    void Logger::Impl::formatTime()
    {
      int64_t microSecondsSinceEpoch = time_.microSecondsSinceEpoch();//获取微秒格式时间
      //获得秒,kMicroSecondsPerSecond为1000000
      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
        }
    
        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);
      }
      else
      {
        Fmt us(".%06dZ ", microseconds);
        assert(us.length() == 9);
        stream_ << T(t_time, 17) << T(us.data(), 9);
      }
    }
    
    
    //所有信息输出完毕,然后再将文件名称和行号输出到buf当中
    void Logger::Impl::finish()
    {
      stream_ << " - " << basename_ << ':' << line_ << '\n';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    Logger类

    Logger的两个内部类看完,分析这个类

    1. 规定日志的几个等级

        |----TRACE
        |----DEBUG
        |----INFO
        |----WARN
        |----ERROR
        |----FATAL
        |----NUM_LOG_LEVELS
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    代码如下:

      enum LogLevel
      {
        TRACE,
        DEBUG,
        INFO,
        WARN,
        ERROR,
        FATAL,
        NUM_LOG_LEVELS,
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2. 两个内部类

    |----SourceFile  (文件名截取)
    |----Impl        (实际日志信息处理类)
    
    • 1
    • 2

    3. 设置日志属性

    |----setLogLevel(***)  //设置日志等级
    |----setOutput(***)    //设置输出函数
    |----setFlush(***)     //设置刷新函数
    |----setTimeZone(***)  //设置日期格式
    
    • 1
    • 2
    • 3
    • 4

    代码如下:

      //设置日志级别
      static void setLogLevel(LogLevel level);
      //设置输出函数(默认的输出函数是输出到stdout)
      typedef void (*OutputFunc)(const char* msg, int len);
      //设置刷新函数(默认的刷新函数是刷新到stdout)
      typedef void (*FlushFunc)();
      static void setOutput(OutputFunc);  //默认fwrite到stdout
      static void setFlush(FlushFunc);    //默认fflush到stdout
      static void setTimeZone(const TimeZone& tz);  默认 GMT
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    下面为默认的OutputFuncFlushFunc

    //默认为stdout
    void defaultOutput(const char* msg, int len)
    {
      size_t n = fwrite(msg, 1, len, stdout);
      //FIXME check n
      (void)n;
    }
    
    //默认为stdout
    void defaultFlush()
    {
      fflush(stdout);
    }
    //设置默认的输出和刷新函数
    Logger::OutputFunc g_output = defaultOutput;
    Logger::FlushFunc g_flush = defaultFlush;
    TimeZone g_logTimeZone;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    初始化日志等级

    //初始化日至等级
    Logger::LogLevel initLogLevel()
    {
    
      //getenv()用来取得参数name环境变量的内容。
      //参数name为环境变量的名称,如果该变量存在则会返回指向该内容的指针。
      if (::getenv("MUDUO_LOG_TRACE"))
        return Logger::TRACE;
      else if (::getenv("MUDUO_LOG_DEBUG"))
        return Logger::DEBUG;
      else
        return Logger::INFO;
    }
    
    Logger::LogLevel g_logLevel = initLogLevel();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    4. 构造函数

    有四个重载
    四种不同类型的日志格式

      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);
    
    • 1
    • 2
    • 3
    • 4

    5. 析构函数

    日志打印的基本所有操作都是在析构中进行的

    //主要在析构进行
    Logger::~Logger()
    {
      impl_.finish();
      const LogStream::Buffer& buf(stream().buffer());
      g_output(buf.data(), buf.length());
      if (impl_.level_ == FATAL)
      {
        //如果这是一个FATAL刷新缓冲区,然后直接中断程序
        g_flush();
        abort();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    日志宏

    使用LOG_*之类的宏会创建一个临时匿名Logger对象,这个对象有一个Impl对象,而Impl对象有一个LogStream对象。LOG_*宏会返回一个LogStream对象的引用。用于将内容输入到LogStream中的一个buffer中。

    在Logger的析构函数中,调用 g_output,即 g_output(buf.data(), buf.length()),将存于LogStream的buffer的日志内容输出。如果是FATAL错误,还要调用g_flush,最后abort()程序。如果没有调用g_flush,会一直输出到缓冲区(标准输出缓冲区,文件FILE缓冲区)满才会真的输出在标准输出,或者写入到文件中去。

    //日志宏
    #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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    SpringBoot 实战 开发中 16 条最佳实践
    RHCE红帽认证考试介绍(EX200:RHCSA;EX294:RHCE)
    Kamailio Debian安装
    数据结构 - AVL树
    10 分钟教会你如何看懂 MySQL 执行计划
    【冒泡排序】
    01背包问题 : 二维dp数组 + 图文
    从0到0.01入门 Webpack| 002.精选 Webpack面试题
    混沌反馈共享和群体协同效应的蝴蝶优化算法—附代码
    数据结构笔记(王道考研)第二章:线性表
  • 原文地址:https://blog.csdn.net/m0_61705102/article/details/127911266