日志级别
- TRACE:比DEBUG更详细(开发过程中使用)
- DEBUG:较详细(开发过程中使用)
- INFO:粗粒度,突出运行过程
- WARN:警告。可运行,但可能有潜在错误
- ERROR:发生错误,但不影响系统继续运行
- FATAL:严重错误,导致应用程序退出
日志级别可更改,以适应不同阶段开发。
//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()
//Logging.h
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)
{
...
}
explicit SourceFile(const char* filename)
: data_(filename)
{
...
}
};
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_; }
...
private:
class Impl
{
...
LogStream stream_;
};
Impl impl_;
};
//LogStream.h
class LogStream : noncopyable
{
public:
//存放日志的缓冲区
typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer;
...
}
- Logger : 日志类
- Impl:实际实现的类(日志信息)
- LogStream : 流的封装,存放缓冲区,重载 << 符号
运行流程:
Logger(Impl) --> LogStream --> 重载<<输出到缓冲区FixedBuffer --> g_output(输出到设备、标准输出、文件等) --> g_flush
//Logging.cc
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::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line)
: time_(Timestamp::now()),
stream_(),
level_(level),
line_(line),
basename_(file)
{
...
stream_ << T(CurrentThread::tidString(), CurrentThread::tidStringLength());
stream_ << T(LogLevelName[level], 6);
...
}
Logger::~Logger()
{
impl_.finish();
//引用缓冲区,避免拷贝内存
const LogStream::Buffer& buf(stream().buffer());
//将缓冲区内容输出到设备或其他
g_output(buf.data(), buf.length());
if (impl_.level_ == FATAL)
{
g_flush();
abort();
}
}
(1)日志使用方法:语句“LOG_*** << str;” 在宏定义中,直接构造了匿名 Logger对象,并调用stream();
(2)在Logger类的构造函数中,会构造Impl对象,调用Impl构造函数,在其 中将日志部分信息(时间,tid等)写入缓冲;
(3)当LOG_**语句执行后,返回LogStream对象,LogStream重载了<<符号,所以将str格式化写入缓冲;
(4)语句结束时,调用Logger析构函数,由缓冲区数据写入设备或标准输出。
//Logging.cc
//g_output(buf.data(), buf.length());析构函数中执行
//Logging.h
//定义了一个函数指针
typedef void (*OutputFunc)(const char* msg, int len);
//默认为标准输出
Logger::OutputFunc g_output = defaultOutput;
void defaultOutput(const char* msg, int len)
{
size_t n = fwrite(msg, 1, len, stdout);
//FIXME check n
(void)n;
}
- 缓冲区类
//LogStream.h
template<int SIZE>
class FixedBuffer : noncopyable
{
public:
FixedBuffer() : cur_(data_) {}
~FixedBuffer(){}
void append(const char* /*restrict*/ buf, size_t len)
{
//implicit_cast<ToType>(expr)
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* memset(void* s, int ch, size_t n):将s中当前位置后面的n个字节用ch替换并返回s
void bzero() { memset(data_, 0, sizeof data_); }
// for used by GDB
const char* debugString(){ //将data_作为字符串处理。字符数组和字符串的区别
*cur_ = '\0';
return data_;
};
...
private:
//end() 指向字符数组末尾的下一位置(并非数据末尾)
const char* end() const { return data_ + sizeof data_; }
...
char data_[SIZE];
char* cur_;
};
data_指向首字节,cur_指向末尾数据的下一字节
- LogStream类
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<<(const void*);
self& operator<<(float v)
{
*this << static_cast<double>(v);
return *this;
}
self& operator<<(double);
self& operator<<(char v)
{
buffer_.append(&v, 1);
return *this;
}
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 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:
template<typename T>
void formatInteger(T);
Buffer buffer_;
static const int kMaxNumericSize = 32;
}
//LogStream.cc
- 将整数放入缓冲区(为什么不直接用snprintf?)
template<typename T>
void LogStream::formatInteger(T v)
{
if (buffer_.avail() >= kMaxNumericSize)
{
size_t len = convert(buffer_.current(), v);
buffer_.add(len);
}
}
//将整数转化为字符(字节)数组的函数,以便放入缓冲区
const char digits[] = "9876543210123456789";
const char* zero = digits + 9; //zero指向'0'
template<typename T>
size_t convert(char buf[], T value)
{
T i = value;
char* p = buf;
do //将数字转换为字符
{
int lsd = static_cast<int>(i % 10);//取个位数
i /= 10;//i去掉个位数
*p++ = zero[lsd];//detail::zero,索引是几就偏移几位
} while (i != 0);
if (value < 0)
{
*p++ = '-';
}
*p = '\0';
//例:“-123”,存进去为“321-”,需反转
std::reverse(buf, p); //接受参数:begin(),end()(不包含结束指针指向字符)
return p - buf;
}
- Fmt类:将T类型格式化存储(应对不是直接存储数据的需求)
//LogStream.h
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_;
};
template<typename T>
Fmt::Fmt(const char* fmt, T val)
{
//断言T是算数类型
static_assert(std::is_arithmetic<T>::value == true, "Must be arithmetic type");
length_ = snprintf(buf_, sizeof buf_, fmt, val);
assert(static_cast<size_t>(length_) < sizeof buf_);
}
template Fmt::Fmt(const char* fmt, char);
template Fmt::Fmt(const char* fmt, int);
...
inline LogStream& operator<<(LogStream& s, const Fmt& fmt)
{
s.append(fmt.data(), fmt.length());
return s;
}