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, };
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);}
因此默认就是将日志信息输出到了标志输出中。
阅读全文
0 0
- muduo源码学习(11)-日志类封装1
- muduo源码学习(12)-日志类封装2
- muduo网络库源码学习————日志类封装
- muduo日志类的封装
- muduo源码学习(1)
- muduo源码学习(5)-线程封装
- muduo源码学习(19)-socket封装
- muduo源码学习(20)-Acceptor封装
- muduo源码分析:日志相关类(1)
- muduo源码分析:异常类封装
- muduo源码分析:线程类Thread封装
- muduo网络库学习笔记(8):高效日志类的封装
- muduo源码学习(15)-IO复用的封装
- muduo日志1
- muduo 日志库学习(一)
- muduo 日志库学习(二)
- muduo 日志库学习(二)
- muduo 日志库学习(一)
- MySQL数据库备份
- 【java】替换字符串一些部分为星号达到保密效果(工具类整理)
- mybatis框架缓存使用
- StringBuilder在高性能场景下的正确用法
- 微信小程序 整理文档(更新中。。。)
- muduo源码学习(11)-日志类封装1
- Java面试个人整理(手打勿喷,易于个人学习)2
- Java消息中间件ActiveMQ初体验
- C++中extern关键字的使用
- 存储专栏:深度解读高端存储的快照技术
- Mybatis的延迟加载
- 阿里云安全科学家吴翰清入选MIT TR35,你知道弹性安全网络技术吗?
- Oracle习题
- 微信分享功能