log日志模块
以下是从sylar服务器中学的,对其的复习;
1.日志器Logger
日志器,包含一个日志格式器m_formatter,一个root Logger,N个LogAppender
提供日志写入方法。根据日志器的配置格式和内容。将日志写到对应的地方
- 智能指针share_ptr的使用
- std::enable_shared_from_this类
class Logger : public std::enable_shared_from_this<Logger> {
friend class LoggerManager;
public:
typedef std::shared_ptr<Logger> ptr;
typedef Spinlock MutexType;
Logger(const std::string& name = "root");
// 写入日志,指定日志级别
void log(LogLevel::Level level, LogEvent::ptr event);
// 写debug日志
void debug(LogEvent::ptr event);
// 写info日志
void info(LogEvent::ptr event);
// 写warn日志
void warn(LogEvent::ptr event);
// 写error日志
void error(LogEvent::ptr event);
// 写fatal日志
void fatal(LogEvent::ptr event);
// 添加appender
void addAppender(LogAppender::ptr appender);
// 删除appender
void delAppender(LogAppender::ptr appender);
// 清空appender
void clearAppenders();
LogLevel::Level getLevel() const { return m_level;}
// 设置日志级别
void setLevel(LogLevel::Level val) { m_level = val;}
// 获取日志名称
const std::string& getName() const { return m_name;}
// 设置日志格式
void setFormatter(LogFormatter::ptr val);
// 设置文本日志格式
void setFormatter(const std::string& val);
LogFormatter::ptr getFormatter();
// 转成Yaml格式的配置文本
std::string toYamlString();
private:
std::string m_name;
//日志级别,低于该级别不会输出
LogLevel::Level m_level;
MutexType m_mutex;
//appender集合
std::list<LogAppender::ptr> m_appenders;
//日志格式
LogFormatter::ptr m_formatter;
//主日志器,如果当前日志未定义,使用主日志器输出
Logger::ptr m_root;
};
1.1 关键实现
//构造函数:
Logger::Logger(const std::string& name)
:m_name(name)
,m_level(LogLevel::DEBUG) {
m_formatter.reset(new LogFormatter("%d{%Y-%m-%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"));
}
//log:遍历m_appenders输出
void Logger::log(LogLevel::Level level, LogEvent::ptr event) {
if(level >= m_level) {
auto self = shared_from_this();
MutexType::Lock lock(m_mutex);
if(!m_appenders.empty()) {
for(auto& i : m_appenders) {
i->log(self, level, event);
}
} else if(m_root) {
m_root->log(level, event);
}
}
}
2.日志输出地LogAppender
日志落地点抽象。目前只要实现了输出到控制台(StdoutLogAppender)和输出到文件(FileLogAppender),LogAppender可以拥有自己的LogFormat。
一个日志器,可以对应多个LogAppender。也就是说写一条日志,可以落到多个输出,并且每个输出的格式都可以不一样。
Appender有单独的日志级别,可以自定义不同级别的日志,输出到不同的Appender,常用于将错误日志统一输出到一个地方。
以后可以通过扩展LogAppender,实现向日志服务器写日志(利用socket)
- yaml-cpp使用
- 线程同步、线程安全
class LogAppender {
friend class Logger;
public:
typedef std::shared_ptr<LogAppender> ptr;
typedef Spinlock MutexType;
virtual ~LogAppender() {}
//将日志输出到对应的落地点
virtual void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) = 0;
//将日志落地点输出成Yaml格式的配置
virtual std::string toYamlString() = 0;
void setFormatter(LogFormatter::ptr val);
LogFormatter::ptr getFormatter();
LogLevel::Level getLevel() const { return m_level;}
void setLevel(LogLevel::Level val) { m_level = val;}
protected:
LogLevel::Level m_level = LogLevel::DEBUG;
bool m_hasFormatter = false;
MutexType m_mutex;
//日志格式器
LogFormatter::ptr m_formatter;
};
//输出到控制台
class StdoutLogAppender : public LogAppender {
public:
typedef std::shared_ptr<StdoutLogAppender> ptr;
void log(Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override;
std::string toYamlString() override;
};
//输出到日志文件
class FileLogAppender : public LogAppender {
public:
typedef std::shared_ptr<FileLogAppender> ptr;
FileLogAppender(const std::string& filename);
void log(Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override;
std::string toYamlString() override;
bool reopen();
private:
//文件名
std::string m_filename;
std::ofstream m_filestream;
uint64_t m_lastTime = 0;
};
2.1关键实现
//输出到标准输出
void StdoutLogAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
if(level >= m_level) {
MutexType::Lock lock(m_mutex);
std::cout << m_formatter->format(logger, level, event);
}
}
//输出到文件
void FileLogAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
if(level >= m_level) {
uint64_t now = time(0);
if(now != m_lastTime) {
reopen();
m_lastTime = now;
}
MutexType::Lock lock(m_mutex);
if(!(m_filestream << m_formatter->format(logger, level, event))) {
std::cout << "error" << std::endl;
}
}
}
3.日志格式器LogFormat
日志格式器,执行日志格式化,负责日志格式的初始化。
解析日志格式,将用户自定义的日志格式,解析为对应的FormatItem。
- 设计模式:策略模式
- 纯虚函数
class LogFormatter {
public:
/**
* @brief 构造函数
* @param[in] pattern 格式模板
* @details
* %m 消息
* %p 日志级别
* %r 累计毫秒数
* %c 日志名称
* %t 线程id
* %n 换行
* %d 时间
* %f 文件名
* %l 行号
* %T 制表符
* %F 协程id
* %N 线程名称
*
* 默认格式 "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
*/
LogFormatter(const std::string& pattern);
/**
* 返回格式化日志文本
* @param[in] logger 日志器
* @param[in] level 日志级别
* @param[in] event 日志事件
*/
std::string format(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event);
std::ostream& format(std::ostream& ofs, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event);
public:
/**
* 日志内容项格式化
*/
class FormatItem {
public:
typedef std::shared_ptr<FormatItem> ptr;
virtual ~FormatItem() {}
/**
* @brief 格式化日志到流
* @param[in, out] os 日志输出流
* @param[in] logger 日志器
* @param[in] level 日志等级
* @param[in] event 日志事件
*/
//纯虚函数
virtual void format(std::ostream& os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) = 0;
};
/**
* 初始化,解析日志模板, 做m_pattern的解析
*/
void init();
private:
/// 日志格式模板
std::string m_pattern;
/// 日志格式解析后格式
std::vector<FormatItem::ptr> m_items;
/// 是否有错误
bool m_error = false;
};
3.1关键实现
/** [%m] 消息*/
class MessageFormatItem : public LogFormatter::FormatItem {
public:
MessageFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << event->getContent();
}
};
/** [%p] 日志级别 */
class LevelFormatItem : public LogFormatter::FormatItem {
public:
LevelFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << LogLevel::ToString(level);
}
};
/** [%r] 累计毫秒数 */
class ElapseFormatItem : public LogFormatter::FormatItem {
public:
ElapseFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << event->getElapse();
}
};
/** [%c] 日志名称 */
class NameFormatItem : public LogFormatter::FormatItem {
public:
NameFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << event->getLogger()->getName();
}
};
/** [%t] 线程id */
class ThreadIdFormatItem : public LogFormatter::FormatItem {
public:
ThreadIdFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << event->getThreadId();
}
};
/** [%F] 协程id */
class FiberIdFormatItem : public LogFormatter::FormatItem {
public:
FiberIdFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << event->getFiberId();
}
};
/** [%N] 线程名称 */
class ThreadNameFormatItem : public LogFormatter::FormatItem {
public:
ThreadNameFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << event->getThreadName();
}
};
/** [%d] 时间 */
class DateTimeFormatItem : public LogFormatter::FormatItem {
public:
DateTimeFormatItem(const std::string& format = "%Y-%m-%d %H:%M:%S")
:m_format(format) {
if(m_format.empty()) {
m_format = "%Y-%m-%d %H:%M:%S";
}
}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
struct tm tm;
time_t time = event->getTime();
//localtime_r 用于linux平台下获取系统时间
localtime_r(&time, &tm);
char buf[64];
//strftime 根据format中定义的格式化规则,格式化结构tm表示的时间,并把它存储在buf中。
strftime(buf, sizeof(buf), m_format.c_str(), &tm);
os << buf;
}
private:
std::string m_format;
};
/** [%f] 文件名 */
class FilenameFormatItem : public LogFormatter::FormatItem {
public:
FilenameFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << event->getFile();
}
};
/** [%l] 行号 */
class LineFormatItem : public LogFormatter::FormatItem {
public:
LineFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << event->getLine();
}
};
/** [%n] 换行 */
class NewLineFormatItem : public LogFormatter::FormatItem {
public:
NewLineFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << std::endl;
}
};
class StringFormatItem : public LogFormatter::FormatItem {
public:
StringFormatItem(const std::string& str)
:m_string(str) {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << m_string;
}
private:
std::string m_string;
};
/** [%T] 制表符 */
class TabFormatItem : public LogFormatter::FormatItem {
public:
TabFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << "\t";
}
};
/** 构造函数,先初始化解析parttern */
LogFormatter::LogFormatter(const std::string& pattern)
:m_pattern(pattern) {
init();
}
/** 执行每个m_iterms内的子类中的format,让其内容都添加到ss中 */
std::string LogFormatter::format(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
std::stringstream ss;
for (auto& i : m_items) {
i->format(ss, logger, level, event);
}
return ss.str();
}
/** 与上一个函数功能类似,只是输出到ostream */
std::ostream& LogFormatter::format(std::ostream& ofs, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
for (auto& i : m_items) {
i->format(ofs, logger, level, event);
}
return ofs;
}
// %xxx %xxx{xxx} %%
// "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
// 解析日志格式
void LogFormatter::init() {
//str, format, type
std::vector<std::tuple<std::string, std::string, int> > vec;
std::string nstr;
for (size_t i = 0; i < m_pattern.size(); ++i) {
if (m_pattern[i] != '%') {
nstr.append(1, m_pattern[i]);
continue;
}
if ((i + 1) < m_pattern.size()) {
if (m_pattern[i + 1] == '%') {
nstr.append(1, '%');
continue;
}
}
size_t n = i + 1;
int fmt_status = 0;
size_t fmt_begin = 0;
std::string str;
std::string fmt;
while (n < m_pattern.size()) {
if (!fmt_status && (!isalpha(m_pattern[n]) && m_pattern[n] != '{'
&& m_pattern[n] != '}')) {
str = m_pattern.substr(i + 1, n - i - 1);
break;
}
if (fmt_status == 0) {
if (m_pattern[n] == '{') {
str = m_pattern.substr(i + 1, n - i - 1);
fmt_status = 1; //解析格式
fmt_begin = n;
++n;
continue;
}
}
else if (fmt_status == 1) {
if (m_pattern[n] == '}') {
fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1);
fmt_status = 0;
++n;
break;
}
}
++n;
if (n == m_pattern.size()) {
if (str.empty()) {
str = m_pattern.substr(i + 1);
}
}
}
if (fmt_status == 0) {
if (!nstr.empty()) {
vec.push_back(std::make_tuple(nstr, std::string(), 0));
nstr.clear();
}
vec.push_back(std::make_tuple(str, fmt, 1));
i = n - 1;
}
else if (fmt_status == 1) {
std::cout << "pattern parse error: " << m_pattern << " - " << m_pattern.substr(i) << std::endl;
m_error = true;
vec.push_back(std::make_tuple("<<pattern_error>>", fmt, 0));
}
}
if (!nstr.empty()) {
vec.push_back(std::make_tuple(nstr, "", 0));
}
static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)> > s_format_items = {
#define XX(str, C) \
{#str, [](const std::string& fmt) { return FormatItem::ptr(new C(fmt));}}
XX(m, MessageFormatItem), //m:消息
XX(p, LevelFormatItem), //p:日志级别
XX(r, ElapseFormatItem), //r:累计毫秒数
XX(c, NameFormatItem), //c:日志名称
XX(t, ThreadIdFormatItem), //t:线程id
XX(n, NewLineFormatItem), //n:换行
XX(d, DateTimeFormatItem), //d:时间
XX(f, FilenameFormatItem), //f:文件名
XX(l, LineFormatItem), //l:行号
XX(T, TabFormatItem), //T:Tab
XX(F, FiberIdFormatItem), //F:协程id
XX(N, ThreadNameFormatItem), //N:线程名称
#undef XX
};
for (auto& i : vec) {
if (std::get<2>(i) == 0) {
m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(i))));
}
else {
auto it = s_format_items.find(std::get<0>(i));
if (it == s_format_items.end()) {
m_items.push_back(FormatItem::ptr(new StringFormatItem("<<error_format %" + std::get<0>(i) + ">>")));
m_error = true;
}
else {
m_items.push_back(it->second(std::get<1>(i)));
}
}
}
}
4.日志事件LogEvent
日志事件的封装,将要写的日志,填充到LogEvent中。填充完毕之后,写入到对应的logger中
- 变长模板va_list
class LogEvent{
public:
void format(const char* fmt, ...);
void format(const char* fmt, va_list al);
private:
/// 文件名
const char* m_file = nullptr;
/// 行号
int32_t m_line = 0;
/// 程序启动开始到现在的毫秒数
uint32_t m_elapse = 0;
/// 线程ID
uint32_t m_threadId = 0;
/// 协程ID
uint32_t m_fiberId = 0;
/// 时间戳
uint64_t m_time = 0;
/// 线程名称
std::string m_threadName;
/// 日志内容流
std::stringstream m_ss;
/// 日志器
std::shared_ptr<Logger> m_logger;
/// 日志等级
LogLevel::Level m_level;
}
4.1关键实现
LogEvent::LogEvent(std::shared_ptr<Logger> logger, LogLevel::Level level
,const char* file, int32_t line, uint32_t elapse
,uint32_t thread_id, uint32_t fiber_id, uint64_t time
,const std::string& thread_name)
:m_file(file)
,m_line(line)
,m_elapse(elapse)
,m_threadId(thread_id)
,m_fiberId(fiber_id)
,m_time(time)
,m_threadName(thread_name)
,m_logger(logger)
,m_level(level) {
}
void LogEvent::format(const char* fmt, ...) {
//va_start、va_end成对出现
va_list al; //val_list 用于获取不确定个数的参数
va_start(al, fmt);
format(fmt, al);
va_end(al);
}
/** 将内容都添加到m_ss中 */
void LogEvent::format(const char* fmt, va_list al) {
char* buf = nullptr;
int len = vasprintf(&buf, fmt, al);
if(len != -1) {
m_ss << std::string(buf, len);
free(buf);
}
}
5.日志级别LogLevel
- enum
- static
- 宏定义
class LogLevel{
public:
enum Level {
/// 未知级别
UNKNOW = 0,
/// DEBUG: 记录有关通过系统的流量的详细信息,应用程序记录的大多数为 DEBUG;
DEBUG = 1,
/// INFO:运行时事件(启动/关闭);
INFO = 2,
/// WARN:使用已弃用的 API、API 使用不当、“几乎”错误、其他不合需要或意外但不一定“错误”的运行时情况;
WARN = 3,
/// ERROR:其他运行时错误或意外情况
ERROR = 4,
/// FATAL:导致提前终止的严重错误
FATAL = 5
};
/** Level转字符串 */
static const char* ToString(LogLevel::Level level);
/** 字符串转Level */
static LogLevel::Level FromString(const std::string& str);
};
6.日志管理器LoggerManager
管理所有的日志器,并且可以通过解析Yaml配置,动态创建或修改日志器相关的内容(日志级别,日志格式,输出落地点等等)
- 设计模式:单例模式
class LoggerManager {
public:
typedef Spinlock MutexType;
LoggerManager();
// 获取名称为name的日志器
// 如果name不存在,则创建一个,并使用root配置
Logger::ptr getLogger(const std::string& name);
void init();
Logger::ptr getRoot() const { return m_root;}
// 转成yaml格式的配置
std::string toYamlString();
private:
MutexType m_mutex;
// 所有日志器
std::map<std::string, Logger::ptr> m_loggers;
// 主日志器(默认日志器)
Logger::ptr m_root;
};
typedef sylar::Singleton<LoggerManager> LoggerMgr;