Muduo日志
首先看时间戳类
这是TimeStamp类的定义
class Timestamp : public muduo::copyable,
public boost::equality_comparable<Timestamp>,
public boost::less_than_comparable<Timestamp>
{
public:
///
/// Constucts an invalid Timestamp.
///
Timestamp()
: microSecondsSinceEpoch_(0)
{
}
///
/// Constucts a Timestamp at specific time
///
/// @param microSecondsSinceEpoch
explicit Timestamp(int64_t microSecondsSinceEpochArg)
: microSecondsSinceEpoch_(microSecondsSinceEpochArg)
{
}
void swap(Timestamp& that)
{
std::swap(microSecondsSinceEpoch_, that.microSecondsSinceEpoch_);
}
// default copy/assignment/dtor are Okay
string toString() const;
string toFormattedString(bool showMicroseconds = true) const;
bool valid() const { return microSecondsSinceEpoch_ > 0; }
// for internal usage.
int64_t microSecondsSinceEpoch() const { return microSecondsSinceEpoch_; }
time_t secondsSinceEpoch() const
{ return static_cast<time_t>(microSecondsSinceEpoch_ / kMicroSecondsPerSecond); }
///
/// Get time of now.
///
static Timestamp now();
static Timestamp invalid()
{
return Timestamp();
}
static Timestamp fromUnixTime(time_t t)
{
return fromUnixTime(t, 0);
}
static Timestamp fromUnixTime(time_t t, int microseconds)
{
return Timestamp(static_cast<int64_t>(t) * kMicroSecondsPerSecond + microseconds);
}
static const int kMicroSecondsPerSecond = 1000 * 1000;
private:
int64_t microSecondsSinceEpoch_;
};
inline bool operator<(Timestamp lhs, Timestamp rhs)
{
return lhs.microSecondsSinceEpoch() < rhs.microSecondsSinceEpoch();
}
inline bool operator==(Timestamp lhs, Timestamp rhs)
{
return lhs.microSecondsSinceEpoch() == rhs.microSecondsSinceEpoch();
}
///
/// Gets time difference of two timestamps, result in seconds.
///
/// @param high, low
/// @return (high-low) in seconds
/// @c double has 52-bit precision, enough for one-microsecond
/// resolution for next 100 years.
inline double timeDifference(Timestamp high, Timestamp low)
{
int64_t diff = high.microSecondsSinceEpoch() - low.microSecondsSinceEpoch();
return static_cast<double>(diff) / Timestamp::kMicroSecondsPerSecond;
}
///
/// Add @c seconds to given timestamp.
///
/// @return timestamp+seconds as Timestamp
///
inline Timestamp addTime(Timestamp timestamp, double seconds)
{
int64_t delta = static_cast<int64_t>(seconds * Timestamp::kMicroSecondsPerSecond);
return Timestamp(timestamp.microSecondsSinceEpoch() + delta);
}
} // namespace muduo
首先来看获取当前时间的now函数
Timestamp Timestamp::now()
{
struct timeval tv;
gettimeofday(&tv, NULL);
int64_t seconds = tv.tv_sec;
return Timestamp(seconds * kMicroSecondsPerSecond + tv.tv_usec);
}
Timestamp的精度为微妙级别 而gettimeofday获得的是秒级
toString是将int64_t转换为字符串输出
string Timestamp::toString() const
{
char buf[32] = {0};
int64_t seconds = microSecondsSinceEpoch_ / kMicroSecondsPerSecond;
int64_t microseconds = microSecondsSinceEpoch_ % kMicroSecondsPerSecond;
snprintf(buf, sizeof(buf), "%" PRId64 ".%06" PRId64 "", seconds, microseconds);
return buf;
}
toFormattedString转换为年月日输出
string Timestamp::toFormattedString(bool showMicroseconds) const
{
char buf[64] = {0};
time_t seconds = static_cast<time_t>(microSecondsSinceEpoch_ / kMicroSecondsPerSecond);
struct tm tm_time;
gmtime_r(&seconds, &tm_time);
if (showMicroseconds)
{
int microseconds = static_cast<int>(microSecondsSinceEpoch_ % kMicroSecondsPerSecond);
snprintf(buf, sizeof(buf), "%4d%02d%02d %02d:%02d:%02d.%06d",
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,
microseconds);
}
else
{
snprintf(buf, sizeof(buf), "%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);
}
return buf;
}
注意!! 此处获得的是UTC时间
关于时间转换
添加链接描述
其中最让人疑惑的应该是TimeStamp类继承了boost库中一个模板类 而这个模板实例化采用的却是该类 看似无限循环
template <class T>
struct less_than_comparable {
friend bool operator>(const T& x, const T& y)
{ return y<x;}
friend bool operator<=(const T& x, const T& y){ !static_cast<bool>(x>y);}
friend bool operator>=(const T& x, const T& y){!static_cast<bool>(x<y);}
};
当要实现运算符时只需要继承并且实现比较的对应运算符 operator>则在子类实现operator<
下来是替代ifstream 的一个进行写日志的流 为了效率摒弃了c++的流 选择自己实现一个LogStream
其中并不是直接设置一个LogStream类进行append 等操作 而是设置了一个FixedBuffer类作为LogStream的成员变量
FixedBuffer类如下
template<int SIZE>
class FixedBuffer : noncopyable
{
public:
FixedBuffer()
: cur_(data_)
{
setCookie(cookieStart);
}
~FixedBuffer()
{
setCookie(cookieEnd);
}
void append(const char* /*restrict*/ buf, size_t len)
{
// FIXME: append partially
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_; }
void bzero() { memZero(data_, sizeof data_); }
// for used by GDB
const char* debugString();
void setCookie(void (*cookie)()) { cookie_ = cookie; }
// for used by unit test
string toString() const { return string(data_, length()); }
StringPiece toStringPiece() const { return StringPiece(data_, length()); }
private:
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];
char* cur_;
};
根据SIZE 的大小决定能存多少数据 而其中的cur/-指针指向当前可用空间的起始地址
这里的append值得注意 只有当剩余可存取的长度大于要存入的数据长度时才会进行copy 这里的函数指针cookie现在没太懂干嘛的
接着是主要实现了对各种输入重载的LogStream类 以满足像c++标准流一样的输入格式
class LogStream : noncopyable
{
typedef LogStream self;
public:
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* str)
{
if (str)
{
buffer_.append(str, strlen(str));
}
else
{
buffer_.append("(null)", 6);
}
return *this;
}
self& operator<<(const unsigned char* str)
{
return operator<<(reinterpret_cast<const char*>(str));
}
self& operator<<(const string& v)
{
buffer_.append(v.c_str(), v.size());
return *this;
}
self& operator<<(const StringPiece& v)
{
buffer_.append(v.data(), v.size());
return *this;
}
self& operator<<(const Buffer& v)
{
*this << v.toStringPiece();
return *this;
}
void append(const char* data, int len) { buffer_.append(data, len); }
const Buffer& buffer() const { return buffer_; }
void resetBuffer() { buffer_.reset(); }
private:
void staticCheck();
template<typename T>
void formatInteger(T);
Buffer buffer_;
static const int kMaxNumericSize = 32;
};
class Fmt // : noncopyable
{
public:
template<typename T>
Fmt(const char* fmt, T val);
const char* data() const { return buf_; }
int length() const { return length_; }
private:
char buf_[32];
int length_;
};
这里的Buffer只能存取4000字节的数据 因为数据在栈区 如果栈开的过大在多线程中容易爆栈 当需要存更多的数据时完全可以分配堆空间 先利用栈分配再利用堆 这是一个技巧
其中的模板函数formatInteger完成对诸如int long long int 的转换
函数定义如下
template<typename T>
void LogStream::formatInteger(T v)
{
if (buffer_.avail() >= kMaxNumericSize)
{
size_t len = convert(buffer_.current(), v);
buffer_.add(len);
}
}
其中kMaxNumricSize=32 当Buffer的剩余长度过小 则不再存入
日志类如下Logger
其中LogLevel表示日志的等级
enum LogLevel
{
TRACE,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
NUM_LOG_LEVELS,
};
SourceFile的构造函数传入当前所在的文件名
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的内部类Imp1 其中记录了当前条日志的时间 级别 文件位置 具体的行数
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_;
};
重点来了!!
#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流中 刚写完Logger便析构
’
由宏完成对Logger内部对象的构造 Imp1构造函数如下
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 << ") ";
}
}
其中FormatTime是对写时间的优化
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);
}
}
这里的t_lastSecond是线程本地数据 始终记录这上一次的毫秒之前的时间(包括毫秒) 当在同一毫秒中进行写日志时 只需要拼接比毫秒更加精细的时间 t_time也是一个线程本地数据
Logger的析构函数如下
Logger::~Logger()
{
impl_.finish();
const LogStream::Buffer& buf(stream().buffer());
g_output(buf.data(), buf.length());
if (impl_.level_ == FATAL)
{
g_flush();
abort();
}
}
在此前依次完成了将时间 线程Id 日志级别 数据写入LogStream流中 在析构的时候完成对文件名 以及行数的写入
void Logger::Impl::finish()
{
stream_ << " - " << basename_ << ':' << line_ << '\n';
}
其余值得关注的点是作者如何将日志输出到文件中,
下列一系列函数是对日志级别 输出地点的设置
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);
其中 以下两个函数则是默认写入的位置
void defaultOutput(const char* msg, int len)
{
size_t n = fwrite(msg, 1, len, stdout);
//FIXME check n
(void)n;
}
void defaultFlush()
{
fflush(stdout);
}
如何改变呢?通过设置g_output g_flush
其中g_output是函数指针 默认指向defaultOutput
通过注册自己的output函数 继而通过setOutput设置使得函数指针指向自己设置的函数 可以更改日志的写入位置
Logger::OutputFunc g_output = defaultOutput;
Logger::FlushFunc g_flush = defaultFlush;
TimeZone g_logTimeZone;
其中对这里每写一条日志则构造一次 析构一次不知道会不会写入大量的日志导致效率变慢呢
测试了以下100万次调用LOG_INFO 花费了大概0.5秒