【Linux】Linux下的日志(日常级)
日志是日后工作中非常重要的一部分,现在写一份简单的日志项目可以帮助我们熟悉并理解原理。
目录
- 设计思路:
- 一些实现细节:
- 代码:
- 日志的使用方法:
设计思路:
图示是我们的最终目的。
- 设计一个类,这个类中存放着以上数据。
- 设计一个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 2const 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也是可以的。
完~