每一个成熟的项目都有大大小小的日志系统,在关键的地方打印日志信息,常用来跟踪程序运行,查找错误原因等,可以节省大量的debug时间

muduo的日志信息有5个级别

  • TRACE,细粒度最高的日志信息,打印的最详细
  • DEBUG,细粒度级别上对调试有帮助的日志信息
  • INFO,粗粒度级别上强调程序的运行信息
  • WARN,程序能正常运行,但存在潜在风险的信息
  • ERROR,执行出错,但不影响程序继续执行的错误信息
  • FATAL,将导致程序退出的严重信息

muduo的日志格式为


在muduo的很多源文件中,多存在着输出日志信息的语句,例如

  LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;LOG_INFO << "TcpServer::newConnection [" << name_<< "] - new connection [" << connName<< "] from " << peerAddr.toIpPort();LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";LOG_FATAL << "Another EventLoop " << t_loopInThisThread<< " exists in this thread " << threadId_;

这些形如LOG_*的调用实际上是宏定义

/* * __FILE__:返回所在文件名* __LINE__:返回所在行数* __func__:返回所在函数名* * 这些都是无名对象,当使用LOG_* << "***"时,* 1.构造Logger类型的临时对象,返回LogStream类型变量* 2.调用LogStream重载的operator<<操作符,将数据写入到LogStream的Buffer中* 3.当前语句结束,Logger临时对象析构,调用Logger析构函数,将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()
#define LOG_SYSERR muduo::Logger(__FILE__, __LINE__, false).stream()
#define LOG_SYSFATAL muduo::Logger(__FILE__, __LINE__, true).stream()

当使用LOG_*时,在编译期会被宏定义后面的语句替换。而实际上是创建了Logger的临时对象,创建后调用stream函数,可以猜测,stream函数返回的对象重载了operator << 函数,可以应对各种日志信息的输出,函数定义如下,实际返回的是LogStream对象

  /* 返回Impl的LogStream对象 */LogStream& stream() { return impl_.stream_; }

LogStream对象重载了各种operator << 函数,可以处理多种类型的信息。而在LogStream中,存在着一个缓冲区Buffer用于保存这些日志信息

/* 使用FixedBuffer,默认缓冲区大小为kSmallBuffer,重载各种operator<<操作 */
class LogStream : noncopyable
{typedef LogStream self;public:/* 缓冲区的类型,是个固定大小的缓冲区,由字符数组实现 */typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer;/* 重载的operator<<函数,将日志信息存放在缓冲区中 */self& operator<<(const char* str){if (str){buffer_.append(str, strlen(str));}else{buffer_.append("(null)", 6);}return *this;}/* ... */private:/* 用于存储日志信息的缓冲区 */Buffer buffer_;static const int kMaxNumericSize = 32;
};

对于临时对象,所在语句结束后就被析构了,所以对于日志信息的输出,肯定都交给Logger对象的析构函数处理了
Logger的定义如下,Logger定义6个日志级别,分别对应不同的LOG_*。同时使用了Impl技法将对象和数据分离,Impl保存着所有Logger需要的数据。SourceFile保存着调用LOG_*语句所在的源文件名和行号,也是一个内部类。

class TimeZone;class Logger
{public:/* 日志级别 */enum LogLevel{TRACE,  /* 细粒度最高的信息 */DEBUG,  /* 对调试有帮助的事件信息 */INFO,   /* 粗粒度级别上强调程序的运行信息 */WARN,   /* 程序能正常运行,但是有潜在危险的信息 */ERROR,  /* 程序出错,但是不影响系统运行的信息 */FATAL,  /* 将导致程序停止运行的严重信息 */NUM_LOG_LEVELS, /* 日志级别个数 */};// compile time calculation of basename of source file/* 内部类,保存调用LOG_WARN<<之类的那条语句所在文件名 */class SourceFile{public:template<int N>inline SourceFile(const char (&arr)[N]): data_(arr),size_(N-1){const char* slash = strrchr(data_, '/'); // builtin functionif (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();/* 返回Impl的LogStream对象 */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);static void setTimeZone(const TimeZone& tz);private:/* * Impl技法,数据和对象分离* 通常应该是在类定义中声明class Impl;* 然后创建Impl对象,Impl impl_;* 最后在.cpp文件中实现Logger::Impl的定义* * Impl对象存储的就是Logger所需要的所有数据*/
class Impl
{public:typedef Logger::LogLevel LogLevel;Impl(LogLevel level, int old_errno, const SourceFile& file, int line);void formatTime();void finish();/* UTC时间,记录写入日志的时间 */Timestamp time_;/* 将日志信息存在缓冲区中,使用LOG_WARN是会返回Logger().stream(),就是返回这个LogStream */LogStream stream_;/* 日志级别,TRACE, DEBUG, WARN... */LogLevel level_;int line_;/* * SourceFile也是Logger的内部类 why ?* 保存调用LOG_WARN<<语句的源文件名*/SourceFile basename_;
};Impl impl_;};

Impl的功能不只局限在保存Logger的数据,同时也在被创建时为日志信息添加前缀,通常是日期,时间,线程号,级别等。注意,在创建Logger对象时,会调用Impl的构造函数,在构造函数中就已经将日志前缀写入LogStream中,而调用stream函数返回LogStream对象时,日志正文信息就只会写在后面,这也是想要的效果。
Impl构造函数如下,用于写入各种前缀信息

/* Impl的构造函数,Impl主要负责日志的格式化 */
Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line): time_(Timestamp::now()), /* 当前时间 */stream_(),  /* LogStream流 */level_(level),  /* 日志级别 */line_(line),    /* 调用LOG_* << 所在行,由__LINE__获取 */basename_(file) /* 调用LOG_* << 所在文件名,由__FILE__获取 */
{/* 格式化当前时间,写入LogStream中 */formatTime();/* 缓存线程id到成员变量中,当获取时直接返回 */CurrentThread::tid();/* 将线程id和日志级别写入LogStream */stream_ << T(CurrentThread::tidString(), CurrentThread::tidStringLength());stream_ << T(LogLevelName[level], 6);/* 如果有错误,写入错误信息 */if (savedErrno != 0){stream_ << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";}
}

由于是临时对象,所以析构函数接管了所有日志输出的任务

  1. 为日志信息添加后缀,通常是源文件名和所在行号
  2. LogStream的缓冲区中回去所有日志信息,包括前缀和后缀
  3. 调用输出函数,默认将日志信息打印到屏幕上
  4. 如果日志级别是FATAL,那么会立即刷新缓冲区,同时发送abort终止程序运行
/* * Logger析构函数,将LogStream中的数据打印出来* 根据日志级别决定要不要立刻刷新*/
Logger::~Logger()
{/* 当前日志输出结束,添加后缀(所在文件名和行号) */impl_.finish();/* 从LogStream的Buffer中拿出日志信息 */const LogStream::Buffer& buf(stream().buffer());/* 将信息输出到屏幕上 */g_output(buf.data(), buf.length());/* 如果当前日志级别是FATAL,表示是个终止程序的严重错误,将输出缓冲区的信息刷新到屏幕上,结束程序 */if (impl_.level_ == FATAL){g_flush();abort();}
}

打印函数如下,默认打印到屏幕上,可以使用Logger::setOuputLogger::setFlush设置自定义打印和刷新函数,将日志信息重定向到文件等,这就涉及muduo另几个日志类,用于写入文件和滚动文件。

/** 默认输出和刷新位置,将信息打印到屏幕上*/
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临时对象也被析构,程序继续执行(如果不是FATAL的话)

muduo网络库学习(九)日志类Logger和LogStream,将日志信息打印到屏幕相关推荐

  1. muduo网络库学习(七)用于创建服务器的类TcpServer

    目前为止,涉及到的绝大多数操作都没有提及线程,EventLoop,Poller,Channel,Acceptor,TcpConnection,这些对象的执行都是在单独线程完成,并没有设计多线程的创建销 ...

  2. muduo网络库学习总结:基本架构及流程分析

    muduo网络库学习:基本架构及流程分析 基本架构 Basic Reactor Mutiple Reactor + ThreadPool muduo库的基本使用 基本结构介绍 EventLoop类 P ...

  3. muduo网络库学习(1)

    muduo网络库学习(1) 文章目录 muduo网络库学习(1) 前言 一.muduo是什么? 二.代码结构 1.base库 2.net库 3.附属库 二.网络库结构 总结 前言 本章节主要介绍mud ...

  4. muduo网络库学习(八)事件驱动循环线程池EventLoopThreadPool

    muduo是支持多线程的网络库,在muduo网络库学习(七)用于创建服务器的类TcpServer中也提及了TcpServer中有一个事件驱动循环线程池,线程池中存在大量线程,每个线程运行一个Event ...

  5. muduo网络库学习(四)事件驱动循环EventLoop

    muduo的设计采用高并发服务器框架中的one loop per thread模式,即一个线程一个事件循环. 这里的loop,其实就是muduo中的EventLoop,所以到目前为止,不管是Polle ...

  6. muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor

    目录 muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor Connector 系统函数connect 处理非阻塞connect的步骤: Connetor时序图 Accep ...

  7. muduo网络库学习(三)定时器TimerQueue的设计

    Linux下用于获取当前时间的函数有 time(2) / time_t (秒) ftime(3) / struct timeb (毫秒) gettimeofday(2) / struct timeva ...

  8. muduo网络库学习(五)服务器监听类Acceptor及Tcp连接TcpConnection的建立与关闭

    通常服务器在处理客户端连接请求时,为了不阻塞在accept函数上,会将监听套接字注册到io复用函数中,当客户端请求连接时,监听套接字变为可读,随后在回调函数调用accept接收客户端连接.muduo将 ...

  9. muduo网络库学习(一)对io复用的封装Poller,面向对象与基于对象

    高效并发的网络框架大多离不开io多路复用函数,Linux下有三种 select poll epoll 关于三者的区别可以参考 linux网络编程-–几种服务器模型及io多路复用函数 前段时间看Libe ...

最新文章

  1. Python里面None True False之间的区别
  2. 《你要么出众,要么出局》读书笔记
  3. opencv源码解析之(6):hog源码分析
  4. linux centos rc.local 自启动无效 解决方法
  5. Graphviz之DT:手把手教你使用可视化工具Graphviz将dot文件转为结构图的pdf文件
  6. C#中的深复制和浅复制(在C#中克隆对象)
  7. Source Code Library 源代码收集器
  8. OpenJudge NOI 1.5 25:求特殊自然数
  9. 使用常识 | 如何在word中添加勾选框
  10. idea git 注意事项
  11. Shell 条件表达式的正则匹配
  12. [转]windows2003的IIS详细设置方法
  13. 回顾:前端模块化和AMD、CMD规范(全)
  14. book回车键 mac_macbook pro键盘失灵 macbook pro键盘失灵解决办法
  15. 利用Python使图片完美去除水印,我想试试马赛克的效果∧v∧
  16. 张一鸣宣布卸任字节跳动CEO,去学习承担社会责任
  17. 设计模式——Revealing Module(揭示模块)模式
  18. C++11产生随机数,random库产生随机数
  19. superIO在Linux下的设备,X86 Linux ITE SuperIO GPIO Control
  20. mac版太空地球3k动态壁纸安装方法

热门文章

  1. 图像坐标球面投影_坐标系统及投影概述
  2. linux下read函数缺失字节_机器人、工控机和Linux 网络编程接口能否蹭出火花?
  3. Docker进阶-容器监控cAdvisor+InfluxDB+Granfana
  4. 现代3D图形编程学习-关于本书(译)
  5. anaconda的简单使用教程(虚拟环境安装)
  6. MySql恢复密码的过程
  7. Mac 安装多个python环境
  8. 1.3 xss原理分析与剖析(4)
  9. phpmyadmin #1045 - Access denied for user 'root'@'localhost' (using password: NO)
  10. 有关linux下redis overcommit_memory的问题