我们运行代码的时候,我们希望有各种各样的运行时候的一些信息。这也就是日志
它一半有日志时间,日志的等级,日志的内容,文件的名称和行号。
对于日志等级一半有如下的
Info : 常规消息
Warning : 报警信息
Error : 比较严重了,可能需要立即处理
Fatal : 致命的
Debug : 调试
关于可变参数我们主要用下面四个中的前三个
我们用如下代码来简单的介绍一下
int sum(int n, ...)
{
//char* 可以去提取一个一个的参数
va_list s;
va_start(s, n);
int SUM = 0;
for(int i = 0; i < n; i++)
{
SUM += va_arg(s,int);
}
va_end(s); //s = NULL
return SUM;
}
在这段代码中,这个va_list其实就是一个char*类型的指针。
我们这个可变参数的函数至少需要带有一个参数。
因为我们传递参数的时候是从右向左传递的,所以我们是可以确定出栈顶的元素就是我们所确定的一个元素的地址
这个va_start是一个宏,代表着让s这个指针指向n。也就是栈顶
然后我们va_arg也是一个宏,它的作用是让s指针往栈里面移动第二个参数的类型大小,并解引用
va_end这个宏的作用是让指针置为空
最终我们就可以计算出这个sum的大小了。这个第一个参数我们一般是用作确定后面的参数有几个
像我们所谓的printf里面也是类似的方法,不过要注意的时候,因为第一个参数是字符串参数,它是可以通过解析字符串里面的%s等格式化控制来确定后面有几个参数的,从而完成依次的替换的。
我们现在来设计我们的日志的函数
只需要下面这样即可
void logmessage(int level, char* format, ...)
{
}
首先对于等级,我们可以直接用宏来表示
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
接下是时间,我们可以使用时间戳,不过其实还有其他的系统调用接口
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
第一个参数是一个结构体,里面的两个参数它可以获取秒,微秒。
第二个参数与时区相关,我们可以不用管,直接置为NULL即可
还有一个接口是这样的
#include <time.h>
struct tm *localtime(const time_t *timep);
这个函数的功能是将时间戳转化为一个结构体,这个结构体里面有时分秒月年等信息
我们可以简单的应用一下
我们会注意到时间存在一定的问题,这是因为它是从1900年开始计时的,这个月是从0开始的,所以我们可以进行一下调整
如下所示就正确了
void logmessage(int level, char* format, ...)
{
time_t t = time(nullptr);
struct tm* ctime = localtime(&t);
printf("%d-%d-%d %d:%d:%d\n",ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
}
我们可以用vsnprintf函数
最终我们的代码为如下
void logmessage(int level, char* format, ...)
{
char leftbuffer[SIZE];
time_t t = time(nullptr);
struct tm* ctime = localtime(&t);
snprintf(leftbuffer, SIZE,
"[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
char rightbuffer[SIZE];
va_list s;
va_start(s, format);
vsnprintf(rightbuffer, SIZE, format, s);
va_end(s);
char logtxt[2*SIZE];
snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
std::cout << logtxt << std::endl;
}
如下所示,这里我们就引进了我们刚刚使用的日志
运行结果如下,我们现在可以看到,这样子就规范多了
如下代码所示,我们再将前面的日志给封装成一个类,然后实现一下分类打印,向文件打印,向显示屏打印等等功能。
#pragma once
#include <stdarg.h>
#include <iostream>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define SIZE 1024
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
#define Screen 1
#define Onefile 2
#define Classfile 3
#define LogFile "log.txt"
class Log
{
public:
Log()
{
printMethod = Screen;
path = "./log/";
}
void Enable(int method)
{
printMethod = method;
}
std::string levelToString(int level)
{
switch(level)
{
case Info : return "Info";
case Debug : return "Debug";
case Warning : return "Warning";
case Error : return "Error";
case Fatal : return "Fatal";
default : return "None";
}
}
void operator()(int level, char* format, ...)
{
char leftbuffer[SIZE];
time_t t = time(nullptr);
struct tm* ctime = localtime(&t);
snprintf(leftbuffer, SIZE,
"[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
char rightbuffer[SIZE];
va_list s;
va_start(s, format);
vsnprintf(rightbuffer, SIZE, format, s);
va_end(s);
char logtxt[2*SIZE];
snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
//std::cout << logtxt << std::endl;
PrintLog(level, logtxt);
}
// void logmessage(int level, char* format, ...)
// {
// char leftbuffer[SIZE];
// time_t t = time(nullptr);
// struct tm* ctime = localtime(&t);
// snprintf(leftbuffer, SIZE,
// "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
// ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
// char rightbuffer[SIZE];
// va_list s;
// va_start(s, format);
// vsnprintf(rightbuffer, SIZE, format, s);
// va_end(s);
// char logtxt[2*SIZE];
// snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
// //std::cout << logtxt << std::endl;
// PrintLog(level, logtxt);
// }
void PrintLog(int level, const std::string& logtxt)
{
switch(printMethod)
{
case Screen:
std::cout << logtxt << std::endl;
break;
case Onefile:
PrintOnefile(LogFile , logtxt);
break;
case Classfile:
PrintClassfile(level, logtxt);
break;
default: break;
}
}
void PrintOnefile(const std::string& filename, const std::string& logtxt)
{
std::string logname = path + filename;
int fd = open(logname.c_str(), O_WRONLY|O_CREAT|O_APPEND, 0666);
if(fd < 0)
{
return;
}
write(fd, logtxt.c_str(), logtxt.size());
close(fd);
}
void PrintClassfile(int level, const std::string& logtxt)
{
std::string filename = LogFile;
filename += ".";
filename += levelToString(level);
PrintOnefile(filename, logtxt);
}
~Log()
{}
private:
int printMethod;
std::string path;
};