一,模塊主要成員
該模塊的主要作用是對日志消息進行格式化,將日志消息組織成制定格式的字符串。
該模塊主要成員有兩個:1.格式化字符串。 2.格式化子項數組
1.1 格式化字符串
格式化字符串的主要功能是保存日志輸出的格式字符串。其格式化字符主要有下面幾種
格式化字符串定義了日志的輸出格式,而格式化字符更有助于用戶自由的控制日志的輸出格式 。這里要注意的是由于我們信息中的日期為時間戳,因此在設置其格式化字符時還應設置對應的子格式化字符如[%d{%H:%M:%S}]指的是按照年月日輸出時間
?1.2 格式化子項數組
格式化子項數組:用于按順序保存格式化字符串對應的子格式化對象(FormatItem類)。
那么什么是子格式化類(FormatItem類)?子格式化類主要負責日志消息子項的獲取以及格式化,其下包含諸多子類,通過繼承關系實現多態,即使是不同的子類也能依靠多態由相同的父類指針指向,從而保存到同一個vector中,Formatltem主要有以下子類:
?格式化字符,以及格式化子項的關系如下,這里我們以日期為例子
?
?
1.3 舉例說明
例子:"[%d{%H:%M:%S}] %m%n"
pattern = "[%d{%H:%M:%S}] %m%n"
items = {{OtherFormatItem(), "["},{TimeFormatItem(), "%H:%M:%S"},{OtherFormatItem(), "]"},{MsgFormatItem (), ""},{NLineFormatItem (), ""}
}
message msg = {size_t _line = 22;size_t _ctime = 12345678;std::thread::id _tid = 0x12345678;std::string _logger = "logger";std::string _file = "main.cpp";std::string _payload = "創建套接字失敗";LogLevel::level _level = ERROR;
};
格式化的過程其實就是按次序從Msg中取出需要的數據進?字符串的連接的過程。
最終組織出來的格式化消息: "[22:32:54] 創建套接字失敗\n"
?二,代碼實現
2.1 代碼
核心代碼bool AnalyPattern()的分析如下
?
?
#ifndef _M_FORMAT_H_
#define _M_FORMAT_H_#include "level.hpp"
#include "message.hpp"
#include "util.hpp"
#include <vector>
#include <memory>
#include <sstream>namespace mjwlog
{// 格式化子項基類class Formatlem{public:using ptr=std::shared_ptr<Formatlem>;virtual void format(std::ostream &os, const message& msg) = 0;};// 格式化子項子類// 日期類子類class TimeFormatlem : public Formatlem{public:TimeFormatlem(std::string format="%H:%M:%S"):_fromat(format){}void format(std::ostream &os, const message& msg) override{struct tm time;localtime_r((time_t*)&msg._time,&time);char str[32];strftime(str,sizeof(str)-1,_fromat.c_str(),&time);os<<str;}private:std::string _fromat;//用來控制輸出格式};//縮進子類class TabFormatlem : public Formatlem{public:void format(std::ostream &os, const message& msg) override{os<<"\t";}};//線程id子類class ThreadFormatlem : public Formatlem{public:void format(std::ostream &os, const message& msg) override{os<<msg._id;}};//日志級別子類class LevelFormatlem : public Formatlem{public:void format(std::ostream &os, const message& msg) override{os<<LogLevel::LeveltoString(msg._level);}};//日志器名稱子類class NameFormatlem : public Formatlem{public:void format(std::ostream &os, const message& msg) override{os<<msg._logger;}};//文件名子類class CFileFormatlem : public Formatlem{public:void format(std::ostream &os, const message& msg) override{os<<msg._filename;}};//行號子類class CLineFormatlem : public Formatlem{public:void format(std::ostream &os, const message& msg) override{os<<msg._line;}};//日志消息主體子類class MsgFormatlem : public Formatlem{public:void format(std::ostream &os, const message& msg) override{os<<msg._msg;}};//換行子類class NLineFormatlem : public Formatlem{public:void format(std::ostream &os, const message& msg) override{os<<"\n";}};//其他字符子類,直接原路返回class OtherFormatlem : public Formatlem{public:OtherFormatlem(std::string str):_str(str){}void format(std::ostream &os, const message& msg) override{os<<_str;}private:std::string _str;};//格式化類class Formatter{/*%d 日期%T 縮進%t 線程id%p 日志級別%c 日志器名稱%f 文件名%l 行號%m 日志消息%n 換行*/public://默認格式:時間{年-月-日 時:分:秒}縮進 線程ID 縮進 [日志級別] 縮進 [日志器名稱] 縮進 文件名:行號 縮進 消息換行Formatter(std::string pattern="[%d{%H:%M:%S}]%T[%t]%T[%p]%T[%c]%T[%f:%l]%T%n"):_pattern(pattern){}//對msg進行格式化nvoid format(std::ostream& os,const message& msg)//將格式化后的內容寫入os執行流{if(AnalyPattern()){for(auto& it:_items){it->format(os,msg);}}}std::string format(const message& msg)//將格式化后的內容以string方式返回{std::stringstream str;if(AnalyPattern()){for(auto& it:_items){it->format(str,msg);}}return str.str();}//對格式化字符串進行分析bool AnalyPattern(){//遍歷_parttern進行逐一分析int cur=0;std::string key="",val="";//"abc[%%d{%H:%M:%S}]"while(cur<_pattern.size()){//先排查非格式化字符,如abcif(_pattern[cur]!='%'){val+=_pattern[cur++];//特殊情況:"aaaa[%%d{%H:%M:%S}]"if(cur==_pattern.size()){_items.push_back(createItem(key,val));}continue;}//到這一步,說明碰到%,這是需要排除"%[" "%%"等情況//"%["//if(_pattern[cur]=='%'&&_pattern[cur+1]=='%')if(_pattern[cur]=='%'&&(!isalpha(_pattern[cur+1]))){val+=_pattern[cur++];//特殊情況:"aaaa[%%d{%H:%M:%S}%"/* if(cur==_pattern.size()){_items.push_back(createItem(key,val));} */continue;}//此時,key為空,val中全是非格式化字符字符//不過也有可能val中沒有任何字符if(!val.empty()) _items.push_back(createItem(key,val));key.clear();val.clear();//cur+1==_pattern.size()if(cur+1==_pattern.size()){std::cout<<"格式化字符關鍵字%后數據錯誤"<<std::endl;return false;}//到這一步,說明目前cur指向%,且cur+1指向的為一個字母字符cur=cur+1;key+=_pattern[cur++];//"abc[%%d{%H:%M:%S}]",此時cur指向d后面的{//因此,這時候我們需要判斷后面是否是子格式化字符if(cur<_pattern.size()&&_pattern[cur]=='{'){cur++;while(cur<_pattern.size()&&_pattern[cur]!='}'){val+=_pattern[cur++];}//出while循環有下面兩種可能//1.cur==_pattern.size(),說明沒有找到'}',說明這是非法子格式化字符if(cur==_pattern.size()){std::cout<<"非法子格式化字符...."<<std::endl;abort();}//2._pattern[cur]=='}'_items.push_back(createItem(key,val));key.clear();val.clear();//此時cur指向},因此進入下一個循環前cur應該++cur++;}else{_items.push_back(createItem(key,val));key.clear();val.clear();}}return true;}private://根據不同的格式創建不同的格式化子類對象//key用來存儲格式化字符,val用來存儲格式化字符子字符如[%d{%H:%M:%S}]std::shared_ptr<Formatlem> createItem(const std::string& key,const std::string& val){if(key=="d") return Formatlem::ptr(new TimeFormatlem(val));if(key=="T") return Formatlem::ptr(new TabFormatlem());if(key=="p") return Formatlem::ptr(new LevelFormatlem());if(key=="t") return Formatlem::ptr(new ThreadFormatlem());if(key=="c") return Formatlem::ptr(new NameFormatlem());if(key=="f") return Formatlem::ptr(new CFileFormatlem());if(key=="l") return Formatlem::ptr(new CLineFormatlem());if(key=="n") return Formatlem::ptr(new NLineFormatlem());//當key為空時,則說明val里存儲的是非格式化化字符,如abcdif(key.empty()) return Formatlem::ptr(new OtherFormatlem(val));//還有一種情況,就是key中存儲的不是設定的格式字符,如%gstd::cout<<"沒有該格式化字符:%"<<key<<std::endl;abort();return nullptr;}private:std::string _pattern;std::vector<Formatlem::ptr> _items;};
}#endif
3.1 測試
?
?