Linux -- 日志
一 日志的重要性
在之前的编程经历中,如果我们的程序运行出现了问题,都是通过 标准输出 或 标准错误 将 错误信息 直接输出到屏幕上,以此来排除程序中的错误。
这在我们以往所写的程序中使用没啥问题,但如果出错的是一个不断在运行中的服务,那问题就大了,因为服务器是不间断运行中,直接将 错误信息 输出到屏幕上,会导致错误排查变得极为困难。
其实,我们可以将各种 错误信息 组织管理,使 每种错误有属于自己的格式(包括时间、文件名及行号、错误等级等),利于排查问题,同时,把这些错误写入一个单独的地方,便于我们查找和阅读(因为错误信息繁多,我们一般写入文件中)。
这种错误信息的集合,我们便称为日志。
所以接下来我们将会实现一个简易版日志器,用于定向输出我们的日志信息。
二 可变参数
日志需要我们指定格式并输出,依赖于可变参数。
因此我们需要认识一下可变参数的使用,主要是几个宏。
#include va_list // 指向可变参数列表的指针 va_start() // 将指针指向起始地址 va_arg() // 根据类型,提取可变参数列表中的参数 va_end() // 将指针置为空
示例,我们通过可变参数实现参数遍历:
#include
#include
void foreach(int format, ...){
va_list p;
va_start(p, format);
// 接下来就是获取其中的每一个参数
for(int i = 0; i
这种依靠自己动手的方法比较麻烦,我们也可以借助标准库提供的 vsnprintf() 函数进行参数解析
//头文件:
#include
//函数声明:
int vsnprintf(char* str, size_t size, const char* format, va_list ap);
- char *str , 把生成的格式化的字符串存放在这里.
- size_t size , str可接受的最大字符数 ,防止产生数组越界.
- const char *format , 指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序。
- va_list ap , va_list变量.
函数功能:将可变参数格式化输出到一个字符数组。
返回值:执行成功,返回最终生成字符串的长度,若生成字符串的长度大于size,则将字符串的前size个字符复制到str,同时将原串的长度返回(不包含终止符);执行失败,返回负值,并置errno.
#include
#include
#include
using namespace std;
void logtest(int format,...){
va_list a;
va_start(a,format);
char msg[1024];
int n = vsnprintf(msg,sizeof(msg),"%d-%d-%d-%d-%d",a);
if(n tm_mon + 1, st->tm_mday, st->tm_hour, st->tm_min, st->tm_sec);
return buff;
}
3.3 日志格式
日志的格式我们一般可以自己规定,这里我们规定我们日志的格式为:
[时间] [PID] {消息体}
接下来就是获取进程 PID,这个简单,直接使用 getpid() 函数获取即可,最后是解析参数,需要用到 vsnprintf() 函数,只要传入缓冲区和 va_list 指针,该函数就可以自动解析出参数,并存入缓冲区中 。
void logMessage(int level, const char* format, ...){
//截获主体消息
char msgbuff[1024];
va_list p;
va_start(p, format); //将 p 定位至 format 的起始位置
vsnprintf(msgbuff, sizeof(msgbuff), format, p); //自动根据格式进行读取
va_end(p);
}
形成测试版日志信息函数
//处理信息
void logMessage(int level, const char* format, ...){
//日志格式: [时间] [PID] {消息体}
string logmsg = getLevel(level); //获取日志等级
logmsg += " " + getTime(); //获取时间
logmsg += " [" + to_string(getpid()) + "]"; //获取进程PID
//截获主体消息
char msgbuff[1024];
va_list p;
va_start(p, format); //将 p 定位至 format 的起始位置
vsnprintf(msgbuff, sizeof(msgbuff), format, p); //自动根据格式进行读取
va_end(p);
logmsg += " {" + string(msgbuff) + "}"; //获取主体消息
printf("%s\n", logmsg); //这里先打印 方便进行测试
}
为什么日志消息最后还是向屏幕输出?这样组织日志消息的好处是什么?
因为现在还在测试阶段,等测试完成后,可以将日志消息存入文件中,做到持久化存储;至于统一组织的好处不言而喻,能够确保每条日志消息都包含必要信息,便于排查错误
3.4 Log.hpp 头文件代码
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
enum{
Debug = 0,
Info,
Warning,
Error,
Fatal
};
string getLevel(int level){
vector vs = {"", "", "", "", "", ""};
//避免非法情况
if(level = vs.size() - 1) {
return vs[vs.size() - 1];
}
return vs[level];
}
string getTime(){
time_t t = time(nullptr); //获取时间戳
struct tm *st = localtime(&t); //获取时间相关的结构体
char buff[128];
snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d", st->tm_year + 1900, st->tm_mon + 1, st->tm_mday, st->tm_hour, st->tm_min, st->tm_sec);
return buff;
}
//处理信息
void logMessage(int level, const char* format, ...){
//日志格式: [时间] [PID] {消息体}
string logmsg = getLevel(level); //获取日志等级
logmsg += " " + getTime(); //获取时间
logmsg += " [" + to_string(getpid()) + "]"; //获取进程PID
//截获主体消息
char msgbuff[1024];
va_list p;
va_start(p, format); //将 p 定位至 format 的起始位置
vsnprintf(msgbuff, sizeof(msgbuff), format, p); //自动根据格式进行读取
va_end(p);
logmsg += " {" + string(msgbuff) + "}"; //获取主体消息
cout