zoukankan      html  css  js  c++  java
  • muduo网络库源码学习————日志类封装

    muduo库里面的日志使方法如下

    这里定义了一个宏
    #define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) 
      muduo::Logger(__FILE__, __LINE__).stream()
    返回的stream重载了一系列的运算符,使用方法如下
    LOG_INFO<<“info ...”;   // 使用方式
    上面那句其实就是
    muduo::Logger(__FILE__, __LINE__).stream()<<“info”;
    

    调用过程实际是如下所示的
    Logger => Impl => LogStream => operator<< FixedBuffer => g_output => g_flush
    muduo库里面的日志类封装相关的文件为Logging.h,Logging.cc,LogSrtream.h,LogSrtream.cc,附带一个StringPiece.h文件,几个文件的注解如下所示:
    Logging.h

    //日志输出
    #ifndef MUDUO_BASE_LOGGING_H
    #define MUDUO_BASE_LOGGING_H
    
    #include <muduo/base/LogStream.h>
    #include <muduo/base/Timestamp.h>
    
    namespace muduo
    {
    
    class Logger
    {
     public:
        //日志级别
      enum LogLevel
      {
        TRACE,
        DEBUG,
        INFO,
        WARN,
        ERROR,
        FATAL,
        NUM_LOG_LEVELS,//级别个数6
      };
    
      // 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)
        {
          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);
    
     private:
    //Loger类的内部嵌套类
    //Impl类主要是负责日志的格式化
    class Impl
    {
     public:
      typedef Logger::LogLevel LogLevel;
      //构造函数
      Impl(LogLevel level, int old_errno, const SourceFile& file, int line);
      //格式化时间函数
      void formatTime();
      void finish();
    
      Timestamp time_;//Timestamp时间戳
      LogStream stream_;//LogStream类对象成员
      LogLevel level_;//日志级别
      int line_;//行号
      SourceFile basename_;
    };
    
      Impl impl_;//Impl类对象成员
    
    };
    
    extern Logger::LogLevel g_logLevel;
    
    inline Logger::LogLevel Logger::logLevel()
    {
      return g_logLevel;//日志级别
    }
    //日志输出宏,输出在哪个文件? 哪一行? 哪个函数? 哪种级别?
    //无名对象所在语句执行后就立即被析构,然后调用析构函数将缓冲区的内容分输出  
    // muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream()返回的是一个LogStream对象
    #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()
    //false表示不会退出程序
    #define LOG_SYSERR muduo::Logger(__FILE__, __LINE__, false).stream()//ERROR
    //true表示会退出程序
    #define LOG_SYSFATAL muduo::Logger(__FILE__, __LINE__, true).stream()//FATAL
    
    const char* strerror_tl(int savedErrno);
    
    // Taken from glog/logging.h
    //
    // Check that the input is non NULL.  This very useful in constructor
    // initializer lists.
    
    #define CHECK_NOTNULL(val) 
      ::muduo::CheckNotNull(__FILE__, __LINE__, "'" #val "' Must be non NULL", (val))
    
    // A small helper for CHECK_NOTNULL().
    template <typename T>
    T* CheckNotNull(Logger::SourceFile file, int line, const char *names, T* ptr) {
      if (ptr == NULL) {
       Logger(file, line, Logger::FATAL).stream() << names;
      }
      return ptr;
    }
    
    }
    
    #endif  // MUDUO_BASE_LOGGING_H
    

    Logging.cc

    #include <muduo/base/Logging.h>
    
    #include <muduo/base/CurrentThread.h>
    #include <muduo/base/StringPiece.h>
    #include <muduo/base/Timestamp.h>
    
    #include <errno.h>
    #include <stdio.h>
    #include <string.h>
    
    #include <sstream>
    
    namespace muduo
    {
    
    /*
    class LoggerImpl
    {
     public:
      typedef Logger::LogLevel LogLevel;
      LoggerImpl(LogLevel level, int old_errno, const char* file, int line);
      void finish();
    
      Timestamp time_;
      LogStream stream_;
      LogLevel level_;
      int line_;
      const char* fullname_;
      const char* basename_;
    };
    */
    
    
    __thread char t_errnobuf[512];
    __thread char t_time[32];
    __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"))//如果获取到了这个环境变量
        return Logger::TRACE;//日志级别为TRACE
      else if (::getenv("MUDUO_LOG_DEBUG"))//如果获取到了这个环境变量
        return Logger::DEBUG;//日志级别为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;
    }
    //默认输出
    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;
    
    }
    
    using namespace muduo;
    //Impl类的构造函数
    //级别,错误(没有错误则传0),文件,行
    //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();//缓存当前线程的id
      //输出字符串格式的线程id
      stream_ << T(CurrentThread::tidString(), 6);
      stream_ << T(LogLevelName[level], 6);
      if (savedErrno != 0)//如果savedErrno不为零,还要输出对应的信息
      {
        stream_ << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";
      }
    }
    
    void Logger::Impl::formatTime()
    {
      int64_t microSecondsSinceEpoch = time_.microSecondsSinceEpoch();//获得微秒格式的时间
      time_t seconds = static_cast<time_t>(microSecondsSinceEpoch / 1000000);//获得秒
      int microseconds = static_cast<int>(microSecondsSinceEpoch % 1000000);//微秒
      if (seconds != t_lastSecond)
      {
        t_lastSecond = seconds;
        struct tm tm_time;
        ::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;
      }
      Fmt us(".%06dZ ", microseconds);
      assert(us.length() == 9);
      //LogStream类重载运算符,把信息输出到缓冲区
      stream_ << T(t_time, 17) << T(us.data(), 9);
    }
    
    void Logger::Impl::finish()
    {
      stream_ << " - " << basename_ << ':' << line_ << '
    ';
    }
    //构造函数,都是构造Impl对象
    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,否则为ERROR
    Logger::Logger(SourceFile file, int line, bool toAbort) : impl_(toAbort?FATAL:ERROR, errno, file, line)
    {
    }
    //析构函数
    Logger::~Logger()
    {
      impl_.finish();
      //取出stream类缓冲区的内容放到buf
      const LogStream::Buffer& buf(stream().buffer());
      //输出,g_output有一个默认的输出stdout
      g_output(buf.data(), buf.length());
      if (impl_.level_ == FATAL)//如果是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;
    }
    

    LogSrtream.h

    //日志流
    #ifndef MUDUO_BASE_LOGSTREAM_H
    #define MUDUO_BASE_LOGSTREAM_H
    
    #include <muduo/base/StringPiece.h>
    #include <muduo/base/Types.h>
    #include <assert.h>
    #include <string.h> // memcpy
    #ifndef MUDUO_STD_STRING
    #include <string>
    #endif
    #include <boost/noncopyable.hpp>
    
    namespace muduo
    {
    
    namespace detail
    {
    //定义两个缓冲区大小的常量
    const int kSmallBuffer = 4000;
    const int kLargeBuffer = 4000*1000;
    //缓冲区类
    //模板,但是SIZE是非类型参数
    template<int SIZE>
    class FixedBuffer : boost::noncopyable//不可被拷贝
    {
     public:
        //构造函数
      FixedBuffer() : cur_(data_)//初始化当前指针
      {//设置函数指针
        setCookie(cookieStart);
      }
    //析构函数
      ~FixedBuffer()
      {//传递函数指针,这个和构造函数传递的两个函数在下面
        setCookie(cookieEnd);
      }
    //添加数据
      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_; }
      //当前可用的空间(end减去当前指针)
      int avail() const { return static_cast<int>(end() - cur_); }
      //增加  
      void add(size_t len) { cur_ += len; }
      //重置空间
      void reset() { cur_ = data_; }
      //把数据都清零,相当于memset(buf,0,sizeof(buf))
      void bzero() { ::bzero(data_, sizeof data_); }
      // for used by GDB
      //在数据后面加个结束符
      const char* debugString();
      //将函数指针指向传递进来的函数
      void setCookie(void (*cookie)()) { cookie_ = cookie; }
      // for used by unit test
      //把数据构造一个String对象出来
      string asString() const { return string(data_, length()); }
    
     private:
        //返回的是缓冲区的后一个地址
      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];//data就是缓冲区,容量为传进来的SIZE
      char* cur_;//当前指针
    };
    
    }
    //LogStream类主要是封装了插入运算符
    class LogStream : boost::noncopyable
    {
      typedef LogStream self;
     public:
        //包含了LogStream的缓冲区,kSmallBuffer,k开头的表示一个常量,是谷歌的编程规范
      typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer;
       //输出数据首先是输出到缓冲区当中
       //以下重载了各种类型的插入运算符
       //返回的是自身的引用
      self& operator<<(bool v)
      {//输出布尔类型
        buffer_.append(v ? "1" : "0", 1);//长度是1
        return *this;
      }
      //实现在.cc文件
      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;
      }
    //如果定义了MUDUO_STD_STRING,则使用的是std::string
    //否则使用的是__gnu_cxx::__sso_string
    #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;
      }
      //这三个只是改装了以下接口
      void append(const char* data, int len) { buffer_.append(data, len); }
      const Buffer& buffer() const { return buffer_; }
      void resetBuffer() { buffer_.reset(); }
    
     private:
      void staticCheck();
    //成员模板函数
      template<typename T>
      void formatInteger(T);
    
      Buffer buffer_;//大小为kSmallBuffer的缓冲区
    
      static const int kMaxNumericSize = 32;
    };
    
    class Fmt // : boost::noncopyable
    {
     public:
        //成员模板,把val进行格式化,然后存放到buf中
      template<typename T>
      Fmt(const char* fmt, T val);
    
      const char* data() const { return buf_; }//返回格式化的数据
      int length() const { return length_; }//返回长度
    
     private:
      char buf_[32];
      int length_;
    };
    
    inline LogStream& operator<<(LogStream& s, const Fmt& fmt)
    {
      s.append(fmt.data(), fmt.length());
      return s;
    }
    
    }
    #endif  // MUDUO_BASE_LOGSTREAM_H
    
    

    LogSrtream.cc

    #include <muduo/base/LogStream.h>
    
    #include <algorithm>
    #include <limits>
    #include <boost/static_assert.hpp>
    #include <boost/type_traits/is_arithmetic.hpp>
    #include <assert.h>
    #include <string.h>
    #include <stdint.h>
    #include <stdio.h>
    
    using namespace muduo;
    using namespace muduo::detail;
    
    #pragma GCC diagnostic ignored "-Wtype-limits"
    //#pragma GCC diagnostic error "-Wtype-limits"
    namespace muduo
    {
    namespace detail
    {
    
    const char digits[] = "9876543210123456789";
    const char* zero = digits + 9;//digits + 9就是"0"
    BOOST_STATIC_ASSERT(sizeof(digits) == 20);
    
    const char digitsHex[] = "0123456789ABCDEF";
    BOOST_STATIC_ASSERT(sizeof digitsHex == 17);
    
    // Efficient Integer to String Conversions, by Matthew Wilson.
    //将 一个数转换成字符串
    template<typename T>
    size_t convert(char buf[], T value)
    {
      T i = value;
      char* p = buf;
    
      do
      {//对10取模得到最后一位数字
        int lsd = static_cast<int>(i % 10);
        i /= 10;//更新当前数
        //zero将数字转换成字符串,zero指针指向的字符是0,再偏移lsd个位置可以得到相应的字符
        *p++ = zero[lsd];
      } while (i != 0);
    //比如-123会转成"321-"
      if (value < 0)//如果要转换的值小于0
      {
        *p++ = '-';
      }
      *p = '';//添加结束符
      std::reverse(buf, p);//使用reverse将字符串进行逆转
    
      return p - buf;//返回转换的字符数
    }
    
    //uintptr_t类型对于32位平台来说就是unsigned int
    //对于64位平台来说就是unsigned long int
    size_t convertHex(char buf[], uintptr_t value)
    {//转换成16进制的字符,和转换成10进制的差不多
      uintptr_t i = value;
      char* p = buf;
      do
      {
        int lsd = i % 16;
        i /= 16;
        *p++ = digitsHex[lsd];
      } while (i != 0);
    
      *p = '';
      std::reverse(buf, p);
    
      return p - buf;
    }
    
    }
    }
    //在数据后面加个,相当于字符串
    template<int SIZE>
    const char* FixedBuffer<SIZE>::debugString()
    {
      *cur_ = '';//加入结束符
      return data_;
    }
    
    template<int SIZE>
    void FixedBuffer<SIZE>::cookieStart()
    {
    }
    
    template<int SIZE>
    void FixedBuffer<SIZE>::cookieEnd()
    {
    }
    
    template class FixedBuffer<kSmallBuffer>;
    template class FixedBuffer<kLargeBuffer>;
    
    void LogStream::staticCheck()
    {
      BOOST_STATIC_ASSERT(kMaxNumericSize - 10 > std::numeric_limits<double>::digits10);
      BOOST_STATIC_ASSERT(kMaxNumericSize - 10 > std::numeric_limits<long double>::digits10);
      BOOST_STATIC_ASSERT(kMaxNumericSize - 10 > std::numeric_limits<long>::digits10);
      BOOST_STATIC_ASSERT(kMaxNumericSize - 10 > std::numeric_limits<long long>::digits10);
    }
    //类型转换成员模板的实现
    template<typename T>
    void LogStream::formatInteger(T v)
    {//当前缓冲区大于32
      if (buffer_.avail() >= kMaxNumericSize)//kMaxNumericSize为32
      {//把v转换成字符串,存放到buffer_.current()
        size_t len = convert(buffer_.current(), v);
        //调整buffer_.current()指针
        buffer_.add(len);
      }
    }
    //重载各种运算符
    LogStream& LogStream::operator<<(short v)
    {//short转成int
      *this << static_cast<int>(v);
      return *this;
    }
    
    LogStream& LogStream::operator<<(unsigned short v)
    {//unsigned short转成unsigned int
      *this << static_cast<unsigned int>(v);
      return *this;
    }
    
    LogStream& LogStream::operator<<(int v)
    {
      formatInteger(v);
      return *this;
    }
    
    LogStream& LogStream::operator<<(unsigned int v)
    {
      formatInteger(v);
      return *this;
    }
    
    LogStream& LogStream::operator<<(long v)
    {
      formatInteger(v);
      return *this;
    }
    
    LogStream& LogStream::operator<<(unsigned long v)
    {
      formatInteger(v);
      return *this;
    }
    
    LogStream& LogStream::operator<<(long long v)
    {
      formatInteger(v);
      return *this;
    }
    
    LogStream& LogStream::operator<<(unsigned long long v)
    {
      formatInteger(v);
      return *this;
    }
    //转换指针,是转成16进制的
    LogStream& LogStream::operator<<(const void* p)
    {//把指针强制转换为uintptr_t类型
    //uintptr_t类型对于32位平台来说就是unsigned int
    //对于64位平台来说就是unsigned long int
      uintptr_t v = reinterpret_cast<uintptr_t>(p);
      if (buffer_.avail() >= kMaxNumericSize)
      {
        char* buf = buffer_.current();//获取当前的位置
        buf[0] = '0';//添加两个字符表示是16进制
        buf[1] = 'x';
        size_t len = convertHex(buf+2, v);
        buffer_.add(len+2);//更新当前指针
      }
      return *this;
    }
    
    // FIXME: replace this with Grisu3 by Florian Loitsch.
    LogStream& LogStream::operator<<(double v)
    {
      if (buffer_.avail() >= kMaxNumericSize)
      {
        int len = snprintf(buffer_.current(), kMaxNumericSize, "%.12g", v);
        buffer_.add(len);
      }
      return *this;
    }
    //把T按fmt格式化后存放到buf中
    template<typename T>
    Fmt::Fmt(const char* fmt, T val)
    {//断言T是算术类型
      BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value == true);
     //使用snprintf函数进行格式化
      length_ = snprintf(buf_, sizeof buf_, fmt, val);
      assert(static_cast<size_t>(length_) < sizeof buf_);
    }
    
    // Explicit instantiations
    //模板的特化
    template Fmt::Fmt(const char* fmt, char);
    template Fmt::Fmt(const char* fmt, short);
    template Fmt::Fmt(const char* fmt, unsigned short);
    template Fmt::Fmt(const char* fmt, int);
    template Fmt::Fmt(const char* fmt, unsigned  int);
    template Fmt::Fmt(const char* fmt, long);
    template Fmt::Fmt(const char* fmt, unsigned long);
    template Fmt::Fmt(const char* fmt, long long);
    template Fmt::Fmt(const char* fmt, unsigned long long);
    template Fmt::Fmt(const char* fmt, float);
    template Fmt::Fmt(const char* fmt, double);
    

    编写两个简单的程序使用下日志功能,Log_test1.cc将日志输出到终端,代码如下

    //日志输出到标准输出
    #include <muduo/base/Logging.h>
    #include <errno.h>
    
    using namespace muduo;
    
    int main()
    {   //简单的使用下日志输出
        //各种级别的日志输出
        LOG_TRACE<<"trace ...";
        LOG_DEBUG<<"debug ...";
        LOG_INFO<<"info ...";
        LOG_WARN<<"warn ...";
        LOG_ERROR<<"error ...";
        //LOG_FATAL<<"fatal ...";//这里会中断程序,先注释掉
        errno = 13;
        LOG_SYSERR<<"syserr ...";
        //LOG_SYSFATAL<<"sysfatal ...";//这里会中断程序,先注释掉
        return 0;
    }
    

    运行结果如下所示:
    这里写图片描述
    Log_test2.cc将日志输出到文件,代码如下所示:

    //日志输出到文件
    #include <muduo/base/Logging.h>
    #include <errno.h>
    #include <stdio.h>
    
    using namespace muduo;
    
    FILE* g_file;//定义一个文件
    //muduo中的  typedef void (*OutputFunc)(const char* msg, int len);
    void dummyOutput(const char* msg, int len)
    {
        if (g_file)
        {//将内容写到文件里
            fwrite(msg, 1, len, g_file);
        }
    }
    
    void dummyFlush()
    {
        fflush(g_file);//清空输出
    }
    
    int main()
    {
        g_file = ::fopen("/mnt/hgfs/lcw_program/lcw_muduo_learning/tests/muduo_log", "ae");//打开文件
        Logger::setOutput(dummyOutput);//设置输出到文件,dummyOutput为函数
        Logger::setFlush(dummyFlush);
    
        LOG_TRACE<<"trace ...";
        LOG_DEBUG<<"debug ...";
        LOG_INFO<<"info ...";
        LOG_WARN<<"warn ...";
        LOG_ERROR<<"error ...";
        //LOG_FATAL<<"fatal ...";//这里会中断程序,先注释掉
        errno = 13;
        LOG_SYSERR<<"syserr ...";
        //LOG_SYSFATAL<<"sysfatal ...";
    
        ::fclose(g_file);//关闭文件
    
        return 0;
    }
    

    运行结果如下:
    这里写图片描述
    文件内容如下:
    这里写图片描述

  • 相关阅读:
    linux 安装ssh
    Pwn环境安装
    18年最后一天
    绘制分形树
    python之绘制图形库turtle(转)
    turtle绘制多个五角星
    Flask的基本操作知识
    pycharm-professional-2018.2.3 汉化激活安装过程
    Could not find a version that satisfies the requirement flask
    豆瓣即将上映电影爬虫作业
  • 原文地址:https://www.cnblogs.com/sigma0-/p/12630478.html
Copyright © 2011-2022 走看看