公共头文件
定义了一个枚举类 LOG_LEVEL
用于表示日志级别,并创建了两个字符串数组 IndToStr
和 LLTOSTR
来将这些级别转换为对应的字符串表示
enum class LOG_LEVEL
{
TRACE = 0,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
SYSERR,
SYSFATAL,
NUM_LOG_LEVELS
};
static const char *IndToStr[] =
{
"TRACE", // 0
"DEBUG", // 1
"INFO", // 2
"WARN", // 3
"ERROR", // 4
"FATAL", // 5
"SYSERR",
"SYSFATAL",
"NUM_LOG_LEVELS" // 6
};
static const char *LLTOSTR[] = {
"TRACE", // 0
"DEBUG", // 1
"INFO", // 2
"WARN", // 3
"ERROR", // 4
"FATAL", // 5
"SYSERR",
"SYSFATAL",
"NUM_LOG_LEVELS"};
}
TRACE 最详细的日志,用于调试内部流程
DEBUG 开发调试信息,生产环境通常关闭
INFO 关键业务流程的运行状态信息
WARN 潜在问题或非关键错误
ERROR 功能异常,但系统仍可继续运行
FATAL 严重错误,可能导致当前操作失败
SYSERR 系统级错误,如资源耗尽
SYSFATAL 系统无法继续运行的致命错误
日志流对象 LogMessage
功能:1. 构造日志消息流 2. 添加正文信息 3. 获取完整的日志字段信息字符串
成员变量
private:
std::string header_; //Timestamp + level + filie.cpp + line
std::string text_; //正文
tulun::LOG_LEVEL level_;
主要成员方法
- 构造函数
public:
LogMessage(const tulun::LOG_LEVEL &level,
const std::string &filename,
const std::string &funcname,
const int line):header_{},text_{},level_(level)
{
std::stringstream ss;
ss << tulun::Timestamp::Now().toFormattedString()<<" ";
ss << tulun::IndexToStr[static_cast<int>(level_)] << " ";
const size_t pos = filename.rfind('/');
const std::string fname = filename.substr(pos + 1);
ss << fname << " " << funcname << " " << line << " ";
header_ = ss.str();
}
注意:我们传入的文件名一般用 __FILE__宏表示,但此展开是标准文件名,即前面会加具体的路径,这个我们是不需要的,有些冗余,所以传入的文件名我们需要做一些改动,删除具体路径信息。
- 重载运算符
LogMessage &operator<<(const _Ty &text)
{
std::stringstream ss;
ss << " : " << text;
text_ += ss.str();
return *this;
}
日志对象 Logger
是一个基于 RAII 技术的日志系统
作用:
1. 设置日志等级
2. 设置打印回调函数,默认打印到终端
3. 设置刷新回调函数
4. 获取日志流对象
5. 一个日志对象对应一条完整的日志信息,析构函数负责将日志信息写入终端或文件
成员变量
tulun::LogMessage impl_; //日志消息对象
//当前日志级别,控制哪些级别的日志可以被输出
static tulun::LOG_LEVEL s_level_; //=InitLogLevel()
输出与刷新机制
//定义输出函数类型,将日志输出到目标位置
using OutputFun = std::function<void(const std::string &)>;
//定义刷新函数类型,确保缓冲区日志被立即输出
using FlushFun = std::function<void(void)>;
static OutputFun s_output_;
static FlushFun s_flush_;
static void SetOuput(OutputFun fun)// 设置自定义输出函数
{
s_output_=fun;
}
static void SetFlush(FlushFun fun)//设置自定义刷新函数
{
s_flush_=fun;
}
void defaultOutput(const string&msg) //默认输出函数,日志输出到stdout
{
//使用fwrite写入到终端
size_t n=fwrite(msg.c_str(),sizeof(char),msg.size(),stdout);
}
void defaultFlush()
{
fflush(stdout);
}
- 通过函数对象实现策略模式,允许动态替换日志输出行为
构造与析构函数
Logger(const tulun::LOG_LEVEL &level,
const string &filename,
const string &funcname,
const int line)
:impl_(level,filename,funcname,line){}
__FILE__
:当前源文件名(预定义宏)__func__
:当前函数名(预定义宏)__LINE__
:当前行号(预定义宏)
~Logger()
{
//添加换行符,并且使完整日志输出
impl_<<'\n';
s_output_(impl_.toString());
//确保所有日志消息被立即输出
s_flush_();
//如果是FATAL级别的日志,中止运行程序
if(impl_.getLogLevel()==LOG_LEVEL::FATAL){
frpintf(stderr,"Process exit\n");
exit(EXIT_FAILURE);
}
}
使用到了RAII思想
- 构造时准备资源(记录上下文)
- 析构时释放资源(输出并刷新日志)
宏定义
// 日志宏定义 - 用于在代码中方便地记录不同级别的日志
// 每个宏都会检查当前日志级别,只有级别足够高时才会生成日志
// 使用方式示例:LOG_INFO << "This is an info message";
// \ 称为宏的续行符 ,此符号后面直接跟回车,不要出现任何字符
#define LOG_TRACE \
if (Logger::getLogLevel() <= LOG_LEVEL::TRACE) \
Logger(LOG_LEVEL::TRACE, __FILE__, __func__, __LINE__).stream()
#define LOG_DEBUG \
if (Logger::getLogLevel() <= LOG_LEVEL::DEBUG) \
Logger(LOG_LEVEL::DEBUG, __FILE__, __func__, __LINE__).stream()
#define LOG_INFO \
if (Logger::getLogLevel() <= LOG_LEVEL::INFO) \
Logger(LOG_LEVEL::INFO, __FILE__, __func__, __LINE__).stream()
#define LOG_WARN \
Logger(LOG_LEVEL::WARN, __FILE__, __func__, __LINE__).stream()
#define LOG_ERROR \
Logger(LOG_LEVEL::ERROR, __FILE__, __func__, __LINE__).stream()
#define LOG_FATAL \
Logger(LOG_LEVEL::FATAL, __FILE__, __func__, __LINE__).stream()
#define LOG_SYSERR \
Logger(LOG_LEVEL::ERROR, __FILE__, __func__, __LINE__).stream()
#define LOG_SYSFATAL \
Logger(LOG_LEVEL::FATAL, __FILE__, __func__, __LINE__).stream()
}