【Linux】模拟实现一个简单的日志系统

news/2024/7/5 4:25:52

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、日志的概念
  • 二、储备知识之C式风格的可变参数
  • 三、获取时间
  • 四、实现打印日志函数
  • 五、封装成类并实现将日志信息打印到文件里(完整代码)

一、日志的概念

在编程中,日志是指程序在运行时生成的记录信息和生成对应记录的时间。这些记录信息可以包括程序的状态、错误消息、警告、调试信息等。通过日志,程序员可以更轻松地跟踪程序的执行过程、诊断问题并监视系统的运行情况。

常见的日志等级包括:

  1. info:常规信息

  2. warning:指示可能会引起问题的情况,但程序仍然可以继续执行。

  3. error:指示程序发生了错误,可能需要立即处理,但程序仍然能够继续执行。

  4. fatal:指示程序出现了致命问题,可能导致程序无法继续执行。

  5. debug:提供关于程序详细执行过程的信息,通常用于调试目的。

二、储备知识之C式风格的可变参数

在C语言中,可变参数函数是一种允许函数接受不定数量参数的机制。比方说printf就可以接受不定数量参数。

#include <stdio.h>
int printf(const char *format, ...);

实现这种功能需要使用stdarg.h头文件提供的一些宏。这些宏包括:

  • va_list:可以理解为一个用于存储所以可变参数的容器。
  • va_start:是一个宏函数,它的作用是初始化一个va_list对象,使其指向可变参数列表的第一个参数。以下是它的原型
void va_start(va_list ap, last_arg);

其中

  • 第一个参数是类型为va_list的对象
  • 第二个参数是一个固定参数,即可变参数列表之前的那个参数。因此,可变参数之前必须要有至少一个具体的参数。
  • va_arg:是一个宏函数,访问可变参数列表中的下一个参数,它的具体实现会有指针的自增操作。
type va_arg(va_list ap, type);
  • 第一个参数是类型为va_list的对象。
  • 第二个参数是你希望从可变参数列表中获取的参数类型。
  • va_end:清理va_list对象。

比方说定义一个可变参数函数,计算所有参数的和,用于演示如何编写和使用可变参数函数:

请添加图片描述

【程序结果】

请添加图片描述

三、获取时间

日志中包含时间是非常重要的,因为它可以帮助程序员准确地定位和跟踪问题。获取时间的方法有很多种,如time函数、clock 函数、gettimeofday 函数、strftime 函数等。

这里我以localtime函数为例,以上函数的具体用法大家可以自行搜索。

#include <time.h>
struct tm *localtime(const time_t *timep);

localtime函数可以将time_t类型的时间戳转换为struct tm类型,而struct tm类型有如下成员变量

请添加图片描述

需要注意的是:在C语言的struct tm结构体中,年份(tm_year)的起始值为1900,月份(tm_mon)的起始值为0。这意味着,如果你想要获取实际的年份和月份,需要对tm_yeartm_mon进行一些调整。

  • tm_year表示从1900年开始经过的年数。因此,要获取实际的年份,需要将其加上1900,即tm_year + 1900

  • tm_mon表示月份,范围从011,其中0表示一月,1表示二月,以此类推。因此,要获取实际的月份,需要将其加上1,即tm_mon + 1

以下是代码示例:

请添加图片描述

【程序结果】

请添加图片描述

四、实现打印日志函数

有了以上的知识,我们就可以开始实现打印日志函数了。

首先规定日志的格式:[时间] [等级] [用户自定义内容]

代码如下(含详细注释)

请添加图片描述

【函数解析】

  1. snprintf函数:用于将格式化的数据写入字符数组中。它的声明通常如下:
int snprintf(char *str, size_t size, const char *format, ...);
  • str: 指向存储输出的字符数组。
  • size: 字符数组的大小。
  • format: 传递给格式化字符串的数据格式,与printf类似。
  • ...: 可变数量的参数,这些参数根据格式字符串进行格式化。
  1. vsnprintf函数:与snprintf类似,但它使用va_list类型的参数列表。这对于在函数内部处理可变参数特别有用。其声明通常如下:
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
  • str: 指向存储输出的字符数组。
  • size: 字符数组的大小
  • format: 传递给格式化字符串的数据格式,与printf类似。
  • ap: va_list类型的参数列表,由va_startva_argva_end宏管理。

【复制即可用】

#pragma once
#include <iostream>
#include <string>
#include <stdarg.h>

// 将日志等级用整数表示
#define Info 0    // 常规
#define Debug 1   // 调试
#define Warning 2 // 警告
#define Error 3   // 错误
#define Fatal 4   // 致命

// 因为我们这里的日志等级是用一个整数表示的
// 而最后日志打印时需要有具体是什么日志等级
// 因此我们可以封装一个函数将日志等级转化为字符串
std::string levelToString(int level)
{
    switch (level)
    {
    case Info:
        return "Info";
    case Debug:
        return "Debug";
    case Warning:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    default:
        return "None";
    }
}

// level - 日志等级
// format - 格式化字符串的数据格式。类似于printf前半部分
// ... - 表示可变参数
void logmessage(int level, const char *format, ...)
{
    // ====== 默认部分:日志等级 + 时间 =========
    time_t _timestamp = time(NULL); // time函数会返回时间戳
    // 再将time_t类型转化为struct tm类型
    struct tm *_tm = localtime(&_timestamp);

    char defaultPart[1024]; // 默认部分
                            // 打印的日志格式:[日志等级][时间]
    snprintf(defaultPart, sizeof(defaultPart), "[%s][%d-%d-%d:%d:%d:%d]",
             levelToString(level).c_str(), _tm->tm_year + 1900, _tm->tm_mon + 1, _tm->tm_mday,
             _tm->tm_hour, _tm->tm_min, _tm->tm_sec);

    // ====== 自定义部分:format内容 + 可变参数... =========
    char self[1024];
    va_list s;
    va_start(s, format);
    vsnprintf(self, sizeof(self), format, s);
    va_end(s);

    // ===== 将默认部分和自定义部分整合 =====
    char logtxt[2048];
    snprintf(logtxt, sizeof(logtxt), "%s %s\n", defaultPart, self);

    // ==== 信息全部在logtxt中,你可以打印出来,或者写到一个文件里 ======
    printf("%s", logtxt);
}

五、封装成类并实现将日志信息打印到文件里(完整代码)

#pragma once
#include <iostream>
#include <string>
#include <stdarg.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

// 将日志等级用整数表示
#define Info 0    // 常规
#define Debug 1   // 调试
#define Warning 2 // 警告
#define Error 3   // 错误
#define Fatal 4   // 致命

#define Screen 1
#define OneFile 2
#define ClassFile 3

class log
{
public:
    // 写一个默认构造函数, 默认打印是向屏幕打印
    log()
    {
        printMethod = Screen;
        logdir = "./logdir/"; // 你需要保证当前路径下有目录名为logdir
    }
    // 让用户选择打印方式
    void Enable(int method)
    {
        printMethod = method;
    }

    // 因为我们这里的日志等级是用一个整数表示的
    // 而最后日志打印时需要有具体是什么日志等级
    // 因此我们可以封装一个函数将日志等级转化为字符串
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    // level - 日志等级
    // format - 格式化字符串的数据格式。类似于printf前半部分
    // ... - 表示可变参数
    void logmessage(int level, const char *format, ...)
    {
        // ====== 默认部分:日志等级 + 时间 =========
        time_t _timestamp = time(NULL); // time函数会返回时间戳
        // 再将time_t类型转化为struct tm类型
        struct tm *_tm = localtime(&_timestamp);

        char defaultPart[1024]; // 默认部分
                                // 打印的日志格式:[日志等级][时间]
        snprintf(defaultPart, sizeof(defaultPart), "[%s][%d-%d-%d:%d:%d:%d]",
                 levelToString(level).c_str(), _tm->tm_year + 1900, _tm->tm_mon + 1, _tm->tm_mday,
                 _tm->tm_hour, _tm->tm_min, _tm->tm_sec);

        // ====== 自定义部分:format内容 + 可变参数... =========
        char self[1024];
        va_list s;
        va_start(s, format);
        vsnprintf(self, sizeof(self), format, s);
        va_end(s);

        // ===== 将默认部分和自定义部分整合 =====
        char logtxt[2048];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", defaultPart, self);

        // ==== 信息全部在logtxt中,你可以打印出来,或者写到一个文件里 ======
        // printf("%s", logtxt); // 直接打印

        printLog(level, logtxt);
    }

    // 封装打印日志文件的方法:1. 向屏幕打印 2. 向文件打印 3. 分类打印
    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case OneFile:
            printOneFile("log.txt", logtxt);
            break;
        case ClassFile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }

    // 向一个文件写
    void printOneFile(const std::string filename, const std::string &logtxt)
    {
        std::string _filename = logdir + filename; // ./logdir/log.txt

        int fd = open(_filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        if (fd < 0)
        {
            return;
        }
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }

    // 文件分类写。比如Info信息放在一个文件中,Errno放在一个文件中...
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = "log.txt";
        filename += '.';
        filename += levelToString(level);

        printOneFile(filename, logtxt);
    }

private:
    int printMethod;
    std::string logdir; // 日志文件存放目录
};

http://lihuaxi.xjx100.cn/news/2215761.html

相关文章

【自动驾驶】浅学一下BEV目标检测记录

现在BEV与OCC占用网络非常火&#xff0c;在日常工作中&#xff0c;如果没有接触到&#xff0c;可能会忽略相应的知识储备。本人还未看大量文献&#xff0c;所以只能算浅学下记录&#xff0c;这里主要从互联网上学习到的&#xff0c;还是就是跟专业的同事请教之后&#xff0c;自…

嵌入式微处理器重点学习(三)

堆栈操作 R1=0x005 R3=0x004 SP=0x80014 STMFD sp!, {r1, r3} 指令STMFD sp!, {r1, r3}是一条ARM架构中的存储多个寄存器到内存的指令,这里用于将r1和r3寄存器的内容存储到栈上。STMFD(Store Multiple Full Descending)是一种全递减模式的多寄存器存储指令,它会先将栈指针…

Python学习笔记13 -- API的说明及使用

一、API说明 1、API&#xff1a;Application Programming Interface -- 接口 2、Restful API&#xff1a;基于网页的API 3、JSON&#xff1a;JavaScript objection Notation 4、很多数据都可以找到公用API 二、安装requests模块 在终端输入&#xff1a; 可以保证使用当前…

Oracle数据库之使用DDL语句管理表、约束(二十)

在Oracle数据库中&#xff0c;DDL&#xff08;Data Definition Language&#xff09;语句用于定义和管理数据库结构&#xff0c;如表、约束、索引等。以下是如何使用DDL语句来管理表和约束的一些示例。 1. 创建表 使用CREATE TABLE语句来创建表。 CREATE TABLE employees ( …

「实战应用」如何用图表控件LightningChart JS创建SQL仪表板应用(二)

LightningChart JS是Web上性能特高的图表库&#xff0c;具有出色的执行性能 - 使用高数据速率同时监控数十个数据源。 GPU加速和WebGL渲染确保您的设备的图形处理器得到有效利用&#xff0c;从而实现高刷新率和流畅的动画&#xff0c;常用于贸易&#xff0c;工程&#xff0c;航…

UltraISO制作U盘系统盘安装openEuler22.03和搭建cuda环境

1.下载openEuler镜像 https://repo.openeuler.org/openEuler-22.03-LTS/ISO/x86_64/ 选择下载&#xff1a;openEuler-22.03-LTS-x86_64-dvd.iso 2.用软碟通将 U 盘制作为启动盘 点击左上方 文件 –> 打开 然后找到下载的 ISO 镜像后&#xff0c;打开 打开后如下 找到菜单…

2024年【广东省安全员A证第四批(主要负责人)】复审考试及广东省安全员A证第四批(主要负责人)复审模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 广东省安全员A证第四批&#xff08;主要负责人&#xff09;复审考试参考答案及广东省安全员A证第四批&#xff08;主要负责人&#xff09;考试试题解析是安全生产模拟考试一点通题库老师及广东省安全员A证第四批&…

[AIGC] Python的Range函数

Python的range()函数是一个内置函数&#xff0c;常常用于编程中生成数列。这个函数可以生成一个整数序列&#xff0c;这个序列通常用在循环中。 文章目录 基本用法详细用法注意事项 基本用法 range()函数的基本形式为 range(stop) —— 这将生成一个从0开始&#xff0c;到stop…