muduo源码学习(11)-日志类封装1

来源:互联网 发布:js 同时满足两个条件 编辑:程序博客网 时间:2024/06/08 07:07

在linux服务器开发中,很少使用gdb调试来找出错误,更多的是使用日志,特别是在多线程的时候,用gdb调试很不方便。日志就是在运行的时候打印一些状态信息。有些日志是写在文件中的。然而日志并不是简单的在程序中写入一些数据到文件中,直接写文件是比较耗时的,更好的方法是先缓存,到一定时候在进行写入。而且,要实现自动化的日志滚动,也就是在服务器不停的运行过程中,日志不可能都写入到一个文件中,更好的方法可以是按日期划分,这样可以更加方便的分析程序的原型状态。有许多开源的日志工具,如java的log4j。muduo中提供了日志的类库。


日志存在几个级别。TRACE,DEBUG,INFO,WARN,ERROR,FATAL。不同的级别决定了会输出怎样程度的日志信息。比如在开发调试过程中,需要详细的了解程序的原型状态,日志级别可能会是DEBUG,发布后,有些日志信息是不需要输出的,那样会耗费更多的资源,这时要调整日志级别,可能是INFO。


日志的使用比较简单,如

LOG_TRACE<<"trace.....";LOG_INFO<<"info......";LOG_DEBUG<<"debug.......";

这些宏在base/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()


LOG_TRACE,LOG_DEBUG,LOG_INFO 会先判断日志级别再决定是否要输出日志信息,使用宏的话会更加高效。使用函数logLevel()获取日志级别

extern Logger::LogLevel g_logLevel;inline Logger::LogLevel Logger::logLevel(){  return g_logLevel;}


级别取值如下

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



在logging.cc中,有

Logger::LogLevel initLogLevel(){  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();
默认是INFO级别。


在使用如

LOG_INFO<<"INFO.....";
的时候,预处理后展开,忽略条件判断,日志的输出实际上就是

muduo::Logger(__FILE__, __LINE__).stream()<<"INFO....";

首先构造了Logger类对象,__FILE,__LINE__是特殊的宏,表示文件和所在行。之后调用的Logger对象的stream()方法,返回一个LogStream对象,之后调用了LogStream的operatr<<()方法。从表面上看就是如此。然而在内部还要复杂一些。Logger类只是用来处理级别的,在内部有一个类Impl,也包含了一个Impl对象,Impl类中有一个LogStream类属性,Impl对象负责组织日志信息的格式,调用LogStream的operator<<()的时候,只是将日志信息存入到了LogStream中的FixBuffer的缓冲区中,等到合适的时机,会将缓冲区中的日志信息输出到指定设备,可以使标准输出,或者是文件。


Logger类:

class Logger{ public: //日志级别  enum LogLevel  {    TRACE,    DEBUG,    INFO,    WARN,    ERROR,    FATAL,    NUM_LOG_LEVELS,  };  // compile time calculation of basename of source file  class SourceFile  {   public://非类型模板//构造函数template<int N>    inline SourceFile(const char (&arr)[N])      : data_(arr),        size_(N-1)    {    //截取data      const char* slash = strrchr(data_, '/'); // builtin function      if (slash)      {        data_ = slash + 1;        size_ -= static_cast<int>(data_ - arr);      }    }//构造函数//截取data    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();//返回Impl中的stream对象的引用  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); private://嵌套类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_;};  Impl impl_;};
有一个impl_对象,Impl中LogStream,
LogStream类

class LogStream : boost::noncopyable{  typedef LogStream self; public: //重定义FixedBuffer,指定缓冲区大小  typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer;//重载<<操作符  self& operator<<(bool v)  {  //将数据添加到缓冲区中    buffer_.append(v ? "1" : "0", 1);    return *this;  }  self& operator<<(short);  self& operator<<(unsigned short);  self& operator<<(int);  self& operator<<(unsigned int);  self& operator<<(long);  self& operator<<(unsigned long);  self& operator<<(long long);  self& operator<<(unsigned long long);  self& operator<<(const void*);  self& operator<<(float v)  {    *this << static_cast<double>(v);    return *this;  }  self& operator<<(double);  // self& operator<<(long double);  self& operator<<(char v)  {    buffer_.append(&v, 1);    return *this;  }  // self& operator<<(signed char);  // self& operator<<(unsigned char);  self& operator<<(const char* v)  {    buffer_.append(v, strlen(v));    return *this;  }  self& operator<<(const string& v)  {    buffer_.append(v.c_str(), v.size());    return *this;  }#ifndef MUDUO_STD_STRING  self& operator<<(const std::string& v)  {    buffer_.append(v.c_str(), v.size());    return *this;  }#endif  self& operator<<(const StringPiece& v)  {    buffer_.append(v.data(), v.size());    return *this;  }//往buffer中追加数据  void append(const char* data, int len) { buffer_.append(data, len); }//获取buffer  const Buffer& buffer() const { return buffer_; }//重置buffer  void resetBuffer() { buffer_.reset(); } private:  void staticCheck();  template<typename T>  void formatInteger(T);//缓冲区对象  Buffer buffer_;  static const int kMaxNumericSize = 32;};
该类主要有一个缓冲区,然后重载了各种类型的operator<<(),将不同数据类型的数据格式化到缓冲区中,数字会先转换成字符串,

缓冲区FixBuffer

template<int SIZE>class FixedBuffer : boost::noncopyable{ public:  FixedBuffer()    : cur_(data_)  {    setCookie(cookieStart);  }  ~FixedBuffer()  {    setCookie(cookieEnd);  }//将buf中数据添加到缓冲区中  void append(const char* /*restrict*/ buf, size_t len)  {    // FIXME: append partially    //如果缓冲区中的可用的字节数>len    if (implicit_cast<size_t>(avail()) > len)    {    //追加到缓冲区中      memcpy(cur_, buf, len);      cur_ += len;    }  }  const char* data() const { return data_; }  //已写的缓存区的长度  int length() const { return static_cast<int>(cur_ - data_); }  // write to data_ directly  //返回开始写的位置  char* current() { return cur_; }  //当前缓冲区可用的空间  int avail() const { return static_cast<int>(end() - cur_); }//增加  void add(size_t len) { cur_ += len; }//重置缓冲区空间  void reset() { cur_ = data_; }//将缓冲区数据置为0  void bzero() { ::bzero(data_, sizeof data_); }  // for used by GDB  //将缓冲区数据的最后一个的下一个置为0,即转换成c风格的字符串  const char* debugString();    void setCookie(void (*cookie)()) { cookie_ = cookie; }  // for used by unit test  //将缓冲区中数据构造为string对象返回  string asString() const { return string(data_, length()); } private: //返回data_最后一个位置的下一个位置  const char* end() const { return data_ + sizeof data_; }  // Must be outline function for cookies.  static void cookieStart();    static void cookieEnd();//函数指针,实际上并没有什么用  void (*cookie_)();    //缓冲区  char data_[SIZE];  //cur指向data_中写入的最后一个字符的下一个位置  char* cur_;};
FixBuffer管理了一个固定长度的字符数组,提供了一些添加数据的操作。


在LOG_INFO<<"info...";后,构造的Logger临时对象会销毁,调用析构函数

Logger::~Logger(){//调用impl_的finish()  impl_.finish();    const LogStream::Buffer& buf(stream().buffer());//调用输出g_output为函数指针  g_output(buf.data(), buf.length());  if (impl_.level_ == FATAL)  {  //刷新输出的缓冲区    g_flush();      abort();  }}

在析构函数中,得到了存放日志的缓冲区,掉头g_output函数指针,有

Logger::OutputFunc g_output = defaultOutput;Logger::FlushFunc g_flush = defaultFlush;

//默认的输出,输出到标准输出上void defaultOutput(const char* msg, int len){  size_t n = fwrite(msg, 1, len, stdout);  //FIXME check n  (void)n;}//默认的刷新,刷新stdoutvoid defaultFlush(){  fflush(stdout);}

因此默认就是将日志信息输出到了标志输出中。