整体设计
LogLevel - LogEvent
LogLevel 类内枚举日志级别 LogEvent 一个时间 log 需要输出的成员变量
cpp
class LogLevel{
public:
enum Level{
UNKNOW = 0,
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4,
FATAL = 5,
NOTEST = 6,
};
static const char* ToString(LogLevel::Level level); // 枚举转字符表示
static LogLevel::Level FromString(const std::string& str); // 字符串转枚举表达
};
//日志事件
class LogEvent{
public:
typedef std::shared_ptr<LogEvent> ptr; //使用ptr,就避免了对象的复制和拷贝
LogEvent(const char* file, int32_t line, uint32_t elapse, uint32_t threadId, const std::string& threadName, uint32_t fiberId , uint64_t time, LogLevel::Level level);
const char* getFile() const {return m_file;}
int32_t getLine() const {return m_line;}
uint32_t getElapse() const {return m_elapse;}
uint32_t getThreadId() const {return m_threadId;}
std::string getThreadName() const {return m_threadName;}
uint32_t getFiberId() const {return m_fiberId;}
uint64_t getTime() const {return m_time;}
std::string getContent() const {return m_ss.str();}
LogLevel::Level getLevel() const {return m_level;}
std::stringstream& getSS() {return m_ss;}
void format(const char* fmt, ...);
void format(const char* fmt, va_list al);
private:
const char* m_file = nullptr; //文件名
int32_t m_line = 0; //行号
uint32_t m_elapse = 0; //程序启动到现在的毫秒数
uint32_t m_threadId = 0; //线程
std::string m_threadName; //线程名
uint32_t m_fiberId = 0; //协程
uint64_t m_time = 0; //时间戳
std::stringstream m_ss;
LogLevel::Level m_level; //日志级别
};
<< 流式输出
getSS() 得到 stringstream,使得通过 << 输入日志信息。
printf 格式输出
cpp
void LogEvent::format(const char* fmt, ...){
va_list al;
va_start(al, fmt);
format(fmt, al);
va_end(al);
}
void LogEvent::format(const char* fmt, va_list al){
char* buf = nullptr;
int len = vasprintf(&buf, fmt, al);
if(len != -1){
m_ss << std::string(buf, len);
free(buf);
}
}
LogFormatter - FormatterItem
cpp
//日志格式器
class LogFormatter{
public:
typedef std::shared_ptr<LogFormatter> ptr;
/**
* @brief 构造函数
* @param[in] pattern 格式模板,参考sylar与log4cpp
* @details 模板参数说明:
* - %%m 消息
* - %%p 日志级别
* - %%c 日志器名称
* - %%d 日期时间,后面可跟一对括号指定时间格式,比如%%d{%%Y-%%m-%%d %%H:%%M:%%S},这里的格式字符与C语言strftime一致
* - %%r 该日志器创建后的累计运行毫秒数
* - %%f 文件名
* - %%l 行号
* - %%t 线程id
* - %%F 协程id
* - %%N 线程名称
* - %%% 百分号
* - %%T 制表符
* - %%n 换行
*
* 默认格式:%%d{%%Y-%%m-%%d %%H:%%M:%%S}%%T%%t%%T%%N%%T%%F%%T[%%p]%%T[%%c]%%T%%f:%%l%%T%%m%%n
*
* 默认格式描述:年-月-日 时:分:秒 [累计运行毫秒数] \\t 线程id \\t 线程名称 \\t 协程id \\t [日志级别] \\t [日志器名称] \\t 文件名:行号 \\t 日志消息 换行符
*/
LogFormatter(const std::string& pattern = "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T[%m]%n");
std::string format(std::shared_ptr<Logger> logger, LogEvent::ptr event);
void init(); // 解析模板字符串
bool isError() const {return m_error;}
std::string getPattern(){return m_pattern;}
public:
class FormatterItem{
public:
typedef std::shared_ptr<FormatterItem> ptr;
virtual ~FormatterItem(){}
virtual void format(std::ostream& os, std::shared_ptr<Logger> logger ,LogEvent::ptr event) = 0;
};
private:
std::string m_pattern; // 模板字符串 "%d{%Y-%m-%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
std::vector<FormatterItem::ptr> m_items; // 存储每一个日志类型的输出子类
bool m_error = false;
};
LogFormatter 定义了模板字符串 m_pattern,以及每一种格式的 FormatterItem 子类集合 m_items(通过解析这个字符串,得到存储每一个格式输出子类)
状态机解析⭐⭐⭐⭐⭐
LogFormatter 在 init(),状态机 解析 m_pattern。
cpp
// 初始化日志格式解析器
void LogFormatter::init() {
// 解析模式字符串,示例:"%d{%Y-%m-%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
// 存储解析结果的三元组容器:
// tuple<原始字符串, 格式参数, 类型>
// 类型说明:0-普通字符串,1-格式项
std::vector<std::tuple<std::string, std::string, int>> vec;
std::string nstr; // 临时存储普通字符
bool error = false; // 解析错误标志
// 遍历模式字符串每个字符
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() && m_pattern[i+1] == '%') {
nstr.append(1, '%');
i++; // 跳过下一个%
continue;
}
// 开始解析格式项(如%d{...})
size_t pos = i + 1;
int fmt_status = 0; // 0-寻找格式项,1-解析{}内参数
size_t fmt_begin = 0; // {的位置索引
std::string str; // 格式项标识符(如d)
std::string fmt; // {}内的格式参数(如%Y-%m-%d)
while(pos < m_pattern.size()) {
// 状态0:寻找格式项结束或{
if(fmt_status == 0 &&
!isalpha(m_pattern[pos]) && // 非字母字符作为分隔,相当于 %f:%l,其中的':'。
m_pattern[pos] != '{' &&
m_pattern[pos] != '}') {
str = m_pattern.substr(i+1, pos-i-1);
break;
}
// 遇到{开始解析格式参数
if(fmt_status == 0 && m_pattern[pos] == '{') {
str = m_pattern.substr(i+1, pos-i-1);
fmt_status = 1;
fmt_begin = pos;
pos++;
continue;
}
// 状态1:寻找}结束
if(fmt_status == 1) {
if(m_pattern[pos] == '}') {
fmt = m_pattern.substr(fmt_begin+1, pos-fmt_begin-1);
fmt_status = 0;
pos++;
break;
}
}
pos++;
if(pos == m_pattern.size()){ //如果到了结尾,还是没有str
if(str.empty()){
str = m_pattern.substr(i+1);
}
}
}
// 处理解析结果
if(fmt_status == 0) {
// 保存之前的普通字符串
if(!nstr.empty()) {
vec.emplace_back(nstr, "", 0);
nstr.clear();
}
// 保存当前格式项
vec.emplace_back(str, fmt, 1);
i = pos - 1; // 跳过已解析部分
} else {
// {}不匹配的错误处理
std::cout << "pattern parse error:" << m_pattern << " - "
<< m_pattern.substr(i) << std::endl;
vec.emplace_back("<<pattern_error>>", fmt, 0);
error = true;
}
}
// 保存最后的普通字符串
if(!nstr.empty()) {
vec.emplace_back(nstr, "", 0);
}
// 格式项映射表(关键说明)⭐⭐⭐
static std::map<std::string, std::function<FormatterItem::ptr(const std::string&)>> s_format_items = {
// 格式说明:
// %m 消息内容 | %p 日志级别 | %r 耗时(毫秒)
// %c 日志器名称 | %t 线程ID | %N 线程名称
// %F 协程ID | %l 行号 | %d 时间 | %n 换行
// %f 文件名 | %T 制表符
#define XX(str, C) {#str, [](const std::string& fmt) { return FormatterItem::ptr(new C(fmt)); }}
XX(m, MessageFormatterItem), // 消息体
XX(p, LevelFormatterItem), // 日志级别
XX(r, ElapseFormatterItem), // 程序启动后的耗时
XX(c, LoggerNameFormatterItem), // 日志器名称
XX(t, ThreadIdFormatterItem), // 线程ID
XX(N, ThreadNameFormatterItem), // 线程名称
XX(F, FiberIdFormatterItem), // 协程ID
XX(l, LineFormatterItem), // 行号
XX(d, DataTimeFormatterItem), // 时间(可带格式参数)
XX(n, NewLineFormatterItem), // 换行符
XX(f, FilenameFormatterItem), // 文件名
XX(T, TabFormatterItem) // 制表符
#undef XX
};
// 构建最终的格式化项列表
for(auto& i : vec) {
if(std::get<2>(i) == 0) {
// 普通字符串项
m_items.push_back(std::make_shared<StringFormatterItem>(std::get<0>(i)));
} else {
// 查找格式项映射
auto it = s_format_items.find(std::get<0>(i));
if(it == s_format_items.end()) {
// 无效格式项处理
m_items.push_back(std::make_shared<StringFormatterItem>(
"<<error_format %" + std::get<0>(i) + ">>"));
error = true;
} else {
// 创建对应的格式化项(如时间格式化项)
m_items.push_back(it->second(std::get<1>(i)));
}
}
}
// 设置最终错误状态
if(error) {
m_error = true;
}
}
emplace_back (C++11)⭐⭐⭐ 具有push_back()和emplace_back()函数功能的容器:deque、list、vector ● push_back()向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素) ● emplace_back()则直接在容器尾部创建这个元素省去了拷贝或移动元素的过程,所以在效率上更优。
解析完毕后,调用 format。遍历执行 m_items 即可。
cpp
std::string LogFormatter::format(std::shared_ptr<Logger> logger, LogEvent::ptr event){
// TODO
std::stringstream ss;
for(auto& it : m_items){
it->format(ss, logger, event);
}
return ss.str();
}
LogAppender
LogAppender 输出器,通过子类StdoutLogAppender,FileLogAppender 继承,实现多种输出方式。 LogAppender 内有 LogFormatter::ptr 成员变量
cpp
//日志输出器
class LogAppender{
public:
typedef std::shared_ptr<LogAppender> ptr;
typedef SpinkLock MutexType;
LogAppender(LogLevel::Level level, LogFormatter::ptr formatter);
virtual ~LogAppender(){}
//纯虚函数
virtual void log(std::shared_ptr<Logger> logger, LogEvent::ptr event) = 0;
virtual std::string toYamlString() = 0;
void setLevel(LogLevel::Level level){m_level = level;}
void setFormatter(LogFormatter::ptr val);
LogFormatter::ptr getFormatter();
protected:
LogLevel::Level m_level;
LogFormatter::ptr m_formatter; //通过这个类,对日志进行格式化
MutexType m_mutex;
};
//输出到控制台
class StdoutLogAppender : public LogAppender{
public:
typedef std::shared_ptr<StdoutLogAppender> ptr;
StdoutLogAppender(LogLevel::Level level, LogFormatter::ptr formatter);
std::string toYamlString();
void log(std::shared_ptr<Logger> logger, LogEvent::ptr event) override;
private:
};
//输出到文件
class FileLogAppender : public LogAppender{
public:
typedef std::shared_ptr<FileLogAppender> ptr;
FileLogAppender(const std::string& filename, LogLevel::Level level, LogFormatter::ptr formatter);
std::string toYamlString();
void log(std::shared_ptr<Logger> logger, LogEvent::ptr event) override;
// 重新打开文件
// 如果 当前的日志事件 比 上一个日志事件 超过 interval 秒,就重新打开一次日志文件。把缓存写入。也避免线程间频繁开关 文件。
// 当 interval = 0时,直接重新打开文件。
bool reopen(uint64_t now, uint64_t interval = 3);
private:
std::string m_filename;
std::ofstream m_filestream; //#include <fstream>
bool m_reopenError;
uint64_t m_lastTime; // 文件最近一次打开的事件 事件
};
LogAppender 日志输出其也能控制输出的日志级别(例如,实现不同级别的日志输出到不同文件。) 日志输出器 存在 重构的空间 ⭐⭐⭐(例如,基于异步日志系统)
Logger
Logger 日志器 Logger 内有 std::listLogAppender::ptrm_appenders 成员变量 存储所有的输出器。
cpp
//继承 public std::enable_shared_from_this<Logger> 可以在成员函数获得自己的shard_ptr
class Logger : public std::enable_shared_from_this<Logger>{
public:
typedef std::shared_ptr<Logger> ptr;
typedef SpinkLock MutexType;
Logger(const std::string name = "root");
void log(LogEvent::ptr event);
void setLevel(LogLevel::Level level) {m_level = level;}
LogLevel::Level getLevel(){return m_level;}
const std::string& getName() const {return m_name;}
void addAppender(LogAppender::ptr appender);
void delAppender(LogAppender::ptr appender);
void clearAppender();
std::string toYamlString();
private:
std::string m_name; //日志名称
LogLevel::Level m_level; //日志级别,当事件的日志级别大于等于该日志器的级别时,才输出
std::list<LogAppender::ptr> m_appenders; //Appender集合
MutexType m_mutex;
};
输出时,遍历所有的 appenders 进行输出。
cpp
void Logger::log(LogEvent::ptr event){
if(event->getLevel() >= m_level){
// todo
auto self = shared_from_this();
for(auto& i: m_appenders){
i->log(self, event);
}
}
}
LogEventWrap
使用LogEventWarp管理 event和logger,在析构的时候,调用logger的方法 流式输出 event
cpp
class LogEventWrap{
public:
LogEventWrap(Logger::ptr logger, LogEvent::ptr event);
~LogEventWrap();
std::stringstream& getSS() {return m_event->getSS();}
LogEvent::ptr getEvent() {return m_event;}
private:
Logger::ptr m_logger;
LogEvent::ptr m_event;
};
// cpp
LogEventWrap::LogEventWrap(Logger::ptr logger, LogEvent::ptr event)
:m_logger(logger), m_event(event){}
LogEventWrap::~LogEventWrap(){
m_logger->log(m_event);
}
构造 LogEventWrap,获取到 LogEvent 的 m_ss,用户可以通过 << 写入 日志信息 同时支持 printf 格式
cpp
#define SYLAR_LOG_LEVEL(logger, level) \
if(logger->getLevel() <= level) \
sylar::LogEventWrap(logger, sylar::LogEvent::ptr( \
new sylar::LogEvent(__FILE__, __LINE__, 0 , \
sylar::GetThreadId(), sylar::Thread::GetName(), \
sylar::GetFiberId(), time(0), level))).getSS()
#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
#define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)
#define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)
#define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)
#define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)
// printf形式
#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
if(logger->getLevel() <= level) \
sylar::LogEventWrap(logger, sylar::LogEvent::ptr( \
new sylar::LogEvent(__FILE__, __LINE__, 0 , \
sylar::GetThreadId(), sylar::Thread::GetName(), \
sylar::GetFiberId(), time(0), level))).getEvent()->format(fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_DEBUG(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_INFO(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_WARN(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_ERROR(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_FATAL(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, format, __VA_ARGS__)
#define SYLAR_LOG_ROOT() sylar::LoggerMgr::GetInstance()->getRoot()
#define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger(name)
#define SYLAR_Log_YAML2String() sylar::LoggerMgr::GetInstance()->toYamlString()
LoggerManager
整个系统可以存在多个 Logger 日志器。
cpp
class LoggerManager{
public:
typedef SpinkLock MutexType;
LoggerManager();
Logger::ptr getLogger(const std::string& name);
Logger::ptr getRoot(){return m_root;}
void init();
std::string toYamlString(); // 将实例序列化
private:
MutexType m_mutex;
std::map<std::string, Logger::ptr> m_loggers;
Logger::ptr m_root;
};
cpp
LoggerManager::LoggerManager(){
m_root.reset(new Logger); // 默认构造Logger,此时level是UNKNOW最低
// 默认是输出std,跟随m_root的level,以及使用默认的格式器
m_root->addAppender(LogAppender::ptr(new StdoutLogAppender(m_root->getLevel(), LogFormatter::ptr(new LogFormatter()))));
m_loggers[m_root->getName()] = m_root; // 添加到m_loggers
// 当配置项发生改变,能改变这个 m_root 的。(见日志系统⭐)
}
/**
* 如果指定名称的日志器未找到,那么就新创建一个,新创建的 Logger 配置默认的 Appender和默认的 LogFormatter
*/
Logger::ptr LoggerManager::getLogger(const std::string& name){
MutexType::Lock lock(m_mutex);
auto it = m_loggers.find(name);
if(it != m_loggers.end()){
return it->second;
}
Logger::ptr logger(new Logger(name));
// 添加一个默认的 std 输出器
logger->addAppender(LogAppender::ptr(new StdoutLogAppender(m_root->getLevel(), LogFormatter::ptr(new LogFormatter()))));
m_loggers[name] = logger;
return logger;
}
备注
当前的日志系统是细粒度的锁,只针对 LogAppender 锁。(高效)
知识点
1. 使用 智能指针 高效管理实例
2. 使用宏定义简化switch
cpp
const char* LogLevel::ToString(LogLevel::Level level){
switch(level){
#define XX(name) \
case LogLevel::name: \
return #name; \ // #name 表示字符串
break;
XX(DEBUG);
XX(INFO);
XX(WARN);
XX(ERROR);
XX(FATAL);
#undef XX
default:
return "UNKNOW";
}
return "UNKNOW";
}
3. 宏定义 + lambda
cpp
/**
* %m -- 消息体
* %p -- level
* %r -- 启动后的时间
* %c -- 日志名称
* %t -- 线程id
* %F -- 协程id
* %l -- 行号
* %d -- 时间
* %n -- 回车换行
* %f -- 文件名
* %T -- Tab
* %s -- string
*/
// 这里的map存储着,每个字符对于的仿函数。
// 普通字符串 以及 DataTime的 FormatterItem 子类 需要初始化str。所以会有一个传入str的构造函数。
static std::map<std::string, std::function<FormatterItem::ptr(const std::string& str)>> s_format_items = {
#define XX(str , C) \
{#str , [](const std::string& fmt){return FormatterItem::ptr(new C(fmt));}}
// #str 表示 字符串
XX(m , MessageFormatterItem),
XX(p , LevelFormatterItem),
XX(r , ElapseFormatterItem),
XX(c , LoggerNameFormatterItem),
XX(t , ThreadIdFormatterItem),
XX(F , FiberIdFormatterItem),
XX(l , LineFormatterItem),
XX(d , DataTimeFormatterItem),
XX(n , NewLineFormatterItem),
XX(f , FilenameFormatterItem),
XX(T , TabFormatterItem),
#undef XX
};
// for循环中的auto&用于引用遍历容器中的元素。
// 最终得到每一个FormatterItem的子类的对象指针
// m_items内的顺序其实就是m_pattern格式解析的顺序。
for(auto& i : vec){
if(std::get<2>(i) == 0){
// 普通字符串输出 的 vec里 三元组 是 <str , "", 0>
m_items.push_back(FormatterItem::ptr(new StringFormatterItem(std::get<0>(i))));
} else {
// 需要解析的 vec 三元组 是 <str, fmt, 1>
auto it = s_format_items.find(std::get<0>(i));
if(it == s_format_items.end()){
m_items.push_back(FormatterItem::ptr(new StringFormatterItem("<< error_format %" + std::get<1>(i) + ">>")));
} else {
m_items.push_back(it->second(std::get<1>(i)));
}
}
}
4. 宏定义简化调用
cpp
#define SYLAR_LOG_LEVEL(logger, level) \
if(logger->getLevel() <= level) \
sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(__FILE__, __LINE__, 0 , sylar::GetThreadId(), sylar::GetFiberId(), time(0), level))).getSS()
#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
#define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)
#define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)
#define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)
#define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)
// 提供另一种不同个 << 传入,而是类似 printf 的。
#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
if(logger->getLevel() <= level) \
sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(__FILE__, __LINE__, 0, \
sylar::GetThreadId(), sylar::GetFiberId(), \
time(0), level))).getEvent()->format(fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_DEBUG(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_INFO(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_WARN(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_ERROR(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_FATAL(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, format, __VA_ARGS__)
5. 实现C风格的输出(va_list 与 vasprintf)
cpp
#include <sstream>
#include <stdarg.h>
std::string format(const char* fmt, ...){
std::stringstream ss;
va_list args; //定义了一个变量args,用于存储可变参数列表。
va_start(args,fmt); //初始化args,使其指向fmt之后的第一个参数。这是处理可变参数列表的必要步骤。
char* buf = nullptr;
/**
vasprintf
这是一个C语言函数,用于将可变参数列表args按照格式化字符串fmt格式化为一个字符串,
并将其存储到动态分配的内存中,buf指向该内存。
该函数返回格式化后的字符串长度(不包括结尾的\0),如果失败则返回-1。
*/
int len = vasprintf(&buf,fmt,args);
if(len != -1){
ss<<std::string(buf,len);
free(buf);
}
va_end(args);
return ss.str();
}
6. RAII (使用匿名对象 析构 进行流式输出)
匿名对象:执行完后立即析构 具名对象(栈内):超出作用域后析构 具名对象(堆内):手动释放后析构
cpp
#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
if(logger->getLevel() <= level) \ //创建匿名对象
sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(__FILE__, __LINE__, 0, \
sylar::GetThreadId(), sylar::GetFiberId(), \
time(0), level))).getEvent()->format(fmt, __VA_ARGS__)
7. 模板实现单例
cpp
#ifndef __SYLAR_SINGLETON_H_
#define __SYLAR_SINGLETON_H_
namespace sylar {
//X 为了创造多个实例对应的Tag
//N 同一个Tag创造多个实例索引
template<class T, class X = void, int N = 0>
class Singleton {
public:
static T* GetInstance() {
static T v;
return &v;
}
};
//X 为了创造多个实例对应的Tag
//N 同一个Tag创造多个实例索引
template<class T, class X = void, int N = 0>
class SingletonPtr {
public:
static std::shared_ptr<T> GetInstance() {
static std::shared_ptr<T> v(new T);
return v;
}
};
}
#endif
8. 时间戳获取和格式转换
cpp
int main(int argc,char** argv){
const std::string m_format = "%Y-%m-%d %H:%M:%S";
struct tm tm;
time_t t = time(0);
localtime_r(&t, &tm); //使用 localtime_r 函数将时间戳 t 转换为本地时间,并将结果存储到 tm 结构体中。
char buf[64];
// 使用 strftime 函数将 tm 结构体中的时间信息格式化为字符串,按照 m_format 中的格式输出到 buf 中。
strftime(buf, sizeof(buf), m_format.c_str(), &tm);
std::cout << buf << std::endl;
return 0;
}
9. 线程号获取(PID)
cpp
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
int main(int argc,char** argv){
std::cout << syscall(SYS_gettid) << std::endl;
return 0;
}
潜在改进点
- 添加LogAppender: Rolling File Appender,循环覆盖写文件 Rolling Memory Appender,循环覆盖写内存缓冲区 支持日志文件按大小分片或是按日期分片 支持网络日志服务器,比如syslog