目录
一、引言
二、日志系统基础
日志级别
日志输出格式
三、创建日志所需函数
认识可变参数
编辑
获取时间的函数
小结
四、创建日志
一、引言
在 Linux 环境中开发 C/C++ 程序时,日志系统是不可或缺的一部分。它不仅有助于调试程序、排查问题,还能记录程序运行时的关键信息,方便后续分析与维护。本文将全面介绍如何在 Linux 下构建高效实用的 C/C++ 日志系统,涵盖从基础概念到实际应用的各个方面。
二、日志系统基础
日志级别
常见日志级别介绍:
DEBUG:调试级别
INFO:信息级别
WARN:警告级别
ERROR:错误级别
FATAL:严重错误级别
日志输出格式
常用格式元素:
时间戳:记录日志发生的时间,精确到秒或毫秒,用于确定事件发生的先后顺序。时间戳的格式通常为 “YYYY - MM - DD HH:MM:SS.SSS”,其中 “YYYY” 表示年份,“MM” 表示月份,“DD” 表示日期,“HH” 表示小时,“MM” 表示分钟,“SS” 表示秒,“SSS” 表示毫秒。
日志级别:明确日志的级别,如 DEBUG、INFO、WARN、ERROR、FATAL 等,便于快速筛选和分析不同重要程度的日志信息。
文件名和行号:记录日志所在的源文件名称和行号,方便定位日志对应的代码位置,在调试和问题排查时非常有用。
线程 ID:在多线程程序中,线程 ID 可以帮助区分不同线程产生的日志,有助于分析多线程环境下的并发问题。
日志内容:具体的日志信息,是开发者或运维人员关注的核心内容,应尽可能清晰、准确地描述事件的发生情况。
自定义格式示例:
例如,定义一种日志格式为 “[时间戳] [日志级别] [线程 ID] [文件名:行号] - 日志内容”。在实际应用中,一条符合该格式的日志记录可能如下所示:
[2025 - 04 - 01 15:30:25.123] [INFO] [12345] [main.cpp:56] - 系统初始化完成,开始接受请求。
这种格式清晰地展示了日志发生的时间、级别、所属线程、代码位置以及具体内容,方便阅读和分析。通过自定义日志格式,可以根据项目的具体需求和团队的习惯,灵活地组织和呈现日志信息,提高日志的可读性和实用性。
三、创建日志所需函数
认识可变参数
像C语言中的printf函数也使用了可变参数,可以传入多个参数
printf函数原型如下:
int printf(const char *format, ...);
使用可变参数我们还需要借助宏
以下全部都是宏函数:
#include <stdarg.h>
void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);
例如:以下代码的函数的作用是多个未知元素求和,由调用者任意输入数据
int sum(int n, ...);
我们知道创建栈帧时参数表中的参数是从左向右开始进行压栈的
va_list:创建一个指向栈帧起始位置的指针变量
void va_start(va_list ap, last);
- 第一个参数是类型为
va_list
的对象 - 第二个参数是一个固定参数,即可变参数列表之前的那个参数。因此,可变参数之前必须要有至少一个具体的参数(因为需要使用这个参数找到可变参数的起始位置)。
功能:初始化s指针,将s指针移动至可变参数第一个元素的起始地址处
type va_arg(va_list ap, type);
- 第一个参数是类型为
va_list
的对象。 - 第二个参数是你希望从可变参数列表中获取的参数类型。
功能:从当前地址开始,通过计算拿到下一个参数的地址,然后在根据type类型,对该地址解引用拿到该参数的值并返回,此时会更新ap的位置,以便继续遍历后续的列表。
void va_end(va_list ap);
功能:将指针变量s置空
va_end(s); //将指针s置为nullptr
以下代码设计:使用以上宏函数,求出1,2,3数值之和
#include <iostream>
#include <string>
#include <cstdarg>
using namespace std;
int sum(int n, ...)
{
va_list s; //定义一个s变量
va_start(s, n); //初始化
int total = 0;
for(int i = 0; i < n; ++i)
{
total += va_arg(s, int); //从初始化的地方开始依次获取可变参数元素
}
va_end(s); //将s置空
return total;
}
int main()
{
printf("total: %d\n", sum(3, 1, 2, 3));
return 0;
}
获取时间的函数
time函数
time函数原型如下:
#include <time.h>
time_t time(time_t *tloc);
功能:获取当前时间戳
#include <iostream>
#include <string>
#include <cstdarg>
using namespace std;
int main()
{
time_t t = time(nullptr);
cout << t << endl;
return 0;
}
gettimeofday函数
gettimeofday函数原型如下:
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
struct timeval结构体如下:
struct timeval {
time_t tv_sec; //秒
suseconds_t tv_usec; //微秒
};
struct timeone第二个结构体是时区,不用管设置为nullptr即可
localtime函数
localtime原型如下:
#include <time.h>
struct tm *localtime(const time_t *timep);
功能:将时间戳转化为 年 月 日 时 分 秒
struct tm结构体如下:
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};
注意:
tm_year:表示从1900年开始经过的年数。因此,要获取实际的年份,需要将其加上1900,即tm_year + 1900。
tm_mon:表示月份,范围从0到11,其中0表示一月,1表示二月,以此类推。因此,要获取实际的月份,需要将其加上1,即tm_mon + 1。
小结
按照va_list(创建变量)、va_start(初始化变量)、va_arg(遍历列表)、va_end(清理工作)这四个步骤,就可以拿到可变列表中的每一个值了。
四、创建日志
#include <iostream>
#include <string>
#include <cstdarg>
using namespace std;
#define SIZE 1024
enum class LogLevel
{
INFO,
DEBUG,
WARNING,
ERROR,
FATAL
};
string LevelToString(LogLevel level) //需要传入枚举类型,我们使用的是枚举类进行进行判断
{
switch(level)
{
case LogLevel::INFO :
return "INFO";
case LogLevel::DEBUG :
return "DEBUG";
case LogLevel::WARNING :
return "WARNING";
case LogLevel::ERROR :
return "ERROR";
case LogLevel::FATAL :
return "FATAL";
}
}
void PrintLog(LogLevel Log_Level, const char *format, ...)
{
//左半部分 日志等级 + 日志时间
time_t t = time(nullptr);
struct tm* pt = localtime(&t);
char leftbuffer[SIZE];
snprintf(leftbuffer, sizeof(leftbuffer) - 1, "[%s][%d-%d-%d %d:%d:%d]: ",
LevelToString(Log_Level).c_str(),
pt->tm_year, pt->tm_mon, pt->tm_mday,
pt->tm_hour, pt->tm_min, pt->tm_sec);
//右半部分 自定义部分
va_list s;
va_start(s, format);
char rightbuffer[SIZE];
vsnprintf(rightbuffer, sizeof(rightbuffer) - 1, "%s", s);
//进行打印
fprintf(stdout, "%s%s", leftbuffer, rightbuffer);
}
int main()
{
PrintLog(LogLevel::INFO, "%s", "HelloWorld\n");
return 0;
}
你可以根据自己的需求增加日志打印内容,这里是一个简易的日志系统