__thread char t_errnobuf[512];
__thread char t_time[64];
__thread time_t t_lastSecond;
const char* strerror_tl(int savedErrno)
{
return strerror_r(savedErrno, t_errnobuf, sizeof t_errnobuf);
}
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)
{
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;
TimeZone g_logTimeZone;
} // namespace muduo
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)
{
formatTime();
CurrentThread::tid();
stream_ << T(CurrentThread::tidString(), CurrentThread::tidStringLength());
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 / Timestamp::kMicroSecondsPerSecond);
int microseconds = static_cast<int>(microSecondsSinceEpoch % Timestamp::kMicroSecondsPerSecond);
if (seconds != t_lastSecond)
{
t_lastSecond = seconds;
struct tm tm_time;
if (g_logTimeZone.valid())
{
tm_time = g_logTimeZone.toLocalTime(seconds);
}
else
{
::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;
}
if (g_logTimeZone.valid())
{
Fmt us(".%06d ", microseconds);
assert(us.length() == 8);
stream_ << T(t_time, 17) << T(us.data(), 8);
}
else
{
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(buf.data(), buf.length());
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;
}
void Logger::setTimeZone(const TimeZone& tz)
{
g_logTimeZone = tz;
}
strerror_tl这个函数很简单,但是挺实用的。strerror这个函数是线程不安全的,因为使用了静态数组,这里作者使用__thread变量,接口形式相同,但是线程安全了。不用使用strerror_r这个用起来比较麻烦的接口。在定义与日志等级对应的字符串时,使用了Logger::NUM_LOG_LEVELS,这是使用了枚举类型的一个特性,就是枚举类型内的成员默认值是按顺序从0开始排序的。所以NUM_LOG_LEVELS作为最后成员,数值就是日志等级的个数。
之后的class T就是一个辅助函数,用来辅助字符串输出的,因为在LogStream类的字符串输出中,会使用strlen获取字符串的长度,该类可以避免一次strlen调用。
defaultOutput和defaultFlush就是默认的输出和刷新缓冲区的函数,即向stdout输出,修改输出和刷新缓冲区的方法,是通过修改下面的g_output和g_flush。
在下面就是比较重要的一个内部类Impl,该类的构造函数实现了日志输出内容的拼接。formatTime是生成表示时间的字符串,可以显示到微秒级别,而且可以修改时区。
最后的主类Logger实际上很简单,使用内部类实现内容的拼接,提供修改输出级别、输出函数和刷新函数等功能。该类最重要的应该是析构函数,该类通过析构实现了日志最终的输出,并且判断是否需要终止程序,调用abort终止程序。
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>
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();
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:
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;
}
//
// CAUTION: do not write:
//
// if (good)
// LOG_INFO << "Good news";
// else
// LOG_WARN << "Bad news";
//
// this expends to
//
// if (good)
// if (logging_INFO)
// logInfoStream << "Good news";
// else
// logWarnStream << "Bad news";
//
#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;
}
头文件中定义了一个SourceFile类,该类的功能跟之前的class T差不多。后面的日志打印宏很有意思,就拿第一个来说,
#define LOG_TRACE if (muduo::Logger::logLevel() <= muduo::Logger::TRACE) \
muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream()
在使用的时候,是这样的:LOG_TRACE << "news";
该宏定义首先判断日志级别,如果无效,那么就什么都不做。在级别有效时,定义了Logger类的一个临时变量,通过该类的构造函数拼接日期等内容,然后调用stream(),该函数返回LogStream&,进而调用LogStream类的“<<”操作,写入“news”,执行完后,Logger的临时对象析构,调用输出函数,输出日志。
最后有一个CHECK_NOTNULL宏定义,判断指针是否为NULL,在该宏定义中,使用“#”实现打印参数名称。