首页 > 其他分享 >日志模块

日志模块

时间:2024-05-27 14:34:41浏览次数:28  
标签:std level fmt ptr 模块 日志 logger

介绍

在学习了sylar的C++高性能分布式服务器框架后,想把自己在学习过程中的感想记录下来。当然主要原因还是sylar的B站视频过于难以理解了,也是想加强一下自己对这个框架的理解。很多内容也是借鉴了其他大佬的博文,比如找人找不到北zhongluqiang

日志模块概述

日志模块的目的:

  • 用于格式化输出程序日志,方便从日志中定位程序运行过程中出现的问题。
  • 同时应该包括文件名/行号,时间戳,线程/协程号,模块名称,日志级别等额外信息。
  • 在打印致命的日志时,还应该附加程序的栈回溯信息,以便于分析和排查问题。

从设计上看,一个完整的日志模块应该具备以下功能:

  1. 区分不同的级别,比如常的DEBUG/INFO/WARN/ERROR等级别。

  2. 区分不同的输出地。不同的日志可以输出到不同的位置,比如可以输出到标准输出,输出到文件,输出到syslog,输出到网络上的日志服务器等,甚至同一条日志可以同时输出到多个输出地。

  3. 区分不同的类别。日志可以分类并命名,一个程序的各个模块可以使用不同的名称来输出日志,这样可以很方便地判断出当前日志是哪个程序模块输出的。

  4. 日志格式可灵活配置。可以按需指定每条日志是否包含文件名/行号、时间戳、线程/协程号、日志级别、启动时间等内容。

  5. 可通过配置文件的方式配置以上功能。

日志模块设计

类似于log4cpp,日志模块拥有以下几个主要类:

  • class LogLevel:定义日志级别。并提供将日志级别与文本之间的互相转化
  • class Logger:日志器。定义日志级别,设置输出地,设置日志格式。
  • class LogEvent:记录日志事件。主要记录一下信息
  • class LogEventWarp:日志事件包装器。将logEvent打包,可以直接通过使用该类完成对日志的定义。
  • class LogFormatter:日志格式化。
  • class LogAppender:日志输出目标。有两个子类 class StdoutLogAppender 和 class FileLogAppender,可以分别输出到控制台和文件
  • class LoggerManager:日志管理器。单例模式

具体实现

日志级别 class LogLevel

enum Level {
    /// 致命情况,系统不可用
    FATAL  = 0,
    /// 高优先级情况,例如数据库系统崩溃
    ALERT  = 100,
    /// 严重错误,例如硬盘错误
    CRIT   = 200,
    /// 错误
    ERROR  = 300,
    /// 警告
    WARN   = 400,
    /// 正常但值得注意
    NOTICE = 500,
    /// 一般信息
    INFO   = 600,
    /// 调试信息
    DEBUG  = 700,
    /// 未设置
    NOTSET = 800,
};
ToString(提供从日志级别 TO 文本的转换)

通过X宏(X Macros)将不同的级别放入switch case语句中。X宏的基本思想是将重复的代码片段定义为一个宏,然后在需要使用这些代码的地方多次调用这个宏。这样可以避免手动编写和维护大量重复代码。

const char* LogLevel::ToString(LogLevel::Level level){
    switch (level){ 
#define XX(name) \
        case LogLevel::name: \
            return #name; \
            break;
​
        XX(DEBUG);
        XX(INFO);
        XX(WARN);
        XX(ERROR);
        XX(FATAL);
#undef XX
​
        default:
            return "UNKNOW";
        }
        return "UNKNOW";
}

swtich (level):对传入的 level 参数进行 switch 分支判断。

//#define XX(name) ... #undef XX:定义了一个名为 XX 的宏,用于减少重复代码。宏 XX 接受一个参数 name,并生成对应的 case 语句。宏会展开成:

case LogLevel::DEBUG:
    return "DEBUG";
    break;
case LogLevel::INFO:
    return "INFO";
    break;
// 依次类推

注1:常见的X宏使用场景:
枚举与字符串映射:如将枚举值转换为字符串。
多次声明相似的代码结构:如函数声明、结构体初始化等。
生成重复的测试代码:如生成一系列测试用例。

FromString(提供从文本 To 日志级别的转换)

同样通过宏定义处理多种情况。转换时不针对大小写,DEBUG和debug都可以完成对应的转化

LogLevel::Level LogLevel::FromString(const std::string &str) {
#define XX(level, v)    \
    if(str == #v) { \
        return LogLevel::level; \
    }
    XX(DEBUG, debug);
    XX(INFO, info);
    XX(WARN, warn);
    XX(ERROR, error);
    XX(FATAL, fatal);
​
    XX(DEBUG, DEBUG);
    XX(INFO, INFO);
    XX(WARN, WARN);
    XX(ERROR, ERROR);
    XX(FATAL, FATAL);
​
    return LogLevel::UNKNOW;
#undef XX
}

日志事件 class LogEvent

用于记录日志现场,比如该日志的级别,文件名/行号,日志消息,线程/协程号,所属日志器名称等。

成员变量
    const char* m_file = nullptr;   //文件名
    int32_t m_line = 0;             //行号
    uint32_t m_elapse = 0;          //程序启动开始到现在的毫秒数
    uint32_t m_thieadId = 0;        //线程id
    uint32_t m_fiberId = 0;         //协程id
    uint64_t m_time;                //时间戳
    std::string m_threadName;       //线程名称
    std::stringstream m_ss;         //日志内容流
    std::shared_ptr<Logger> m_logger;   //日志器
    LogLevel::Level m_level;        //日志等级
成员函数
     /**
     * @brief 构造函数
     * 
     * @param[in] logger 日志器
     * @param[in] level 日志级别
     * @param[in] file 文件名
     * @param[in] line 文件行号
     * @param[in] elapse 程序启动依赖的耗时(毫秒)
     * @param[in] thread_id 线程id
     * @param[in] fiber_id 协程id
     * @param[in] time 日志时间
     * @param[in] thread_name 线程名称
     */
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_thieadId(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_list al;  		//1)
	va_start(al, fmt);	//2)
	format(fmt, al);	//3)
	va_end(al);			//6)
}

void LogEvent::format(const char* fmt, va_list al){
	char *buf = nullptr;
    // len返回写入buf的长度
	int len = vasprintf(&buf, fmt, al);	//4)
	if(len != -1) {
		m_ss << std::string(buf, len);	//5)
		free(buf);
	}
}

日志事件包装器 class LogEventWarp

日志事件包装类,其实就是将日志事件和日志器包装到一起,因为一条日志只会在一个日志器上进行输出。将日志事件和日志器包装到一起后,方便通过宏定义来简化日志模块的使用。另外,LogEventWrap还负责在构建时指定日志事件和日志器,在析构时调用日志器的log方法将日志事件进行输出。

// 日志事件
LogEvent::ptr m_event;

// 构造函数
LogEventWarp::LogEventWarp(LogEvent::ptr e)
	:m_event(e){
}

// 析构函数
LogEventWarp::~LogEventWarp() {
	m_event->getLogger()->log(m_event->getLevel(), m_event);
}

在此说一下使用日志的宏,这里定义了SYLAR_LOG_LEVEL宏,用来输出Level级别的LogEvent,并将LogEvent写入到Logger中。

#define SYLAR_LOG_LEVEL(logger, level) \
	if (logger->getLevel() <= level) \
		sylar::LogEventWarp(sylar::LogEvent::ptr (new sylar::LogEvent(logger, level, \
				__FILE__, __LINE__, 0, sylar::GetThreadId(), \
			sylar::GetFiberId(), time(0), sylar::Thread::GetName()))).getSS()

#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)

日志格式

日志内容格式化 class FormatItem

该类为LogFormatter的public内部类成员,通过该类得到解析后的格式。

此类为抽象类,不同事件的子类继承该类,并且重写纯虚函数format将日志格式转化到流

格式化日志到流

// 消息format
class MessageFormatItem : public LogFormatter::FormatItem{
public:
	MessageFormatItem(const std::string& str = "") {}
	void format(std::ostream& os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
		os << event->getContent(); 
	}
};

// 日志级别format
class LevelFormatItem : public LogFormatter::FormatItem{
public:
	LevelFormatItem(const std::string& str = "") {}
	void format(std::ostream& os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
		os << LogLevel::ToString(level);
	}
};

// 执行时间format
class ElapseFormatItem : public LogFormatter::FormatItem{
public:
	ElapseFormatItem(const std::string& str = "") {}
	void format(std::ostream& os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
		os << event->getElapse(); 
	}
};

// 日志器名称format
class NameFormatItem : public LogFormatter::FormatItem{
public:
	NameFormatItem(const std::string& str = "") {}
	void format(std::ostream& os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
		os << event->getLogger()->getName(); 
	}
};

// 线程id format
class ThreadIdFormatItem : public LogFormatter::FormatItem{
public:
	ThreadIdFormatItem(const std::string& str = "") {}
	void format(std::ostream& os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
		os << event->getThieadId(); 
	}
};

// 协程id format
class FiberIdFormatItem : public LogFormatter::FormatItem{
public:
	FiberIdFormatItem(const std::string& str = "") {}
	void format(std::ostream& os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
		os << event->getFiberId(); 
	}
};

// 线程名称format
class ThreadNameFormatItem : public LogFormatter::FormatItem{
public:
	ThreadNameFormatItem(const std::string& str = "") {}
	void format(std::ostream& os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
		os << event->getThreadName(); 
	}
};

// 时间format
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, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
		struct tm tm;
		time_t time = event->getTime();	//创建event时默认给的 time(0) 当前时间戳
		localtime_r(&time, &tm);	//将给定时间戳转换为本地时间,并将结果存储在tm中
		char buf[64];
		strftime(buf, sizeof(buf), m_format.c_str(), &tm);	//将tm格式化为m_format格式,并存储到buf中
		os << buf; 
	}
private:
	std::string m_format;
};
日志格式器 class LogFormatter

与log4cpp的PatternLayout对应,用于格式化一个日志事件。该类构建时可以指定pattern,表示如何进行格式化。提供format方法,用于将日志事件格式化成字符串。

// 成员变量
// 日志格式模板
std::string m_pattern;
// 日志格式解析后格式
std::vector<FormatItem::ptr> m_items;
// 判断日志格式错误
bool m_error = false;

// 构造函数
LogFormatter::LogFormatter(const std::string& pattern)
	:m_pattern(pattern) {
		init();
}

// 将解析后的日志信息输出到流中
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();
}

// init(解析格式)
// 得到相应FormatItem放入m_items
// 默认格式模板为:"%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
// e.g.Y-M-D H:M:S threadId threadName fiberId [Level] [logName] FILE:LINE message

//%xxx %xxx{xxx} %%
// m_pattern "%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(){ 
	//string, 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]);	//在nstr后面添加一个该字符
			continue;
		}

		if((i + 1) < m_pattern.size()) {	//保证m_pattern不越界
			if (m_pattern[i + 1] == '%') {	//解析 "%%"
				nstr.append(1, '%');		//在nstr后面加上%
				continue;
			}
        
												
		size_t n = i + 1;		//遇到'%'往下  (e.g.) n = 1, m_pattern[1] = 'd'
		int fmt_status = 0;		//状态1: 解析时间{%Y-%m-%d %H:%M:%S} 状态0:解析之后的
		size_t fmt_begin = 0;	//开始位置 为{

		std::string str;		//d T t N等格式
		std::string fmt;		//保存时间格式 %Y-%m-%d %H:%M:%S	

		while(n < m_pattern.size()){
            // fmt_status != 0, m_attern[n]不是字母,m_pattern[n]不是'{', m_pattern[n]不是'}'
            // (e.g.) %T%  (i -> %, n -> T, while循环 n -> % 此时解析完一个T, break
            // (e.g.) 遇到 [ ] break,取出[%p]中的p
			if(!fmt_status && (!isalpha(m_pattern[n]) && m_pattern[n] != '{' //返回0表示该字符不是字母字符。
					&& 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);	//str = "d"
					fmt_status = 1;	
					fmt_begin = n;
					++n;
					continue;
				}
			} else if(fmt_status == 1) {	//结束解析时间格式
				if(m_pattern[n] == '}') {
                    // fmt = %Y-%m-%d %H:%M:%S
					fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1);
					fmt_status = 0;
					++n;
					break;		//解析时间结束break
				}
			}
			++n;
			if (n == m_pattern.size()) {	//最后一个字符
				if (str.empty()) {
					str = m_pattern.substr(i + 1);
				}
			}
		}
		if(fmt_status == 0){
			if(!nstr.empty()){	// nstr: [ :
				vec.push_back(std::make_tuple(nstr, std::string(), 0));	// 将[ ]放入, type为0
				nstr.clear();
			}
			vec.push_back(std::make_tuple(str, fmt, 1));	//(e.g.) ("d", %Y-%m-%d %H:%M:%S, 1) type为1
			i = n - 1;	 //跳过已解析的字符,让i指向当前处理的字符,下个for循环会++i处理下个字符
		} else if(fmt_status == 1) {
			std::cout << "Pattern parde 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));	//(e.g.) 最后一个字符为[ ] :
	}
	
    // map类型为<string, cb>, string为相应的日志格式, cb返回相应的FormatItem智能指针
	static std::map<std::string, std::function<FormatItem::ptr(const std::string& fmt)> > 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) {	//若type为0
            //将解析出的FormatItem放到m_items中 [ ] :
			m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(i))));
		} else {	//type为1
			auto it = s_format_items.find(std::get<0>(i));	//从map中找到相应的FormatItem
			if(it == s_format_items.end()) {	//若没有找到则用StringFormatItem显示错误信息 并设置错误标志位
				m_items.push_back(FormatItem::ptr(new StringFormatItem("<<error_format %" + std::get<0>(i) + ">>")));
				m_error = true;
			} else {	//返回相应格式的FormatItem,其中std::get<1>(i)作为cb的参数
				m_items.push_back(it->second(std::get<1>(i)));
			}
		}
	}
}

日志输出

日志输出器 class LogAppender

class LogAppender是抽象类,有两个子类,分别为StdoutLogAppender和FileLogAppender,分别实现控制台和文件的输出。两个类都重写纯虚函数log方法实现写入日志,重写纯虚函数toYamlString方法实现将日志转化为YAML格式的字符串

成员变量

//日志级别
LogLevel::Level m_level = LogLevel::DEBUG;
//日志格式器
LogFormatter::ptr m_formatter;
// 互斥锁
MutexType m_mutex;
// 是否有formatter
bool m_hasFormatter = false;

成员函数

// setFormatter(更改日志格式器)
void LogAppender::setFormatter(LogFormatter::ptr val) {
	MutexType::Lock lock(m_mutex);
	m_formatter = val;
	if (m_formatter) {
		m_hasFormatter = true;
	} else {
		m_hasFormatter = false;
	}
}

// getFormatter(获得日志格式器)
LogFormatter::ptr LogAppender::getFormatter() {
	MutexType::Lock lock(m_mutex);
	return m_formatter;
}
class StdoutLogAppender(输出到控制台的Appender)
class StdoutLogAppender : public LogAppender {
public:
	typedef std::shared_ptr<StdoutLogAppender> ptr;
	void log(Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event){
        if(level >= m_level) {
            MutexType::Lock lock(m_mutex);
            std::cout << m_formatter->format(logger, level, event);	//这里调用Logformat的format,它会遍历m_items调用相应的format输出到流
        }
    }
	std::string toYamlString(){
        MutexType::Lock lock(m_mutex);
        YAML::Node node;
        node["type"] = "StdoutLogAppender";
        if(m_level != LogLevel::UNKNOW) {
            node["level"] = LogLevel::ToString(m_level);
        }
        if(m_hasFormatter && m_formatter) {
            node["formatter"] = m_formatter->getPattern();
        }
        std::stringstream ss;
        ss << node;
        return ss.str();
    }
};
class FileLogAppender(输出到文件的Appender)

mumber(成员变量)

// 文件路径
std::string m_filename;
// 文件流
std::ofstream m_filestream;
// 每秒reopen一次,判断文件有没有被删
uint64_t m_lastTime = 0;

成员函数

// 构造函数
FileLogAppender::FileLogAppender(const std::string& filename)
	:m_filename(filename){
		reopen();
}

// reopen(写入文件)
bool FileLogAppender::reopen(){
	MutexType::Lock lock(m_mutex);
	if (m_filestream){
		m_filestream.close();
	}

	m_filestream.open(m_filename, std::ios::app);	//以追加的方式写入文件中
	return !!m_filestream;
}
// log(输出到文件)
// 重写log方法,输出到文件
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
			reopen();
			m_lastTime = now;
		}
		MutexType::Lock lock(m_mutex);
		if (!(m_filestream << m_formatter->format(logger, level, event))) {		//写到m_filestream流中
			std::cout << "error" << std::endl;
		}
	}
}

// toYamlString(转化为YAML格式字符串)
// 重写toYamlString方法,转化为YAML格式字符串
std::string FileLogAppender::toYamlString() {
	MutexType::Lock lock(m_mutex);
	YAML::Node node;
	node["type"] = "FileLogAppender";
	node["file"] = m_filename;
	if(m_level != LogLevel::UNKNOW) {
		node["level"] = LogLevel::ToString(m_level);
	}
	if(m_hasFormatter && m_formatter) {
		node["formatter"] = m_formatter->getPattern();
	}
	std::stringstream ss;
	ss << node;
	return ss.str();
}

日志器 class Logger

负责进行日志输出。一个Logger包含多个LogAppender和一个日志级别,提供log方法,传入日志事件,判断该日志事件的级别高于日志器本身的级别之后调用LogAppender将日志进行输出,否则该日志被抛弃。

成员变量
//日志名称
std::string m_name;
//日志级别
LogLevel::Level m_level;
// 互斥锁
MutexType m_mutex;
// 日志目标集合
std::list<LogAppender::ptr> m_appenders;
//日志格式器
LogFormatter::ptr m_formatter;
// root Log
Logger::ptr m_root;
成员函数
// Logger(构造函数)
// 名称,def = root
// 日志级别, def = DEBUG
// 日志格式, def = "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
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%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"));
}

// log(写不同Level日志到日志目标)
// m_appenders为日志目标地,将当前logger输出到相应的appender,因为Appender的log要传入logger的智能指针,所以使用shared_from_this()获得当前logger的智能指针
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) {		//当logger的appenders为空时,使用root写logger
			m_root->log(level, event);
		}
	}	
}

// addAppender(添加日志目标)
// 若appender没有formatter的话就将默认formatter赋给他,若有formatter则直接添加到m_appenders队列中
void Logger::addAppender(LogAppender::ptr appender){
		MutexType::Lock lock(m_mutex);
		if (!appender->getFormatter()) {
			MutexType::Lock ll(appender->m_mutex);
			appender->m_formatter = m_formatter;
		}
	m_appenders.push_back(appender);
}

// delAppender(删除日志目标)
// 在m_appenders中找到要删除的appender,erase掉
void Logger::delAppender(LogAppender::ptr appender){
	MutexType::Lock lock(m_mutex);
	for (auto it = m_appenders.begin();
		it != m_appenders.end(); ++it) {
	     if(*it == appender) {
	     	m_appenders.erase(it);
		break;
	     }
	}
}

// setFormatter(通过智能指针 )
// 将新的formatter赋给m_formatter,若appender没有formatter,则将appender的formatter更新。
void Logger::setFormatter(LogFormatter::ptr val){
	MutexType::Lock lock(m_mutex);
	m_formatter = val;

	for (auto& i : m_appenders) {
		MutexType::Lock ll(i->m_mutex);
		if (!i->m_hasFormatter) {
			i->m_formatter = m_formatter;
		}
	}
}

// setFormatter(通过字符串)
// new一个新的formatter,若格式没错,调用上面的setFormatter设置Formatter。
void Logger::setFormatter(const std::string &val){
	sylar::LogFormatter::ptr new_val(new sylar::LogFormatter(val));
	if (new_val->isError()) {
		 std::cout << "Logger setFormatter name = " << m_name
				   << "value = " << val << "invalid formatter"
				   << std::endl;
		 return;
	}
	// m_formatter = new_val;
	setFormatter(new_val);
}

// toYamlString(转换为YAML格式输出)
// 将当前logger name,level,formatter,appenders YAML格式按流输出
std::string Logger::toYamlString() {
	MutexType::Lock lock(m_mutex);
	YAML::Node node;
	node["name"] = m_name;
	if(m_level != LogLevel::UNKNOW) {
		node["level"] = LogLevel::ToString(m_level);
	}
	if (m_formatter) {
		node["formatter"] = m_formatter->getPattern();
	}
	for (auto& i : m_appenders) {
		node["appenders"].push_back(YAML::Load(i->toYamlString()));
	}
	std::stringstream ss;
	ss << node;
	return ss.str();
}

日志管理器 class LoggerManager

单例模式,用于统一管理所有的日志器,提供日志器的创建与获取方法。LogManager自带一个root Logger,用于为日志模块提供一个初始可用的日志器。

typedef sylar::Singleton<LoggerManager> LoggerMgr;
成员变量
// 互斥锁
MutexType m_mutex;
// 日志器容器
std::map<std::string, Logger::ptr> m_loggers;
// 主日志器
Logger::ptr m_root;
成员函数
// LoggerManager(构造函数)
LoggerManager::LoggerManager() {
	m_root.reset(new Logger);
	m_root->addAppender(LogAppender::ptr(new StdoutLogAppender));

	m_loggers[m_root->m_name] = m_root;
}

// getLogger(获取日志器)
// 在map中找到相应的logger就返回他,若没有就创建一个logger并将他放到日志器容器m_loggers中,再返回他
Logger::ptr LoggerManager::getLogger(const std::string& name) {
	MutexType::Lock lock(m_mutex);
	auto it = m_loggers.find(name);
	if (it != m_loggers.end()) {
		return it->second;
	}
	Logger::ptr logger(new Logger(name));
	logger->m_root = m_root;	//将logger的root赋值,当没有appender时,使用root写logger
	m_loggers[name] = logger;
	return logger;
}

// toYamlString(将日志格式转化为YAML字符串)
std::string LoggerManager::toYamlString() {
	MutexType::Lock lock(m_mutex);
	YAML::Node node;
	for (auto& i : m_loggers) {
		node.push_back(YAML::Load(i.second->toYamlString()));
	}
	std::stringstream ss;
	ss << node;
	return ss.str();
}

宏定义

使用流的方式,将不同日志级别的事件写入logger中
#define SYLAR_LOG_LEVEL(logger, level) \
	if (logger->getLevel() <= level) \
		sylar::LogEventWarp(sylar::LogEvent::ptr (new sylar::LogEvent(logger, level, \
				__FILE__, __LINE__, 0, sylar::GetThreadId(), \
			sylar::GetFiberId(), time(0), sylar::Thread::GetName()))).getSS()

#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
#define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)
#define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)
#define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)
#define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)
使用格式化方式, 将不同日志级别的事件写入logger中
#define SYLARY_LOG_FMT_LEVEL(logger, level, fmt, ...) \
	if (logger->getLevel() <= level) \
		sylar::LogEventWarp(sylar::LogEvent::ptr(new sylar::LogEvent(logger, level, \
							__FILE__, __LINE__, 0, sylar::GetThreadId(), \
					sylar::GetFiberId(), time(0), sylar::Thread::GetName()))).getEvent()->format(fmt, __VA_ARGS__)
#define SYLARY_LOG_FMT_DEBUG(logger, fmt, ...) SYLARY_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, fmt, __VA_ARGS__)
#define SYLARY_LOG_FMT_INFO(logger, fmt, ...) SYLARY_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, fmt, __VA_ARGS__)
#define SYLARY_LOG_FMT_WARN(logger, fmt, ...) SYLARY_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, fmt, __VA_ARGS__)
#define SYLARY_LOG_FMT_ERROR(logger, fmt, ...) SYLARY_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, fmt, __VA_ARGS__)
#define SYLARY_LOG_FMT_FATAL(logger, fmt, ...) SYLARY_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, fmt, __VA_ARGS__)
获得主日志器
#define SYLAR_LOG_ROOT() sylar::LoggerMgr::GetInstance()->getRoot()
获得相应名字的日志器
#define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger(name)

总结

总结一下日志模块的工作流程:

  1. 初始化LogFormatter,LogAppender, Logger。

  2. 通过宏定义提供流式风格和格式化风格的日志接口。每次写日志时,通过宏自动生成对应的日志事件LogEvent,并且将日志事件和日志器Logger包装到一起,生成一个LogEventWrap对象。

  3. 日志接口执行结束后,LogEventWrap对象析构,在析构函数里调用Logger的log方法将日志事件进行输出。

待补充与完善

目前来看,sylar日志模块已经实现了一个完整的日志框架,并且配合后面的配置模块,可用性很高,待补充与完善的地方主要存在于LogAppender,目前只提供了输出到终端与输出到文件两类LogAppender,但从实际项目来看,以下几种类型的LogAppender都是非常有必要的:

  1. Rolling File Appender,循环覆盖写文件
  2. Rolling Memory Appender,循环覆盖写内存缓冲区
  3. 支持日志文件按大小分片或是按日期分片
  4. 支持网络日志服务器,比如syslog

标签:std,level,fmt,ptr,模块,日志,logger
From: https://www.cnblogs.com/zjq1999/p/18215424

相关文章

  • BOSHIDA AC/DC电源模块:高质量的电力转换解决方案
    BOSHIDAAC/DC电源模块:高质量的电力转换解决方案AC/DC电源模块是一种电力转换器件,可以将交流电转换为直流电。它通常用于各种电子设备和系统中,提供高质量的电力转换解决方案。 AC/DC电源模块具有许多优点。首先,它能够提供稳定的电流和电压输出,确保设备能够正常工作。这对于需......
  • 记录接口操作日志, 原始数据和变更数据 这几个工具可以看看
    mzt-biz-log支持Springboot,基于注解的可使用变量、可以自定义函数的通用操作日志组件此组件解决的问题是:「谁」在「什么时间」对「什么」做了「什么事」Github地址:https://github.com/mouzt/mzt-biz-logMyBatis-Plus数据变动记录插件https://baomidou.com/plugins/data-ch......
  • logging 模块
    logging模块的核心点主要包括以下几个方面:基本配置、日志级别、日志记录器(Logger)、处理器(Handler)、格式器(Formatter)和过滤器(Filter)当然可以,系统学习logging模块的核心点主要包括以下几个方面:基本配置、日志级别、日志记录器(Logger)、处理器(Handler)、格式器(Formatter)和过滤器(Fil......
  • 万字详解YOLOv8网络结构Backbone/neck/head以及Conv、Bottleneck、C2f、SPPF、Detect
    YOLO目标检测创新改进与实战案例专栏目录:YOLO有效改进系列及项目实战目录包含卷积,主干注意力,检测头等创新机制以及各种目标检测分割项目实战案例简介YOLOv8是由Ultralytics开发的最先进的目标检测模型,推升了速度、准确性和用户友好性的界限。YOLO这一缩写代表“你......
  • 创新实训2024.05.26日志:落地基于硬盘的数据库服务
    1.需求任务列表以下描述易学大模型软件的web应用的功能。用户注册用户邮箱,密码,验证码开启官方邮箱,用来发验证码(QQ网易都支持开启smtp协议,找教程,用邮箱不用手机号是为了省买发短信云服务的钱)验证码缓存于redis,5min内有效验证密码长度,验证码是否正确新用户信息保存于mysq......
  • 【Python快速上手(三十)】- 详解Python random 模块和 statistics 模块
    目录Python快速上手(三十)-详解Pythonrandom模块和statistics模块1.Pythonrandom模块1.1生成随机数1.2随机选择和打乱1.3随机分布1.4种子和状态2.Pythonstatistics模块2.1均值和中位数2.2众数2.3方差和标准差2.4协方差和相关性2.5分位数和百分位数2.6......
  • ELK+kafka+filebeat企业内部日志分析系统
    1、组件介绍1、Elasticsearch:  是一个基于Lucene的搜索服务器。提供搜集、分析、存储数据三大功能。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTfulweb接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计......
  • 设计一个代办功能模块
    目录1.需求分析2.数据库设计用户表(UsersTable)代办任务表(TasksTable)订单表(OrdersTable)评价表(ReviewsTable)3.功能实现创建代办任务前端部分后端部分接受代办任务前端部分后端部分完成代办任务前端部分后端部分支付代办任务前端部分后端部分评价代办任务前端部......
  • 关于Undertow启动时的警告日志
    错误提示:当使用Undertow作为SpringBoot嵌入式服务器时,启动应用。会看到有一条 WARN 日志,如下:UT026010:BufferpoolwasnotsetonWebSocketDeploymentInfo,thedefaultpoolwillbeused大致意思是“没有给WebSocketDeploymentInfo设置Bufferpool,将会使用默......
  • YOLOv5/v7 引入 YOLOv8 的 C2f 模块
    1.介绍YOLOv8是Ultralytics团队于2022年10月发布的最新一代目标检测模型。YOLOv8在YOLOv7的基础上进行了多项改进,包括引入C2f模块、改进PathAggregationNetwork(PAN)结构、优化LabelAssigning算法等。C2f模块是YOLOv8中引入的主要创新之一。C2f模块......