个人主页:Lei宝啊
愿所有美好如期而遇
目录
日志是什么?
为什么需要日志?
实现一个简单日志
时间戳
clock_gettime
time & localtime
可变模板参数(使用C语言),va_start & va_end & vsprintf
宏 __LINE__ & __FILE__ & ##__VA_ARGS__
小技巧
代码实现
日志是什么?
日志(Log) 是记录系统、应用或设备在运行过程中产生的各种事件、状态、错误等信息的文件或数据流。这些日志信息对于了解系统的运行状况、排查问题、优化性能以及安全审计等方面都非常重要。
为什么需要日志?
- 问题排查:当系统或应用出现故障时,通过查看日志可以快速定位问题发生的原因和位置,从而加快故障解决的速度。
- 性能监控:通过分析日志,可以了解系统的运行状况,包括请求量、响应时间、资源使用情况等,从而发现性能瓶颈并进行优化。
- 安全审计:日志中记录了用户的操作行为、系统的访问情况等信息,这些信息可以用于安全审计,检查是否有异常操作或入侵行为。
- 合规性要求:在某些行业或地区,法律法规要求企业或组织必须保留一定期限的日志记录,以便在需要时进行审计或调查。
- 系统恢复:在某些情况下,如系统崩溃或数据丢失时,可以通过日志中的信息来恢复系统或数据。
- 版本追踪:对于软件来说,日志可以记录软件的版本变化、功能更新等信息,方便开发人员进行版本追踪和回滚。
- 用户行为分析:通过分析用户的操作日志,可以了解用户的使用习惯、喜好等信息,从而优化产品设计和提高用户体验
实现一个简单日志
实现日志前,我们先介绍几个函数以及几个小技巧:
时间戳
时间戳(Timestamp) 是一个表示特定时间点的数字或字符串。它通常表示从某个固定时间点(1970年1月1日00:00:00 UTC,也被称为Unix纪元或Unix时间戳)到特定事件发生时经过的秒数。
我们在Linux系统中有这样几个函数:
clock_gettime
第一个参数我们填CLOCK_REALTIME即可,表示系统实时时间。
#include <stdio.h>
#include <time.h> int main()
{ struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { printf("Current time: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); } else { perror("clock_gettime"); return 1; } return 0;
}
time & localtime
time函数获取当前时间的时间戳。
locltime函数将时间戳转换成我们平时使用的年月日时分秒。
可变模板参数(使用C语言),va_start & va_end & vsprintf
void message(const char* format, ...){} //可变模板参数
我们如何获取可变模板参数中的参数呢?
使用vsprintf函数:
这个函数用来将可变模板参数以format格式,将其写入到str指向的char类型数组中。
而可变模板参数,将会使用va_list类型变量指向,实际上他是一个void*类型指针,我们定义后,还需要使用函数初始化以及销毁。
va_list args;
va_start(&args, format);
//...
va_end(&args);
va_start,初始化va_list类型变量,last就是可变模板参数左边第一个参数,这样这个函数就可以将args初始化为指向可变模板参数的指针。
va_end,将va_list类型变量清空。
char buffer[1024];
vsprintf(buffer, format, args);
//这样就将可变模板参数中的内容以我们希望的格式获取到
//类似于printf("%d %s", xx,xx); "%d %s"就是格式,后面就是可变模板参数
宏 __LINE__ & __FILE__ & ##__VA_ARGS__
- __LINE__ 当前代码所在行数
- __FILE__ 当前代码所在文件
- __VA_ARGS__ C99及C++支持的宏可变模板参数
//##__VA_ARGS__ 中的 ## 操作符。当 __VA_ARGS__ 为空时(即没有额外的参数),
## 操作符会将其前面的逗号去掉,以防止在编译时产生语法错误。
简单来说,就是没有可变模板参数,只有格式,那么格式后面还有个逗号,##操作符会将其去掉。
小技巧
在宏替换中,关于语句的替换,以及多语句的替换,我们可以将其写在do{}while(0)中。
如果上述讲解没有理解,那么可以在下面的代码中进行理解,所有讲解都会在下面代码中有体现。
代码实现
#include <iostream>
#include <string>
#include <ctime>
#include <cstdarg>
#include <pthread.h>
#include <fstream>
using namespace std;//##__VA_ARGS__ 中的 ## 操作符。当 __VA_ARGS__ 为空时(即没有额外的参数),## 操作符会将其前面的逗号去掉,以防止在编译时产生语法错误。
#define Log(level, format, ...) do{LogMessage(__FILE__, __LINE__, level, format, ##__VA_ARGS__);}while(0)
#define YSave do{IsSave = true;}while(0)
#define NSave do{IsSave = false;}while(0)pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
string file_name_path = "log.txt";
bool IsSave = false; enum Level
{Debug = 0,Info,Warning,Error,Fatal
};string LevelToString(int level)
{string name;switch (level){case Debug : return "Debug";case Info : return "Info";case Warning : return "Warning";case Error : return "Error";case Fatal : return "Fatal";default: return "阿西吧";}
}string TimeToString()
{time_t Time; time(&Time);struct tm* attr = localtime(&Time);char buffer[1024];sprintf(buffer,"%4d-%2d-%2d %2d:%2d:%2d",attr->tm_year+1900,attr->tm_mon+1,attr->tm_mday,attr->tm_hour,attr->tm_min,attr->tm_sec);return buffer;
}void Save(string ret)
{fstream out(file_name_path.c_str(), ios::app); if(out.is_open()){out << ret;}else{std::cerr << "无法打开文件: " << file_name_path << std::endl;}out.close();
}// 2. 日志是有格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容
void LogMessage(string filename, int line, int level, const char* format, ...)
{string levelname = LevelToString(level);string timename = TimeToString();va_list args; //一个void*指针va_start(args, format); //将args初始化,指向可变参数列表char buffer[1024]; vsprintf(buffer, format, args);va_end(args);string ret = "[" + filename + "]" + "[" + "line: " + to_string(line)+ "]" + "[" + levelname + "]" + "[" + timename + "]" + "[" + buffer + "]" + "\n";pthread_mutex_lock(&log_mutex);if(IsSave){Save(ret);}else{cout << ret;}pthread_mutex_unlock(&log_mutex);
}