muduo网络库学习(九)日志类Logger和LogStream,将日志信息打印到屏幕
来源:互联网 发布:安卓版ps软件中文版 编辑:程序博客网 时间:2024/05/20 00:15
每一个成熟的项目都有大大小小的日志系统,在关键的地方打印日志信息,常用来跟踪程序运行,查找错误原因等,可以节省大量的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 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(); /* 返回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 << ") "; }}
由于是临时对象,所以析构函数接管了所有日志输出的任务
- 为日志信息添加后缀,通常是源文件名和所在行号
- 从
LogStream
的缓冲区中回去所有日志信息,包括前缀和后缀 - 调用输出函数,默认将日志信息打印到屏幕上
- 如果日志级别是
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::setOuput
和Logger::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,将日志信息打印到屏幕
- muduo网络库学习之Logger类、LogStream类、LogFile类封装中的知识点
- 手机不打印日志和Logger日志库的使用
- muduo库源码学习(base):LogStream
- Logger 日志打印库详解
- 手机不打印日志和Logger日志库的使用 logger颜色
- muduo 日志库学习(一)
- muduo 日志库学习(二)
- muduo 日志库学习(二)
- muduo 日志库学习(一)
- Logger打印日志
- 将程序运行信息打印到 系统日志
- 将运行信息打印日志到文件中
- 打印日志信息到文件
- muduo网络库源码学习————日志类封装
- muduo网络库学习笔记(8):高效日志类的封装
- 一个简单的日志类,将打印信息写到文件
- 利用logger打印完整的okhttp网络请求和响应日志
- 初学python:输出指定范围内的素数,范围由键盘输入
- webpack 3新学习
- 机房---“奇怪”的问题---time被隐藏
- 【Prufer编码+组合】BZOJ1005(HNOI2008)[明明的烦恼]题解
- Java8 Lamdba 在Android Studio 使用
- muduo网络库学习(九)日志类Logger和LogStream,将日志信息打印到屏幕
- Python数据迭代
- 基于DR模式的LVS
- 引用计数写时拷贝
- 关于Visual Studio "当前不会命中断点.还没有为该文档加载任何符号"的解决方法
- 【Maven】settings配置了解
- zookeeper javaApi/zkclient
- CNCC day2下午 人工智能与机器学习前沿技术论坛
- 欢迎使用CSDN-markdown编辑器