当前位置 博文首页 > wanggao的专栏:muduo学习笔记:base部分之高性能日志库-Logger
前面介绍了【muduo学习笔记:base部分之高性能日志库-LogStream】前端中的LogStream类,本文接受日志前端的管理对象Logger,代码位于 muduo/base/Logging.{h, cc}。
整体功能如下图:Logger负责全局的日志级别、输出目的地设置(静态成员函数),实际的数据流处理由Impl内部类实现。Imp的成员变量LogSteam对象是实际的缓存处理对象,包含了日志信息的加工,通过Logger的stream()函数取得实现各种日志宏功能。当Logger对象析构时,将LogStream的日志数据flush到输出目的地,默认是stdout。
Logging部分实现由多个部分,先给头文件主体代码(略作修改),再依次介绍不同部分。
class TimeZone;
class Logger
{
public:
enum LogLevel{ TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NUM_LOG_LEVELS };
// 编译器计算源文件名
class SourceFile
{
public:
template<int N>
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_;
};
// 构造函数,实际是用于初始化 Impl 类
Logger(SourceFile file, int line)
: impl_(INFO, 0, file, line) {}
Logger(SourceFile file, int line, LogLevel level)
: impl_(level, 0, file, line) { impl_.stream_ << func << ' '; }
Logger(SourceFile file, int line, LogLevel level, const char* func)
: impl_(level, 0, file, line) {}
Logger(SourceFile file, int line, bool toAbort)
: impl_(toAbort?FATAL:ERROR, errno, file, line) {}
// 析构函数, flush输出
~Logger();
// 用于日志宏
LogStream& stream() { return impl_.stream_; }
// 全局方法,设置日志级别、flush输出目的地、日志时区等
static LogLevel logLevel();
static void setLogLevel(LogLevel level);
typedef void (*OutputFunc)(const char* msg, int len);
typedef void (*FlushFunc)();
static void setOutput(OutputFunc); // 默认 fwrite 到 stdout
static void setFlush(FlushFunc); // 默认 fflush 到 stdout
static void setTimeZone(const TimeZone& tz); // 默认 GMT
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_;
};
// 全局的日志级别,静态成员函数定义,静态成员函数实现
extern Logger::LogLevel g_logLevel;
inline Logger::LogLevel Logger::logLevel(){
return g_logLevel;
}
Logger类主要有三个部分:
(1)静态成员函数:
全局方法,设置日志级别、flush输出目的地、日志时区等。
(2)构造函数
类Logger
构造函数传递参数,主要用于构造Impl
类。
第一个参数类型是SourceFile
,传递一个字符串数组、或者一个字符串的文件路径,使用函数strrchr
查找分隔符获取文件的basename;在Release模式下编译,将在编译期调用__builtin_strrchr
计算分隔符位置而得到文件名,避免了运行时的计算,提高效率。
(3)成员变量 Impl
内部类,实际用于日志信息的处理类。在Logger构造函数的初始化列表中进行初始化。
理论上,使用Impl的设计手法,应该使用指针或指针隐藏实现,如
//************************** 头文件
class Logger
{
public:
...
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
//************************** 源文件
class Logger::Impl
{
public:
typedef Logger::LogLevel LogLevel;
Impl(LogLevel level, int old_errno, const Logger::SourceFile& file, int line);
void formatTime();
void finish();
Timestamp time_;
LogStream stream_;
LogLevel level_;
int line_;
Logger::SourceFile basename_;
};
Logger::Logger(SourceFile file, int line)
: //impl_(INFO, 0, file, line)
impl_(new Impl(INFO, 0, file, line))
{
}
...
目前muduo是开源库,Impl设计没有多大意思。另外,使用指针管理,在堆中分配,相比在栈中分配相比些微效率上的影响。直接只用类对象方式,是为了模块化编程,使得编程结构层次清晰。
(4)成员函数 LogStream& LogStream::stream()
返回Impl内部的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()
Logger类和LogStream类如何配合?
使用LOG_*之类的宏会创建一个临时匿名Logger对象,这个对象有一个Impl对象,而Impl对象有一个LogStream对象。LOG_*宏会返回一个LogStream对象的引用。用于将内容输入到LogStream中的一个buffer中。
在Logger的析构函数中,调用 g_output,即 g_output(buf.data(), buf.length()),将存于LogStream的buffer的日志内容输出。如果是FATAL错误,还要调用g_flush,最后abort()程序。如果没有调用g_flush,会一直输出到缓冲区(标准输出缓冲区,文件FILE缓冲区)满才会真的输出在标准输出,或者写入到文件中去。
一次日志写入,Impl主要干三件事:
(1)构造函数初始化各成员变量,往LogStream中写入格式化时间字符串、线程id,日志级别
(2)往LogStream中写入日志消息(实际是Logger外部处理的)
(3)往LogStream中写入记录日志所在源文件名、行数(Logger析构调用)
着重说明格式化时间字符串的处理。有两个线程前后两次日志操作,都是在同一秒钟内,仅格式化微秒部分。为了实现多线程中日志时间格式化的效率,增加了两个__thread变量,用于缓存当前线程存日期时间字符串、上一次日志记录的秒数。线程id使用了同样的方式缓存对应的字符串。
__thread char t_errnobuf[512];
__thread char t_time[64]; // 当前线程的时间字符串 “年:月:日 时:分:秒”
__thread time_t t_lastSecond; // 当前线程上一次日志记录时的秒数
//Impl类的构造函数
//级别,错误(没有错误则传0),文件,行
//Impl类主要是负责日志的格式化
Logger::Impl::Impl(