Linux 下 日志系统搭建全攻略

发布于:2025-04-06 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

一、引言

二、日志系统基础

日志级别

日志输出格式

三、创建日志所需函数

认识可变参数

​编辑

获取时间的函数

小结 

四、创建日志


一、引言

在 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;
}

你可以根据自己的需求增加日志打印内容,这里是一个简易的日志系统