日志模塊
介紹
在學(xué)習(xí)了sylar的C++高性能分布式服務(wù)器框架后,想把自己在學(xué)習(xí)過(guò)程中的感想記錄下來(lái)。當(dāng)然主要原因還是sylar的B站視頻過(guò)于難以理解了,也是想加強(qiáng)一下自己對(duì)這個(gè)框架的理解。很多內(nèi)容也是借鑒了其他大佬的博文,比如找人找不到北,zhongluqiang
日志模塊概述
日志模塊的目的:
- 用于格式化輸出程序日志,方便從日志中定位程序運(yùn)行過(guò)程中出現(xiàn)的問(wèn)題。
- 同時(shí)應(yīng)該包括文件名/行號(hào),時(shí)間戳,線程/協(xié)程號(hào),模塊名稱(chēng),日志級(jí)別等額外信息。
- 在打印致命的日志時(shí),還應(yīng)該附加程序的棧回溯信息,以便于分析和排查問(wèn)題。
從設(shè)計(jì)上看,一個(gè)完整的日志模塊應(yīng)該具備以下功能:
-
區(qū)分不同的級(jí)別,比如常的DEBUG/INFO/WARN/ERROR等級(jí)別。
-
區(qū)分不同的輸出地。不同的日志可以輸出到不同的位置,比如可以輸出到標(biāo)準(zhǔn)輸出,輸出到文件,輸出到syslog,輸出到網(wǎng)絡(luò)上的日志服務(wù)器等,甚至同一條日志可以同時(shí)輸出到多個(gè)輸出地。
-
區(qū)分不同的類(lèi)別。日志可以分類(lèi)并命名,一個(gè)程序的各個(gè)模塊可以使用不同的名稱(chēng)來(lái)輸出日志,這樣可以很方便地判斷出當(dāng)前日志是哪個(gè)程序模塊輸出的。
-
日志格式可靈活配置。可以按需指定每條日志是否包含文件名/行號(hào)、時(shí)間戳、線程/協(xié)程號(hào)、日志級(jí)別、啟動(dòng)時(shí)間等內(nèi)容。
-
可通過(guò)配置文件的方式配置以上功能。
日志模塊設(shè)計(jì)
類(lèi)似于log4cpp,日志模塊擁有以下幾個(gè)主要類(lèi):
- class LogLevel:定義日志級(jí)別。并提供將日志級(jí)別與文本之間的互相轉(zhuǎn)化
- class Logger:日志器。定義日志級(jí)別,設(shè)置輸出地,設(shè)置日志格式。
- class LogEvent:記錄日志事件。主要記錄一下信息
- class LogEventWarp:日志事件包裝器。將logEvent打包,可以直接通過(guò)使用該類(lèi)完成對(duì)日志的定義。
- class LogFormatter:日志格式化。
- class LogAppender:日志輸出目標(biāo)。有兩個(gè)子類(lèi) class StdoutLogAppender 和 class FileLogAppender,可以分別輸出到控制臺(tái)和文件
- class LoggerManager:日志管理器。單例模式
具體實(shí)現(xiàn)
日志級(jí)別 class LogLevel
enum Level {
/// 致命情況,系統(tǒng)不可用
FATAL = 0,
/// 高優(yōu)先級(jí)情況,例如數(shù)據(jù)庫(kù)系統(tǒng)崩潰
ALERT = 100,
/// 嚴(yán)重錯(cuò)誤,例如硬盤(pán)錯(cuò)誤
CRIT = 200,
/// 錯(cuò)誤
ERROR = 300,
/// 警告
WARN = 400,
/// 正常但值得注意
NOTICE = 500,
/// 一般信息
INFO = 600,
/// 調(diào)試信息
DEBUG = 700,
/// 未設(shè)置
NOTSET = 800,
};
ToString(提供從日志級(jí)別 TO 文本的轉(zhuǎn)換)
通過(guò)X宏(X Macros)將不同的級(jí)別放入switch case語(yǔ)句中。X宏的基本思想是將重復(fù)的代碼片段定義為一個(gè)宏,然后在需要使用這些代碼的地方多次調(diào)用這個(gè)宏。這樣可以避免手動(dòng)編寫(xiě)和維護(hù)大量重復(fù)代碼。
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):對(duì)傳入的 level 參數(shù)進(jìn)行 switch 分支判斷。
//#define XX(name) ... #undef XX:定義了一個(gè)名為 XX 的宏,用于減少重復(fù)代碼。宏 XX 接受一個(gè)參數(shù) name,并生成對(duì)應(yīng)的 case 語(yǔ)句。宏會(huì)展開(kāi)成:
case LogLevel::DEBUG:
return "DEBUG";
break;
case LogLevel::INFO:
return "INFO";
break;
// 依次類(lèi)推
注1:常見(jiàn)的X宏使用場(chǎng)景:
枚舉與字符串映射:如將枚舉值轉(zhuǎn)換為字符串。
多次聲明相似的代碼結(jié)構(gòu):如函數(shù)聲明、結(jié)構(gòu)體初始化等。
生成重復(fù)的測(cè)試代碼:如生成一系列測(cè)試用例。
FromString(提供從文本 To 日志級(jí)別的轉(zhuǎn)換)
同樣通過(guò)宏定義處理多種情況。轉(zhuǎn)換時(shí)不針對(duì)大小寫(xiě),DEBUG和debug都可以完成對(duì)應(yīng)的轉(zhuǎn)化
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
用于記錄日志現(xiàn)場(chǎng),比如該日志的級(jí)別,文件名/行號(hào),日志消息,線程/協(xié)程號(hào),所屬日志器名稱(chēng)等。
成員變量
const char* m_file = nullptr; //文件名
int32_t m_line = 0; //行號(hào)
uint32_t m_elapse = 0; //程序啟動(dòng)開(kāi)始到現(xiàn)在的毫秒數(shù)
uint32_t m_thieadId = 0; //線程id
uint32_t m_fiberId = 0; //協(xié)程id
uint64_t m_time; //時(shí)間戳
std::string m_threadName; //線程名稱(chēng)
std::stringstream m_ss; //日志內(nèi)容流
std::shared_ptr<Logger> m_logger; //日志器
LogLevel::Level m_level; //日志等級(jí)
成員函數(shù)
/**
* @brief 構(gòu)造函數(shù)
*
* @param[in] logger 日志器
* @param[in] level 日志級(jí)別
* @param[in] file 文件名
* @param[in] line 文件行號(hào)
* @param[in] elapse 程序啟動(dòng)依賴(lài)的耗時(shí)(毫秒)
* @param[in] thread_id 線程id
* @param[in] fiber_id 協(xié)程id
* @param[in] time 日志時(shí)間
* @param[in] thread_name 線程名稱(chēng)
*/
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返回寫(xiě)入buf的長(zhǎng)度
int len = vasprintf(&buf, fmt, al); //4)
if(len != -1) {
m_ss << std::string(buf, len); //5)
free(buf);
}
}
日志事件包裝器 class LogEventWarp
日志事件包裝類(lèi),其實(shí)就是將日志事件和日志器包裝到一起,因?yàn)橐粭l日志只會(huì)在一個(gè)日志器上進(jìn)行輸出。將日志事件和日志器包裝到一起后,方便通過(guò)宏定義來(lái)簡(jiǎn)化日志模塊的使用。另外,LogEventWrap還負(fù)責(zé)在構(gòu)建時(shí)指定日志事件和日志器,在析構(gòu)時(shí)調(diào)用日志器的log方法將日志事件進(jìn)行輸出。
// 日志事件
LogEvent::ptr m_event;
// 構(gòu)造函數(shù)
LogEventWarp::LogEventWarp(LogEvent::ptr e)
:m_event(e){
}
// 析構(gòu)函數(shù)
LogEventWarp::~LogEventWarp() {
m_event->getLogger()->log(m_event->getLevel(), m_event);
}
在此說(shuō)一下使用日志的宏,這里定義了SYLAR_LOG_LEVEL宏,用來(lái)輸出Level級(jí)別的LogEvent,并將LogEvent寫(xiě)入到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)
日志格式
日志內(nèi)容格式化 class FormatItem
該類(lèi)為L(zhǎng)ogFormatter的public內(nèi)部類(lèi)成員,通過(guò)該類(lèi)得到解析后的格式。
此類(lèi)為抽象類(lèi),不同事件的子類(lèi)繼承該類(lèi),并且重寫(xiě)純虛函數(shù)format將日志格式轉(zhuǎn)化到流
格式化日志到流
// 消息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();
}
};
// 日志級(jí)別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);
}
};
// 執(zhí)行時(shí)間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();
}
};
// 日志器名稱(chēng)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();
}
};
// 協(xié)程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();
}
};
// 線程名稱(chēng)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();
}
};
// 時(shí)間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(); //創(chuàng)建event時(shí)默認(rèn)給的 time(0) 當(dāng)前時(shí)間戳
localtime_r(&time, &tm); //將給定時(shí)間戳轉(zhuǎn)換為本地時(shí)間,并將結(jié)果存儲(chǔ)在tm中
char buf[64];
strftime(buf, sizeof(buf), m_format.c_str(), &tm); //將tm格式化為m_format格式,并存儲(chǔ)到buf中
os << buf;
}
private:
std::string m_format;
};
日志格式器 class LogFormatter
與log4cpp的PatternLayout對(duì)應(yīng),用于格式化一個(gè)日志事件。該類(lèi)構(gòu)建時(shí)可以指定pattern,表示如何進(jìn)行格式化。提供format方法,用于將日志事件格式化成字符串。
// 成員變量
// 日志格式模板
std::string m_pattern;
// 日志格式解析后格式
std::vector<FormatItem::ptr> m_items;
// 判斷日志格式錯(cuò)誤
bool m_error = false;
// 構(gòu)造函數(shù)
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(解析格式)
// 得到相應(yīng)FormatItem放入m_items
// 默認(rèn)格式模板為:"%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后面添加一個(gè)該字符
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; //狀態(tài)1: 解析時(shí)間{%Y-%m-%d %H:%M:%S} 狀態(tài)0:解析之后的
size_t fmt_begin = 0; //開(kāi)始位置 為{
std::string str; //d T t N等格式
std::string fmt; //保存時(shí)間格式 %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循環(huán) n -> % 此時(shí)解析完一個(gè)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){ //開(kāi)始解析時(shí)間格式
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) { //結(jié)束解析時(shí)間格式
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; //解析時(shí)間結(jié)束break
}
}
++n;
if (n == m_pattern.size()) { //最后一個(gè)字符
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; //跳過(guò)已解析的字符,讓i指向當(dāng)前處理的字符,下個(gè)for循環(huán)會(huì)++i處理下個(gè)字符
} 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.) 最后一個(gè)字符為[ ] :
}
// map類(lèi)型為<string, cb>, string為相應(yīng)的日志格式, cb返回相應(yīng)的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:日志級(jí)別
XX(r, ElapseFormatItem), //r:累計(jì)毫秒數(shù)
XX(c, NameFormatItem), //c:日志名稱(chēng)
XX(t, ThreadIdFormatItem), //t:線程id
XX(n, NewLineFormatItem), //n:換行
XX(d, DateTimeFormatItem), //d:時(shí)間
XX(f, FilenameFormatItem), //f:文件名
XX(l, LineFormatItem), //l:行號(hào)
XX(T, TabFormatItem), //T:Tab
XX(F, FiberIdFormatItem), //F:協(xié)程id
XX(N, ThreadNameFormatItem), //N:線程名稱(chēng)
#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中找到相應(yīng)的FormatItem
if(it == s_format_items.end()) { //若沒(méi)有找到則用StringFormatItem顯示錯(cuò)誤信息 并設(shè)置錯(cuò)誤標(biāo)志位
m_items.push_back(FormatItem::ptr(new StringFormatItem("<<error_format %" + std::get<0>(i) + ">>")));
m_error = true;
} else { //返回相應(yīng)格式的FormatItem,其中std::get<1>(i)作為cb的參數(shù)
m_items.push_back(it->second(std::get<1>(i)));
}
}
}
}
日志輸出
日志輸出器 class LogAppender
class LogAppender是抽象類(lèi),有兩個(gè)子類(lèi),分別為StdoutLogAppender和FileLogAppender,分別實(shí)現(xiàn)控制臺(tái)和文件的輸出。兩個(gè)類(lèi)都重寫(xiě)純虛函數(shù)log方法實(shí)現(xiàn)寫(xiě)入日志,重寫(xiě)純虛函數(shù)toYamlString方法實(shí)現(xiàn)將日志轉(zhuǎn)化為YAML格式的字符串
成員變量
//日志級(jí)別
LogLevel::Level m_level = LogLevel::DEBUG;
//日志格式器
LogFormatter::ptr m_formatter;
// 互斥鎖
MutexType m_mutex;
// 是否有formatter
bool m_hasFormatter = false;
成員函數(shù)
// 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(輸出到控制臺(tái)的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); //這里調(diào)用Logformat的format,它會(huì)遍歷m_items調(diào)用相應(yīng)的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一次,判斷文件有沒(méi)有被刪
uint64_t m_lastTime = 0;
成員函數(shù)
// 構(gòu)造函數(shù)
FileLogAppender::FileLogAppender(const std::string& filename)
:m_filename(filename){
reopen();
}
// reopen(寫(xiě)入文件)
bool FileLogAppender::reopen(){
MutexType::Lock lock(m_mutex);
if (m_filestream){
m_filestream.close();
}
m_filestream.open(m_filename, std::ios::app); //以追加的方式寫(xiě)入文件中
return !!m_filestream;
}
// log(輸出到文件)
// 重寫(xiě)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))) { //寫(xiě)到m_filestream流中
std::cout << "error" << std::endl;
}
}
}
// toYamlString(轉(zhuǎn)化為YAML格式字符串)
// 重寫(xiě)toYamlString方法,轉(zhuǎn)化為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
負(fù)責(zé)進(jìn)行日志輸出。一個(gè)Logger包含多個(gè)LogAppender和一個(gè)日志級(jí)別,提供log方法,傳入日志事件,判斷該日志事件的級(jí)別高于日志器本身的級(jí)別之后調(diào)用LogAppender將日志進(jìn)行輸出,否則該日志被拋棄。
成員變量
//日志名稱(chēng)
std::string m_name;
//日志級(jí)別
LogLevel::Level m_level;
// 互斥鎖
MutexType m_mutex;
// 日志目標(biāo)集合
std::list<LogAppender::ptr> m_appenders;
//日志格式器
LogFormatter::ptr m_formatter;
// root Log
Logger::ptr m_root;
成員函數(shù)
// Logger(構(gòu)造函數(shù))
// 名稱(chēng),def = root
// 日志級(jí)別, 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(寫(xiě)不同Level日志到日志目標(biāo))
// m_appenders為日志目標(biāo)地,將當(dāng)前l(fā)ogger輸出到相應(yīng)的appender,因?yàn)锳ppender的log要傳入logger的智能指針,所以使用shared_from_this()獲得當(dāng)前l(fā)ogger的智能指針
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) { //當(dāng)logger的appenders為空時(shí),使用root寫(xiě)logger
m_root->log(level, event);
}
}
}
// addAppender(添加日志目標(biāo))
// 若appender沒(méi)有formatter的話(huà)就將默認(rèn)formatter賦給他,若有formatter則直接添加到m_appenders隊(duì)列中
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(刪除日志目標(biāo))
// 在m_appenders中找到要?jiǎng)h除的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(通過(guò)智能指針 )
// 將新的formatter賦給m_formatter,若appender沒(méi)有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(通過(guò)字符串)
// new一個(gè)新的formatter,若格式?jīng)]錯(cuò),調(diào)用上面的setFormatter設(shè)置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(轉(zhuǎn)換為YAML格式輸出)
// 將當(dāng)前l(fā)ogger 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
單例模式,用于統(tǒng)一管理所有的日志器,提供日志器的創(chuàng)建與獲取方法。LogManager自帶一個(gè)root Logger,用于為日志模塊提供一個(gè)初始可用的日志器。
typedef sylar::Singleton<LoggerManager> LoggerMgr;
成員變量
// 互斥鎖
MutexType m_mutex;
// 日志器容器
std::map<std::string, Logger::ptr> m_loggers;
// 主日志器
Logger::ptr m_root;
成員函數(shù)
// LoggerManager(構(gòu)造函數(shù))
LoggerManager::LoggerManager() {
m_root.reset(new Logger);
m_root->addAppender(LogAppender::ptr(new StdoutLogAppender));
m_loggers[m_root->m_name] = m_root;
}
// getLogger(獲取日志器)
// 在map中找到相應(yīng)的logger就返回他,若沒(méi)有就創(chuàng)建一個(gè)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賦值,當(dāng)沒(méi)有appender時(shí),使用root寫(xiě)logger
m_loggers[name] = logger;
return logger;
}
// toYamlString(將日志格式轉(zhuǎn)化為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();
}
宏定義
使用流的方式,將不同日志級(jí)別的事件寫(xiě)入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)
使用格式化方式, 將不同日志級(jí)別的事件寫(xiě)入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()
獲得相應(yīng)名字的日志器
#define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger(name)
總結(jié)
總結(jié)一下日志模塊的工作流程:
-
初始化LogFormatter,LogAppender, Logger。
-
通過(guò)宏定義提供流式風(fēng)格和格式化風(fēng)格的日志接口。每次寫(xiě)日志時(shí),通過(guò)宏自動(dòng)生成對(duì)應(yīng)的日志事件LogEvent,并且將日志事件和日志器Logger包裝到一起,生成一個(gè)LogEventWrap對(duì)象。
-
日志接口執(zhí)行結(jié)束后,LogEventWrap對(duì)象析構(gòu),在析構(gòu)函數(shù)里調(diào)用Logger的log方法將日志事件進(jìn)行輸出。
待補(bǔ)充與完善
目前來(lái)看,sylar日志模塊已經(jīng)實(shí)現(xiàn)了一個(gè)完整的日志框架,并且配合后面的配置模塊,可用性很高,待補(bǔ)充與完善的地方主要存在于LogAppender,目前只提供了輸出到終端與輸出到文件兩類(lèi)LogAppender,但從實(shí)際項(xiàng)目來(lái)看,以下幾種類(lèi)型的LogAppender都是非常有必要的:
- Rolling File Appender,循環(huán)覆蓋寫(xiě)文件
- Rolling Memory Appender,循環(huán)覆蓋寫(xiě)內(nèi)存緩沖區(qū)
- 支持日志文件按大小分片或是按日期分片
- 支持網(wǎng)絡(luò)日志服務(wù)器,比如syslog

浙公網(wǎng)安備 33010602011771號(hào)