一,模块主要成员
该模块的主要作用是对日志消息进行格式化,将日志消息组织成制定格式的字符串。
该模块主要成员有两个: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进行格式化n
void 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())
{
//先排查非格式化字符,如abc
if(_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里存储的是非格式化化字符,如abcd
if(key.empty()) return Formatlem::ptr(new OtherFormatlem(val));
//还有一种情况,就是key中存储的不是设定的格式字符,如%g
std::cout<<"没有该格式化字符:%"<<key<<std::endl;
abort();
return nullptr;
}
private:
std::string _pattern;
std::vector<Formatlem::ptr> _items;
};
}
#endif
3.1 测试