muduo 日志库学习(一)

转载自:

大佬博客

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类用枚举类型定义了日志等级。

enum LogLevel
  {
    TRACE,
    DEBUG,
    INFO,
    WARN,
    ERROR,
    FATAL,
    NUM_LOG_LEVELS
}

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

#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()

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

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分别存放 输出函数和 刷新函数。

typedef void (*OutputFunc)(const char* msg, int len);
typedef void (*FlushFunc)();

默认情况下,其是向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;

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

static void setOutput(OutputFunc);
static void setFlush(FlushFunc); 

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

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

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

Logger::~Logger()
{
  impl_.finish();//只是在buffer中添加文件名和行数。
  constLogStream::Buffer& buf(stream().buffer());
 
 g_output(buf.data(), buf.length());
  if (impl_.level_== FATAL)
  {
    g_flush();
    abort();
  }
}

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

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

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

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

猜你喜欢

转载自blog.csdn.net/u014303647/article/details/88630117