介紹
在學習了sylar的C++高效能分散式伺服器框架後,想把自己在學習過程中的感想記錄下來。當然主要原因還是sylar的B站影片過於難以理解了,也是想加強一下自己對這個框架的理解。很多內容也是借鑑了其他大佬的博文,比如找人找不到北,zhongluqiang
日誌模組概述
日誌模組的目的:
- 用於格式化輸出程式日誌,方便從日誌中定位程式執行過程中出現的問題。
- 同時應該包括檔名/行號,時間戳,執行緒/協程號,模組名稱,日誌級別等額外資訊。
- 在列印致命的日誌時,還應該附加程式的棧回溯資訊,以便於分析和排查問題。
從設計上看,一個完整的日誌模組應該具備以下功能:
-
區分不同的級別,比如常的DEBUG/INFO/WARN/ERROR等級別。
-
區分不同的輸出地。不同的日誌可以輸出到不同的位置,比如可以輸出到標準輸出,輸出到檔案,輸出到syslog,輸出到網路上的日誌伺服器等,甚至同一條日誌可以同時輸出到多個輸出地。
-
區分不同的類別。日誌可以分類並命名,一個程式的各個模組可以使用不同的名稱來輸出日誌,這樣可以很方便地判斷出當前日誌是哪個程式模組輸出的。
-
日誌格式可靈活配置。可以按需指定每條日誌是否包含檔名/行號、時間戳、執行緒/協程號、日誌級別、啟動時間等內容。
-
可透過配置檔案的方式配置以上功能。
日誌模組設計
類似於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)
總結
總結一下日誌模組的工作流程:
-
初始化LogFormatter,LogAppender, Logger。
-
透過宏定義提供流式風格和格式化風格的日誌介面。每次寫日誌時,透過宏自動生成對應的日誌事件LogEvent,並且將日誌事件和日誌器Logger包裝到一起,生成一個LogEventWrap物件。
-
日誌介面執行結束後,LogEventWrap物件析構,在解構函式里呼叫Logger的log方法將日誌事件進行輸出。
待補充與完善
目前來看,sylar日誌模組已經實現了一個完整的日誌框架,並且配合後面的配置模組,可用性很高,待補充與完善的地方主要存在於LogAppender,目前只提供了輸出到終端與輸出到檔案兩類LogAppender,但從實際專案來看,以下幾種型別的LogAppender都是非常有必要的:
- Rolling File Appender,迴圈覆蓋寫檔案
- Rolling Memory Appender,迴圈覆蓋寫記憶體緩衝區
- 支援日誌檔案按大小分片或是按日期分片
- 支援網路日誌伺服器,比如syslog