muduo 日志库学习(一)

来源:互联网 发布:2k18欧文捏脸数据 编辑:程序博客网 时间:2024/06/05 14:14

  muduo的日志库由LogStream{.h,.cc}、Logging{.h, .cc}、LogFile{.h, .cc}、AsyncLogging{.h, .cc}组成。这里主要说明一下,这些文件(主要是文件里面对应的类)之间是怎么关联,并协同工作的。

 

        LogStream类里面有一个Buffer成员(一个模板类,并非muduo::Buffer类)。该类主要负责将要记录的日志内容放到这个Buffer里面。包括字符串,整型、double类型(整型和double要先将之转换成字符型,再放到buffer里面)。该类对这些类型都重载了<<操作符。这个LogStream类不做具体的IO操作。以后要是需要这个buffer里的数据,可以调用LogStream的buffer()函数,这个函数返回const Buffer& 


        Logging.h文件定义了logger类,而非Logging类。 Logger类用枚举类型定义了日志等级。

[cpp] view plain copy
  1. enum LogLevel  
  2.   {  
  3.     TRACE,  
  4.     DEBUG,  
  5.     INFO,  
  6.     WARN,  
  7.     ERROR,  
  8.     FATAL,  
  9.     NUM_LOG_LEVELS  
  10.   };  

        在Logging.h文件中,还定义了一系列的宏定义。

[cpp] view plain copy
  1. #define LOG_TRACE if (muduo::Logger::logLevel() <= muduo::Logger::TRACE) \  
  2.   muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream()  
  3. #define LOG_DEBUG if (muduo::Logger::logLevel() <= muduo::Logger::DEBUG) \  
  4.   muduo::Logger(__FILE__, __LINE__, muduo::Logger::DEBUG, __func__).stream()  
  5. #define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \  
  6.   muduo::Logger(__FILE__, __LINE__).stream()  
  7. #define LOG_WARN muduo::Logger(__FILE__, __LINE__, muduo::Logger::WARN).stream()  
  8. #define LOG_ERROR muduo::Logger(__FILE__, __LINE__, muduo::Logger::ERROR).stream()  
  9. #define LOG_FATAL muduo::Logger(__FILE__, __LINE__, muduo::Logger::FATAL).stream()  
  10. #define LOG_SYSERR muduo::Logger(__FILE__, __LINE__, false).stream()  
  11. #define LOG_SYSFATAL muduo::Logger(__FILE__, __LINE__, true).stream()  

        我们使用日志功能就是通过这些宏的。比如

[cpp] view plain copy
  1. LOG_TRACE<<"the server has read the client message";  

        细看这些宏定义,就可以发现其等价于

[cpp] view plain copy
  1. muduo::Logger(__FILE__,__LINE__,muduo::Logger::TRACE,__func__).stream()<<"the server has read the client message";  

        而Logger类返回的stream是一个LogStream对象,于是通过这些宏进行的日志记录将存放到LogStream对象中的一个buffer里面。前面已经说到LogStream函数并不进行实际的IO操作,所以写入LogStream的数据通过buffer()函数获取。

        Logger类还定义了两个函数指针,用于设置日志的输出位置。因为这种设置是基于类的,而不是基于对象,所以自然其有两个静态变量g_output,g_flush分别存放 输出函数和 刷新函数。

[cpp] view plain copy
  1. typedef void (*OutputFunc)(const char* msg, int len);  
  2. typedef void (*FlushFunc)();  
        默认情况下,其是向stdout(即标准输出)进行输出的。
[cpp] view plain copy
  1. void defaultOutput(const char* msg, int len)  
  2. {  
  3.   size_t n = fwrite(msg, 1, len, stdout);  
  4.   //FIXME check n  
  5.   (void)n;  
  6. }  
  7.   
  8. void defaultFlush()  
  9. {  
  10.   fflush(stdout);  
  11. }  
  12.   
  13. Logger::OutputFunc g_output = defaultOutput;  
  14. Logger::FlushFunc g_flush = defaultFlush;  

        此外,这个类还提供了两个静态函数,用于设置静态变量g_output和g_flush的。

[cpp] view plain copy
  1. static void setOutput(OutputFunc);  
  2. static void setFlush(FlushFunc);   

        不用说就能感觉到 这两个函数很有用,因为其可以修改输出函数,进而重定向日志输出,我们可以将之修改为自己写的输出函数,在这个输出函数中,可以向我们想要输出的缓存或者文件进行输出。在muduo中,一般是将日志重定向到AsyncLogging对象中,具体是:先存放到AsyncLogging对象的一个缓存中,然后由AsyncLogging进行异步写入文件中。


        Logger类还有一个特征就是其使用了Impl方法,定义了一个私有的Impl类。这个Impl类有一个LogStream类。同大多数Impl方法一样,Logger类的具体操作由这个Impl类来完成。不过,Logger类中的Impl成员不是一个指针。使用Impl方法的一大原因是为了闭源,不仅仅隐藏实现代码还隐藏类的私有成员函数。但对于muduo这个开源库来说,这没有意义。而且使用指针的话,new的时候需要在堆中申请空间,这无疑会降低运行速度。


        Logger类是间接进行IO的。Logger的析构函数如下:

[cpp] view plain copy
  1. Logger::~Logger()  
  2. {  
  3.   impl_.finish();//只是在buffer中添加文件名和行数。  
  4.   constLogStream::Buffer& buf(stream().buffer());  
  5.    
  6.  g_output(buf.data(), buf.length());  
  7.   if (impl_.level_== FATAL)  
  8.   {  
  9.     g_flush();  
  10.     abort();  
  11.   }  
  12. }  

        在析构函数中,会调用输出函数g_output,把日志数据(已经存放于buf中)输出。

 

 

        现在来说一下Logger类和LogStream类是怎么配合工作的。

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

        LOG_*的宏,创建一个临时匿名Logger对象,临时匿名很重要,因为临时匿名对象是一使用完就马上销毁,调用析构函数。而C++对于栈中的具名对象,先创建的后销毁。这就使得后创建的Logger对象先于先创建的Logger对象销毁。即先调用析构函数将日志输出,这就会使得日志内容反序(具体说是一个由{}包括的块中反序)。使用临时匿名Logger对象的效果就是:LOG_*这行代码不仅仅包含日志内容,还会马上把日志输出。

 

 

        至此,已经完成了一个基本的日志功能,不过还不是异步。