开发环境
Centos 7.6 vim Visual Studio 2022 Makefile gdb g++ 9
系统思想
仿照log4j的模式进行开发
格式如下:
%m--->消息体
%p--->日志级别
%r--->启动的时间
%c--->日志名称
%t--->进程id
%n--->回车
%d--->时间
%f--->文件名
%l--->行号
将日志抽象成Logger(日志器),LogAppender(输出落地点),LogFormat(日志格式器)三大模块。 Logger, 对外使用的类,输入的日志级别大于等于Logger的日志,才会被真正写入。可以有多个Logger,不同的logger,记录不同类型的日志,比如将系统框架日志和业务逻辑日志分离。 LogAppender, 定义日志的输出落地点,目前实现了控制台日志(StdoutLogAppender),文件日志(FileLogAppender).两种类型。拥有自己的日志级别和日志格式,可以灵活定义不同的输出。主要用于区分日志级别,将error日志,单独输出到一个文件,以防被其他类型的日志淹没 Formater,日志格式,通过字符串自定义日志的格式,仿printf格式。可以灵活定义日志格式
系统介绍
系统最大特点就是具有独立性,可以作为不同模块的日志系统,之间互不影响。可以实现不同模块之间的日志分离,系统还支持流式日志风格写日志和格式化风格写日志,支持日志格式自定义,日志级别,多日志分离等等功能 流式日志使用:LOG_INFO(logger) << “this is a log”; 格式化日志使用:LOG_FMT_INFO(logger, “%s”, “hello log”); 支持时间,线程id,线程名称,日志级别,日志名称,文件名,行号等内容的自由配置
系统主体框架
系统日志展示
控制台流式日志输出 文件格式日志输出
核心代码展示
日志系统最为核心的就是解析用户输出的日志格式,其算法和C标准库中printf的算法相似
void Formattor::Init() {
string nstr;
for (size_t i = 0;i < m_pattern.size();i++) {
if (m_pattern[i] != '%') {
nstr.append(1, m_pattern[i]);
continue;
}
if ((i + 1) < m_pattern.size()) {
if (m_pattern[i + 1] == '%') {
nstr.append(1, '%');
continue;
}
}
size_t n = i + 1;
int fmt_staus = 0;
int fmt_beign = 0;
string str;
string fmt;
while (n < m_pattern.size()) {
if (!isalpha(m_pattern[n]) && m_pattern[n] != '}' && m_pattern[n] != '{') {
str = m_pattern.substr(i + 1, n - i - 1);
break;
}
if (fmt_staus == 0) {
if (m_pattern[n] == '{') {
str = m_pattern.substr(i + 1, n - i - 1);
fmt_staus = 1;
fmt_beign = n;
n++;
continue;
}
}
else if (fmt_staus == 1) {
if (m_pattern[n] == '}') {
fmt = m_pattern.substr(fmt_beign + 1, n - fmt_beign - 1);
fmt_staus = 0;
++n;
break;
}
}
++n;
if (n == m_pattern.size()) {
if (str.empty()) {
str = m_pattern.substr(i - 1);
}
}
}
if (fmt_staus == 0) {
if (!nstr.empty()) {
vec.push_back(make_tuple(nstr, string(), 0));
nstr.clear();
}
str = m_pattern.substr(i + 1, n - i - 1);
vec.push_back(make_tuple(str, fmt, 1));
i = n - 1;
}
else if (fmt_staus == 1) {
cout << "pattern parse error" << m_pattern << "-" << m_pattern.substr(i) << endl;
vec.push_back(make_tuple("<<pattern_error>>", fmt, 0));
}
}
if (!nstr.empty()) {
vec.push_back(make_tuple(nstr, "", 0));
}
for (auto& i : vec) {
if (get<2>(i) == 0) {
m_items.push_back(Formattor::Item::ptr(new stringitemforma(get<0>(i))));
}
else {
auto it = formar_item.find(get<0>(i));
if (it == formar_item.end()) {
m_items.push_back(Formattor::Item::ptr(new stringitemforma("<<error_format %" + get<0>(i) + ">>")));
}
else {
m_items.push_back(it->second(get<1>(i)));
}
}
}
}
管理所有的日志器,并且可以通过解析类模板,动态创建或修改日志器相关的内容(日志级别,日志格式,输出落地点等等)
class LogMessage {
public:
LogMessage() {
root.reset(new Logger());
root->AddAppender(LogAppender::ptr(new stdoutAppender()));
}
map<string, Logger::ptr> mp;
Logger::ptr root;
public:
Logger::ptr getlogger(string name);
void Init();
}
日志落地点抽象。目前只实现了输出到控制台(StdoutAppender)和输出到文件(FileAppender),LogAppender可以拥有自己的LogFormat。 一个日志器,可以对应多个LogAppender。也就是说写一条日志,可以落到多个输出,并且每个输出的格式都可以不一样。 Appender有单独的日志级别,可以自定义不同级别的日志,输出到不同的Appender,常用于将错误日志统一输出到一个地方。
class LogAppender {
public:
typedef shared_ptr<LogAppender> ptr;
virtual~LogAppender() {}
virtual void Log(shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) = 0;
void setformattor(Formattor::ptr val) ;
Formattor::ptr getformattor() { return m_forattor; }
void setlevel(LogLevel::Level level);
LogLevel::Level getlevel();
protected:
LogLevel::Level m_level;
Formattor::ptr m_forattor;
};
class stdoutAppender :public LogAppender {
public:
typedef shared_ptr<stdoutAppender> ptr;
virtual void Log(Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event);
};
class FileAppender :public LogAppender {
public:
FileAppender(string filename);
typedef shared_ptr<FileAppender> ptr;
void Log(Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event);
bool repoen();
private:
string m_file_name;
ofstream m_filestream;
};
日志器,包含一个日志格式器,一个root Logger,N个LogAppender提供日志写入方法。根据日志器的配置格式和内容。将日志写到对应的地方
class Logger :public enable_shared_from_this<Logger> {
public:
typedef shared_ptr<Logger> ptr;
Logger(string _name = "root");
void Log(LogLevel::Level level, LogEvent::ptr event);
void AddAppender(LogAppender::ptr appender);
void DelAppendrt(LogAppender::ptr appender);
void debug(LogEvent::ptr event);
void info(LogEvent::ptr event);
void warn(LogEvent::ptr event);
void error(LogEvent::ptr event);
void fatal(LogEvent::ptr event);
LogLevel::Level getlevel() { return m_level; }
void setleve(LogLevel::Level level) { m_level = level; }
void setformat(Formattor::ptr fmt) { format = fmt; }
string getname()const { return m_name; }
private:
string m_name;
LogLevel::Level m_level;
list<LogAppender::ptr> m_appender;
Formattor::ptr format;
};
日志格式器,执行日志格式化,负责日志格式的初始化。 解析日志格式,将用户自定义的日志格式,解析为对应的Item。 格式解析: %t : 线程id ThreadIdFormatItem %N : 线程名称 ThreadNameFormatItem %F : 协程id FiberIdFormatItem %p : 日志级别 LevelFormatItem %c : 日志名称 NameFormatItem %f : 文件名 FilenameFormatItem %l : 行号 LineFormatItem %m : 日志内容 MessageFormatItem
class Forater{
public:
typedef shared_ptr<Formattor> ptr;
Forater(string pattern) ;
string format(shared_ptr<Logger> logger, LogLevel::Level leve, LogEvent::ptr event);
public:
class Item {
public:
typedef shared_ptr<Item> ptr;
virtual ~Item() {}
virtual void format(ostream& os, shared_ptr<Logger> logger, LogLevel::Level leve, LogEvent::ptr event) = 0;
};
void Init();
private:
string m_pattern;
vector<Item::ptr> m_items;
};
日志事件的封装,将要写的日志,填充到LogEvent中。填充完毕之后,写入到对应的logger中
class LogEvent {
public:
LogEvent() {};
typedef shared_ptr<LogEvent> ptr;
void format(const char* fmt, ...);
void format(const char* fmt, va_list al);
shared_ptr<Logger> GetLogger() { return logger; }
stringstream& getSS() { return m_ss; }
LogLevel::Level GetLevel() { return level; }
private:
shared_ptr<Logger> logger;
LogLevel::Level level;
const char* m_file = NULL;
int32_t m_line = 0;
uint32_t m_please = 0;
uint32_t m_threadid = 0;
uint64_t m_time = 0;
stringstream m_ss;
};
日志级别
enum Level {
UNKNOW = 0,
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4,
FATAL = 5
};
|