muduo 12 日志类的封装
2014-01-21 12:12
357 查看
01 我们使用日志来调试程序的逻辑操作比gdb容易多 有时阅读别人代码的时候 通过日志来理清程序的流程
02 记录系统的运行状态
![](http://images.cnitblog.com/blog/595738/201401/211136100634.png)
![](http://images.cnitblog.com/blog/595738/201401/211136563289.png)
先将日志输出到缓冲区中 再将日志写入到文件或者标准输出
02 记录系统的运行状态
![](http://images.cnitblog.com/blog/595738/201401/211136100634.png)
![](http://images.cnitblog.com/blog/595738/201401/211136563289.png)
先将日志输出到缓冲区中 再将日志写入到文件或者标准输出
#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, }; // compile time calculation of basename of source file class SourceFile // 嵌套类 get basebame { public: template<int N> inline SourceFile(const char (&arr) ) : data_(arr), size_(N-1) { const char* slash = strrchr(data_, '/'); // builtin function 查找一个字符c在另一个字符串str中末次出现的位置 if (slash) { data_ = slash + 1; size_ -= static_cast<int>(data_ - arr); } } explicit SourceFile(const char* filename) : data_(filename) { // strrchr函数 从字符串的右侧开始查找字符c首次出现的位置), // 并返回从字符串中的这个位置起,一直到字符串结束的所有字符 const char* slash = strrchr(filename, '/'); if (slash) { data_ = slash + 1; // data_不包括/ 这样就得到了basename 不包括路径的 } 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_; } // stream()函数返回一个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 //嵌套类 Impl隐藏信息的手法 这个类才是真正的实现 { public: typedef Logger::LogLevel LogLevel; Impl(LogLevel level, int old_errno, const SourceFile& file, int line); void formatTime(); // 时间的格式化函数 void finish(); // 最后将string格式化写入缓冲区 Timestamp time_; // 时间 LogStream stream_; // 日志的缓存区 LogLevel level_; // 等级 int line_; // 行 SourceFile basename_; // 名字 }; Impl impl_; // Logger包含了一个Impl对象 }; extern Logger::LogLevel g_logLevel; inline Logger::LogLevel Logger::logLevel() { return g_logLevel; } // 外部使用调用这几个宏 来输出日志 #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() 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
#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 { __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); // strerror_r 使用线程安全的方式返回strerror()的结果。 // 在必要的时候才使用给定的内存缓冲 (与POSIX中的定义不一致). } 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(); 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) // 默认写到标准输出1中 { 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; Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line) : time_(Timestamp::now()), stream_(), level_(level), line_(line), basename_(file) { // ----------------时间:---- 线程ID: ---------- 日志等级 ----------------- 格式化到缓冲区中 formatTime(); // 格式化当前时间 CurrentThread::tid(); // 缓存下的当前线程的id stream_ << T(CurrentThread::tidString(), 6); // 将这些日志信息缓存到缓冲区中 stream_ << T(LogLevelName[level], 6); if (savedErrno != 0) { 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); stream_ << T(t_time, 17) << T(us.data(), 9); } void Logger::Impl::finish() { stream_ << " - " << basename_ << ':' << line_ << '\n'; } 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) { } Logger::Logger(SourceFile file, int line, bool toAbort) : impl_(toAbort?FATAL:ERROR, errno, file, line) { } Logger::~Logger() { impl_.finish(); const LogStream::Buffer& buf(stream().buffer()); // 直接引用 // 在析构函数里调用g_output将buff数据写入到对应的out中 g_output(buf.data(), buf.length());// 使用g_output将stream buffer的数据写入stdout if (impl_.level_ == 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; }
#include <muduo/base/Logging.h> #include <muduo/base/LogFile.h> #include <muduo/base/ThreadPool.h> #include <stdio.h> int g_total; FILE* g_file; boost::scoped_ptr<muduo::LogFile> g_logFile; void dummyOutput(const char* msg, int len) { g_total += len; if (g_file) { fwrite(msg, 1, len, g_file); } else if (g_logFile) { g_logFile->append(msg, len); } } void bench(const char* type) { muduo::Logger::setOutput(dummyOutput); muduo::Timestamp start(muduo::Timestamp::now()); g_total = 0; int n = 1000*1000; const bool kLongLog = false; muduo::string empty = " "; muduo::string longStr(3000, 'X'); longStr += " "; for (int i = 0; i < n; ++i) { LOG_INFO << "Hello 0123456789" << " abcdefghijklmnopqrstuvwxyz" << (kLongLog ? longStr : empty) << i; } muduo::Timestamp end(muduo::Timestamp::now()); double seconds = timeDifference(end, start); printf("%12s: %f seconds, %d bytes, %10.2f msg/s, %.2f MiB/s\n", type, seconds, g_total, n / seconds, g_total / seconds / (1024 * 1024)); } void logInThread() { LOG_INFO << "logInThread"; usleep(1000); } // 多线程写入可能效率不如多线程 // 我们可以借助异步IO来实现(可以先将数据发送到单个线程再写入) 即异步日志 int main() { getppid(); // for ltrace and strace muduo::ThreadPool pool("pool"); pool.start(5); pool.run(logInThread); pool.run(logInThread); pool.run(logInThread); pool.run(logInThread); pool.run(logInThread); LOG_TRACE << "trace"; LOG_DEBUG << "debug"; LOG_INFO << "Hello"; LOG_WARN << "World"; LOG_ERROR << "Error"; LOG_INFO << sizeof(muduo::Logger); LOG_INFO << sizeof(muduo::LogStream); LOG_INFO << sizeof(muduo::Fmt); LOG_INFO << sizeof(muduo::LogStream::Buffer); sleep(1); bench("nop"); char buffer[64*1024]; g_file = fopen("/dev/null", "w"); setbuffer(g_file, buffer, sizeof buffer); bench("/dev/null"); fclose(g_file); g_file = fopen("/tmp/log", "w"); setbuffer(g_file, buffer, sizeof buffer); bench("/tmp/log"); fclose(g_file); g_file = NULL; g_logFile.reset(new muduo::LogFile("test_log_st", 500*1000*1000, false)); bench("test_log_st"); g_logFile.reset(new muduo::LogFile("test_log_mt", 500*1000*1000, true)); bench("test_log_mt"); g_logFile.reset(); }
相关文章推荐
- Hadoop 伪分布式配置安装
- SQL:Example Uses of the SUBSTRING String Function
- A drop of performance testing- manual correlation
- [转]用 jQuery 实现页面滚动(Scroll)效果的完美方法
- 数字整除
- 九度OJ 1073: 杨辉三角形
- Android 事件分发机制探析
- ubuntu 基本命令
- YOHO!获得3千万美元C轮融资
- Java编程思想——第九章续
- 新年新主张:改善客户体验三步走
- 建立信任主机
- opencv中的椭圆拟合
- 时间函数应用
- SQL:Example Uses of the SUBSTRING String Function
- js面向对象实践
- poj3259,简单判断有无负环,spfa
- poj3259,简单判断有无负环,spfa
- 曾经的内容为王等于今天的内容为王吗?
- oracle virtual 上虚机win 2003 破解密码问题