日志是日后工作中非常重要的一部分,现在写一份简单的日志项目可以帮助我们熟悉并理解原理。
目录
- 设计思路:
- 一些实现细节:
- 代码:
- 日志的使用方法:
设计思路:
图示是我们的最终目的。
- 设计一个类,这个类中存放着以上数据。
- 设计一个Log类,这个类中有一个Print函数进行按照指定格式(文件或显示器)输出,我们向这个成员函数中传参进行打印。
一些实现细节:
- 获取时间:可以使用time函数获取时间戳,由localtime进行格式转换为我们指定的样式。
- printf的格式输出:
printf("%02d", 10);
代表域宽为2,数字在右边,空余补0。 - 可变参数的处理:
有很多的方法:
比如自己逐个提取或者利用现有的接口提取。
这并不是重点,会用即可。
可变参数的博客。
我们还是最好使用接口进行提取,避免看很多麻烦的操作,那就是
与snprintf使用方法类似,可以直接将转换后的直接写入指定字符串中!
- 关于封装:不同功能的函数可以在成员函数与非成员函数之间自由切换(根据对成员变量的需求…)
- 宏中的可变参数:这个
__VA_ARGS__
链接包含了一些宏的用法。
代码:
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fstream>
#include <pthread.h>
namespace log_ns
{
#define SCREE_TYPE 1
#define FILE_TYPE 2
const char *gfilename = "log.txt";
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
enum
{
DEBUG = 1,
INFO,
WARNNING,
ERROR,
FATAL
};
std::string LevelTostrint(int level)
{
switch (level)
{
case DEBUG:
return "DEBUG";
break;
case INFO:
return "INFO";
break;
case WARNNING:
return "WARNING";
break;
case ERROR:
return "ERROR";
break;
case FATAL:
return "FATAL";
break;
default:
return "Unknow";
}
}
class LogMessage
{
public:
std::string _level;
pid_t _id;
std::string _filaname;
int _filenumber;
std::string _curr_time;
std::string _logmsg;
};
std::string GetCurTime()
{
time_t timestamp = time(nullptr);
tm *ptm = localtime(×tamp);
char buffer[128];
snprintf(buffer, sizeof(buffer), "%d/%02d/%02d %02d:%02d:%02d",
ptm->tm_year + 1900,
ptm->tm_mon + 1,
ptm->tm_mday,
ptm->tm_hour,
ptm->tm_min,
ptm->tm_sec);
return buffer;
}
class Log
{
public:
Log(int type = SCREE_TYPE, std::string FILEName = gfilename)
: _type(type), _FILEName(gfilename)
{}
~Log()
{}
void Print(int level, const char *filename, int filenumber, const char *msg, ...)
{
LogMessage lmsg;
lmsg._level = LevelTostrint(level);
lmsg._id = getpid();
lmsg._filaname = filename;
lmsg._filenumber = filenumber;
lmsg._curr_time = GetCurTime();
// 处理可变参数
va_list ap;
va_start(ap, msg);
char buffer[1024];
vsnprintf(buffer, sizeof(buffer), msg, ap);
lmsg._logmsg = buffer;
va_end(ap);
// 打印到指定文件
pthread_mutex_lock(&gmutex);
Flush(lmsg);
pthread_mutex_unlock(&gmutex);
}
void Enable(int type)
{
_type = type;
}
void FlushScree(const LogMessage &logMsg)
{
printf("[%s][%d][%s][%d][%s] %s",
logMsg._level.c_str(),
logMsg._id,
logMsg._filaname.c_str(),
logMsg._filenumber,
logMsg._curr_time.c_str(),
logMsg._logmsg.c_str());
}
void FlushFILE(const LogMessage &logMsg)
{
std::ofstream out(_FILEName.c_str(), std::fstream::app | std::fstream::out);
if (!out.is_open())
{
perror("open fail");
return;
}
char buffer[2048];
snprintf(buffer, sizeof(buffer), "[%s][%d][%s][%d][%s] %s",
logMsg._level.c_str(),
logMsg._id,
logMsg._filaname.c_str(),
logMsg._filenumber,
logMsg._curr_time.c_str(),
logMsg._logmsg.c_str());
out << buffer;
out.close();
}
void Flush(const LogMessage &logMsg)
{
switch (_type)
{
case SCREE_TYPE:
FlushScree(logMsg);
break;
case FILE_TYPE:
FlushFILE(logMsg);
break;
}
}
private:
int _type;
std::string _FILEName;
};
Log lg;
#define LOG(LEVEL, format, ...) \
do \
{ \
lg.Print(LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__); \
} while (0)
#define EnableScree() \
do \
{ \
lg.Enable(SCREE_TYPE); \
} while (0)
#define EnableFILE() \
do \
{ \
lg.Enable(FILE_TYPE); \
} while (0)
}
整体来说并不是很难,但是这里的一些知识点对于有些同学过于偏僻,导致了整个处理过程有点无措。
日志的使用方法:
我们定义了宏,就避免了一些繁琐的步骤,比如创建一个对象再去调用。
另外,我们的宏也提供了改变输出文件的,使用也更方便。
#include "Log.hpp"
using namespace log_ns;
int main()
{
EnableScree();
LOG(DEBUG, "hello world%d\n", 666);
EnableFILE();
LOG(DEBUG, "hello world%d\n", 666);
LOG(DEBUG, "hello world%d\n", 666);
LOG(DEBUG, "hello world%d\n", 666);
return 0;
}
当然,创建对象去调用Print也是可以的。
完~