使用工具类设计
cpp
#ifndef __UTIL_H__
#define __UTIL_H__
/*
通用功能类,与业务无关的功能实现
1. 获取系统时间
2. 获取文件大小
3. 创建目录
4. 获取文件所在目录
*/
#include <iostream>
#include<ctime>
#include <unistd.h>
#include <sys/stat.h>
namespace wjj_logs
{
namespace util
{
class Date
{
public:
static size_t now()
{
return (size_t)time(nullptr);
}
};
class File
{
public:
static bool exists(const std::string &pathname)//判断文件是否存在
{
// return (access(pathname.c_str(),F_OK)==0);//系统调用接口,跨平台会有问题
struct stat st;
if(stat(pathname.c_str(),&st)<0) return false;
return true;
}
static std::string path(const std::string &pathname)
{
size_t pos=pathname.find_last_of("/\\");
if(pos==std::string::npos) return ".";
else return pathname.substr(0,pos+1);//./dir/a.txt->./dir/
} //获取文件所在目录
static void createDirectory(const std::string &pathname)//创建目录,有可能有分级创建多个目录
{
// dir3/dir2/dir1/a.txt
size_t pos=0,idx=0;
std::cout<<pathname<<std::endl;
while (idx<pathname.size())
{
pos=pathname.find_first_of("/\\",idx);
if(pos==std::string::npos)
{
mkdir(pathname.c_str(),0777);
std::cout<<"mkdir"<<pathname<<std::endl;
}
std::string parent_dir=pathname.substr(0,pos+1);
if(exists(parent_dir)==true)
{
idx=pos+1;
continue;
}
mkdir(parent_dir.c_str(),0777);
std::cout<<"mkdir"<<pathname<<std::endl;
idx=pos+1;
}
}
};
}
}
#endif
日志等级设计
日志等级模块:
1、定义出日志系统所包含的所有日志等级:
NNKNOW = 0; DEBUG,调试等级的日志;INFO, 提示等级的日志;WARN,警告等级的日志;ERROR,错误等级的日志;FATAL,致命错误等级的日志;OFF,关闭;
每一个项目中都会设置一个默认的日志输出等级,只有输出的日志等级大于等于默认限制
等级的时候才可以进行输出
2、提供一个接口,将对应等级的枚举,转换为一个对应的字符串:DEBUG --->"DEBUG"
cpp
#ifndef __LEVEL_H__
#define __LEVEL_H__
/*
定义枚举类,枚举出日志等级
提供转换接口:将枚举转换成为对应的字符出啊
*/
namespace wjj_logs
{
class LogLevel
{
public:
enum class value
{
UNKOW=0,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
OFF
};
static const char* toString(LogLevel::value level)
{
switch (level)
{
case LogLevel::value::DEBUG: return "DEBUG";
case LogLevel::value::INFO: return "INFO";
case LogLevel::value::WARN: return "WARN";
case LogLevel::value::ERROR: return "ERROR";
case LogLevel::value::FATAL: return "FATAL";
case LogLevel::value::OFF: return "OFF";
}
return "UNKOW";
}
};
}
#endif
日志消息类
日志消息模块:中间存储日志输出所需的各项要素信息 。
时间:描述本条日志的输出时间。
日志源文件名称和行号:定位错误
日志等级:描述本条日志的等级。用于进行日志过滤。
线程ID:描述本条日志是哪个线程输出的。
日志主体内容
日志器名称:当前支持多日志器的同时使用
cpp
#ifndef __MESSAGE_H__
#define __MESSAGE_H__
/*
日志消息模块:中间存储日志输出所需的各项要素信息 。
时间:描述本条日志的输出时间。
日志源文件名称和行号:定位错误
日志等级:描述本条日志的等级。用于进行日志过滤。
线程ID:描述本条日志是哪个线程输出的。
日志主体内容
日志器名称:当前支持多日志器的同时使用
*/
#include <iostream>
#include <string>
#include <thread>
#include "level.hpp"
#include "util.hpp"
namespace wjj_logs
{
struct LogMsg
{
size_t _ctime;//日志产生时间戳
std::string _file;//源码文件名称
size_t _line;//源码行号
LogLevel::value _level;//日志等级
std::thread::id _tid;//线程id
std::string _logger;//日志器名称
std::string _payload;//日志主体消息
LogMsg(LogLevel::value level,
size_t line,
const std::string file,
const std::string logger,
const std::string msg):
_ctime(util::Date::now()),
_level(level),
_line(line),
_tid(std::this_thread::get_id()),
_file(file),
_logger(logger),
_payload(msg)
{
}
};
} // namespace wjj_logs
#endif // DEBUG
日志格式化
对日志消息进行格式化组织成为指定格式的字符串。格式化字符串控制了日志输出的格式,定义格式化字符串是为了让日志系统进行日志格式化时更加的灵活方便。
cpp
#ifndef __FORMAT_H__
#define __FORMAT_H__
#include "level.hpp"
#include "message.hpp"
#include <ctime>
#include <vector>
#include <cassert>
#include <sstream>
namespace wjj_logs
{
class FormatItem
{
public:
using ptr=std::shared_ptr<FormatItem>;
virtual void format(std::ostream& out,const LogMsg &msg)=0;
};
class FileFormatItem : public FormatItem
{
public:
void format(std::ostream &out,const LogMsg &msg) override
{
out << msg._file;
}
};
class MsgFormatItem:public FormatItem
{
public:
void format(std::ostream& out,const LogMsg &msg) override
{
out<<msg._payload;
}
};
class LevelFormatItem:public FormatItem
{
public:
void format(std::ostream& out,const LogMsg &msg) override
{
out<<LogLevel::toString(msg._level);
}
};
class TimeFormatItem:public FormatItem
{
public:
TimeFormatItem(const std::string &fmt="%H:%M:%S"):_time_fmt(fmt){}
void format(std::ostream& out,const LogMsg &msg) override
{
struct tm result;
localtime_r(&msg._ctime,&result);//将时间戳转换成为时间结构化数据result
char tmp[32]={0};
strftime(tmp,31,_time_fmt.c_str(),&result);//将result按照指定的格式生成字符串放入到tmp当中
out<<tmp;
}
private:
std::string _time_fmt;//%H:%M:%S
};
class LineFormatItem : public FormatItem
{
public:
virtual void format(std::ostream &out,const LogMsg &msg) override
{
out << msg._line;
}
};
class ThreadFormatItem : public FormatItem
{
public:
virtual void format(std::ostream &out,const LogMsg &msg) override
{
out << msg._tid;
}
};
class LoggerFormatItem : public FormatItem
{
public:
virtual void format(std::ostream &out,const LogMsg &msg) override
{
out << msg._logger;
}
};
class TabFormatItem : public FormatItem //制表符
{
public:
virtual void format(std::ostream &out,const LogMsg &msg) override
{
out << "\t";
}
};
class NLFormatItem : public FormatItem //制表符
{
public:
virtual void format(std::ostream &out,const LogMsg &msg) override
{
out <<"\n";//new line换行
}
};
class OherFormatItem : public FormatItem //制表符
{
public:
OherFormatItem(const std::string &str):_str(str){}
virtual void format(std::ostream &out,const LogMsg &msg) override
{
out << _str;
}
private:
std::string _str;
};
/*
%d 日期,包含资格是{%H%M%S}
%t 线程id
%c 日志器名称
%f 源码文件名
%l 源码行号
%p 日志级别
%T 制表符缩进
%m 主体信息
%n 换行
*/
class Formatter
{
public:
Formatter(const std::string &pattern="[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n"):_pattern(pattern)
{
assert(parsePattern());//解析必须成功,不然无法知道按照什么格式输出
}
std::string format(LogMsg &msg) //对msg进行格式化返回一个格式化的字符串
{
std::stringstream ss;
format(ss,msg);
return ss.str();
}
void format(std::ostream &out,LogMsg &msg)//将msg格式化放到io流中
{
for(auto &item:_items)
{
item->format(out,msg);
}
}
private:
bool parsePattern()//对格式化规则字符串_pattern进行解析放入到_items中,不要对外面暴漏无实际意义或者用不到的接口。
{
//1.对格式化规则字符串进行解析
//avc%d{%H:%M:%S}%Tuuu%t%T[%p]%T[%c]:从前向后遍历,遇到%就代表其后面是一个格式化字符,一开始如果没遇到%就代表他是原始字符串,格式化字符如果后面紧跟{代表后面是一个字串
std::vector<std::pair<std::string,std::string>> fmt_order;//存放解析之后的结果
size_t pos=0;
std::string key,val;
while(pos<_pattern.size())
{
//1.处理原始字符串,判断是否是%,不是就是原始字符
if(_pattern[pos]!='%')
{
val.push_back(_pattern[pos++]);
continue;
}
//能走下来就代表pos位置上就是%字符,先考虑%%这种原始字符%的情况
if(pos+1<_pattern.size() && _pattern[pos+1]=='%')
{
val.push_back('%');
pos+=2;
continue;
}
//这时候开始处理原始字符串val
if(val.empty()==false)
{
fmt_order.push_back(std::make_pair("",val));
val.clear();
}
//接下来开始格式化字符的处理,此时pos指向'%'的位置
pos+=1;
if(pos==_pattern.size())
{
std::cout<<"%之后,没有对应的格式化字符!\n";
return false;
}
key=_pattern[pos];//获取格式化字符
//判断格式化字符后面是否有{}即是否有子串,但也要考虑是否已经到达格式化规则字符串末尾的情况
pos+=1;
if(pos<_pattern.size() && _pattern[pos]=='{')
{
pos+=1;//这时候pos指向'{'之后即子规则的起始位置
while (pos<_pattern.size() && _pattern[pos]!='}')
{
val.push_back(_pattern[pos++]);
}
if(pos==_pattern.size()) //走到末尾跳出循环,说明格式化规则字符串有问题,没有遇到'}'。
{
std::cout<<"子规则{}匹配出错!\n";
return false;
}
pos+=1;//此时pos从'}'移动到了下一循环处理的新起始位置
}
fmt_order.push_back(std::make_pair(key,val));
key.clear();
val.clear();
}
//2.根据得到的数据初始化格式化子项数组成员_items
for(auto &ch:fmt_order)
{
_items.push_back(createItem(ch.first,ch.second));
}
return true;
}
private:
FormatItem::ptr createItem(const std::string &key,const std::string &val)//根据不同的格式化字符key创建不同的格式化子项对象
{
if (key == "d") return std::make_shared<TimeFormatItem>(val);
if (key == "t") return std::make_shared<ThreadFormatItem>();
if (key == "c") return std::make_shared<LoggerFormatItem>();
if (key == "f") return std::make_shared<FileFormatItem>();
if (key == "l") return std::make_shared<LineFormatItem>();
if (key == "p") return std::make_shared<LevelFormatItem>();
if (key == "T") return std::make_shared<TabFormatItem>();
if (key == "m") return std::make_shared<MsgFormatItem>();
if (key == "n") return std::make_shared<NLFormatItem>();
if (key=="") return std::make_shared<OherFormatItem>(val);//key为空就说明此时是原始字符串
std::cout<<"没有对应的格式化字符 %"<<key<<std::endl;
abort();
return FormatItem::ptr();
}
private:
std::string _pattern;//格式化规则字符串
std::vector<FormatItem::ptr> _items;
};
}
#endif