日志模塊程序結構圖
sylarLog
├── LogLevel(日志級別封裝類)
│ ├── 提供“從日志級別枚舉值轉換到字符串”、“從字符串轉換相應的日志級別枚舉值”等方法
├── LogEvent(日志事件類)
│ ├── 封裝日志事件的屬性,例如時間、線程id、日志等級、內容等等,并對外提供訪問方法
│ └── 日志事件的構造在使用上會通過宏定義來簡化
├── LogEventWrap(日志事件包裝類)
│ ├── 內含日志事件 LogEvent
│ └── 日志事件在析構時由日志器進行輸出
├── LogFormatter(日志格式類)
│ ├── 通過傳遞日志樣式字符串給該類,該類對傳入的字符串進行解析,例如 %d%t%p%m%n 表示 時間、線程號、日志等級、內容、換行
│ ├── 內含一個虛基類-日志內容格式化項 FormatItem
│ ├── 有13個子類,消息-MessageFormatItem、日志級別-LevelFormatItem、累計毫秒數-ElapseFormatItem、日志名稱-NameFormatItem、線程id-ThreadIdFormatItem、換行-NewLineFormatItem、時間-DateTimeFormatItem、文件名-FilenameFormatItem、行號-LineFormatItem、Tab-TabFormatItem、協程id-FiberIdFormatItem、線程名稱-ThreadNameFormatItem、直接打印字符串-StringFormatItem
│ └── 整個日志模塊最復雜的邏輯就是該類解析日志樣式的函數 init()
├── LogAppender(日志輸出目的地類)
│ ├── LogAppender 為虛基類,有純虛函數,留給子類去各自實現
│ ├── 實現的子類如 StdoutLogAppender 和 FileLogAppender
│ └── Appender 自帶一個默認的 LogFormatter,以默認方式輸出
├── StdoutLogAppender(標準化輸出類)
│ └── 日志輸出到控制臺
├── FileLogAppender(文件輸出類)
│ └── 日志輸出到相應的文件中
├── Logger(日志器類)
│ ├── 設置日志名稱、設置日志等級 LogLevel、設置日志輸出位置 LogAppender、設置日志格式、根據日志級別控制日志輸出等
├── LoggerManager(日志管理器類)
│ ├── 利用 map 存放各個 Logger 實例,其中 key 是日志器的名稱,value 是日志器的智能指針
│ └── 還內含一個主日志器 root
└── 其他說明├── 每個類都 typedef std::shared_ptr ptr,方便外界使用其智能指針└── 普遍使用 Spinlock 實現互斥,保證線程安全。Spinlock 比 普通的 Mutex 效率高,但耗CPU。
數據流轉
- 首先通過SYLAR_LOG_NAME(name)宏從LoggerMgr中獲取對應的Logger對象,然后通過SYLAR_LOG_DEBUG(logger)->SYLAR_LOG_LEVEL(logger,level)宏創建一個新的LogEvent對象,并將其傳遞給LogEventWrap臨時對象。接著,通過std::stringstream將日志內容存入其中。
- 當LogEventWrap臨時對象析構時,會調用Logger的log方法,遍歷其所有的LogAppender,并調用每個LogAppender的log方法(傳入event參數)。
- 這里以FileLogAppender為例,LogAppender的log方法會加上自己的std::ostream參數(如果是輸出到控制臺,則是std::cout),然后調用LogFormatter的format方法(傳入ostream、event參數)。
- LogFormatter的format方法會遍歷自己緩存的所有FormatItem(繼承了FormatItem的各種子類智能指針),將日志內容格式化(例如加上時間日期、線程id等)。
- 調用的是每個FormatItem的format方法(傳入ostream、event參數)。最后,每個FormatItem的format方法會將格式化后的內容以流式方式存入std::ostream。如果是輸出到控制臺,那么這里就直接輸出了。如果是文件,因為std::ostream關聯了文件,因此會對文件進行緩存寫(非實時寫)。
LogFormatter類的init方法
LogFormatter類的init方法,用于解析日志格式字符串。主要功能如下:
首先,定義了一個patterns向量,用于存儲解析到的模式項。每個模式項包括一個整數類型和一個字符串,類型為0表示該模式是常規字符串,為1表示該模式需要轉義。
定義了一個臨時變量tmp,用于存儲常規字符串。
定義了一個日期格式字符串dateformat,默認把位于%d后面的大括號對里的全部字符都當作格式字符,不校驗格式是否合法。
定義了一個布爾變量error,用于標記解析過程中是否出錯。
定義了兩個布爾變量parsing_string和parsing_pattern,分別表示是否正在解析常規字符和模板字符。初始時,parsing_string為true。
使用一個循環遍歷m_pattern字符串中的每個字符,根據不同的情況進行解析。
如果當前字符是"%“,則根據parsing_string的值進行不同的處理。如果正在解析常規字符,則將之前的常規字符串添加到patterns中,并將parsing_string設置為false;如果正在解析模板字符,則將當前的”%“作為模板字符添加到patterns中,并將parsing_string設置為true。
如果當前字符不是”%“,則根據parsing_string的值進行不同的處理。如果正在解析常規字符,則將當前字符添加到tmp中;如果正在解析模板字符,則將當前字符作為模板字符添加到patterns中,并根據不同情況進行特殊處理。
在解析模板字符的過程中,如果遇到”%d",則需要進一步解析日期格式字符串。通過遍歷后續字符,直到找到閉合的大括號,將其中的字符添加到dateformat中。
如果在解析過程中出現錯誤,將m_error設置為true并返回。
最后,根據解析得到的模式項創建相應的格式化項對象,并將其添加到m_items中。
如果解析過程中出現錯誤,將m_error設置為true并返回。
void LogFormatter::init() {// 按順序存儲解析到的pattern項// 每個pattern包括一個整數類型和一個字符串,類型為0表示該pattern是常規字符串,為1表示該pattern需要轉義// 日期格式單獨用下面的dataformat存儲std::vector<std::pair<int, std::string>> patterns;// 臨時存儲常規字符串std::string tmp;// 日期格式字符串,默認把位于%d后面的大括號對里的全部字符都當作格式字符,不校驗格式是否合法std::string dateformat;// 是否解析出錯bool error = false;// 是否正在解析常規字符,初始時為truebool parsing_string = true;// 是否正在解析模板字符,%后面的是模板字符// bool parsing_pattern = false;size_t i = 0;while(i < m_pattern.size()) {std::string c = std::string(1, m_pattern[i]);if(c == "%") {if(parsing_string) {if(!tmp.empty()) {patterns.push_back(std::make_pair(0, tmp));}tmp.clear();parsing_string = false; // 在解析常規字符時遇到%,表示開始解析模板字符// parsing_pattern = true;i++;continue;} else {patterns.push_back(std::make_pair(1, c));parsing_string = true; // 在解析模板字符時遇到%,表示這里是一個%轉義// parsing_pattern = false;i++;continue;}} else { // not %if(parsing_string) { // 持續解析常規字符直到遇到%,解析出的字符串作為一個常規字符串加入patternstmp += c;i++;continue;} else { // 模板字符,直接添加到patterns中,添加完成后,狀態變為解析常規字符,%d特殊處理patterns.push_back(std::make_pair(1, c));parsing_string = true; // parsing_pattern = false;// 后面是對%d的特殊處理,如果%d后面直接跟了一對大括號,那么把大括號里面的內容提取出來作為dateformatif(c != "d") {i++;continue;}i++;if(i < m_pattern.size() && m_pattern[i] != '{') {continue;}i++;while( i < m_pattern.size() && m_pattern[i] != '}') {dateformat.push_back(m_pattern[i]);i++;}if(m_pattern[i] != '}') {// %d后面的大括號沒有閉合,直接報錯std::cout << "[ERROR] LogFormatter::init() " << "pattern: [" << m_pattern << "] '{' not closed" << std::endl;error = true;break;}i++;continue;}}} // end while(i < m_pattern.size())if(error) {m_error = true;return;}// 模板解析結束之后剩余的常規字符也要算進去if(!tmp.empty()) {patterns.push_back(std::make_pair(0, tmp));tmp.clear();}// for debug // std::cout << "patterns:" << std::endl;// for(auto &v : patterns) {// std::cout << "type = " << v.first << ", value = " << v.second << std::endl;// }// std::cout << "dataformat = " << dateformat << std::endl;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(c, LoggerNameFormatItem), // c:日志器名稱
// XX(d, DateTimeFormatItem), // d:日期時間XX(r, ElapseFormatItem), // r:累計毫秒數XX(f, FileNameFormatItem), // f:文件名XX(l, LineFormatItem), // l:行號XX(t, ThreadIdFormatItem), // t:編程號XX(F, FiberIdFormatItem), // F:協程號XX(N, ThreadNameFormatItem), // N:線程名稱XX(%, PercentSignFormatItem), // %:百分號XX(T, TabFormatItem), // T:制表符XX(n, NewLineFormatItem), // n:換行符
#undef XX};//根據解析得到的模式項創建相應的格式化項對象,并將其添加到m_items中。for(auto &v : patterns) {if(v.first == 0) {m_items.push_back(FormatItem::ptr(new StringFormatItem(v.second)));} else if( v.second =="d") {m_items.push_back(FormatItem::ptr(new DateTimeFormatItem(dateformat)));} else {auto it = s_format_items.find(v.second);if(it == s_format_items.end()) {std::cout << "[ERROR] LogFormatter::init() " << "pattern: [" << m_pattern << "] " << "unknown format item: " << v.second << std::endl;error = true;break;} else {m_items.push_back(it->second(v.second));}}}if(error) {m_error = true;return;}
}