Linux_实现简易日志系统

news2024/11/28 4:53:28

       

目录

1、认识可变参数

2、解析可变参数

3、打印可变参数

3.1 va_list

3.2 va_start

3.3 va_arg

3.4 va_end 

3.5 小结 

4、实现日志 

4.1 日志左半部分 

4.2 日志右半部分 

4.3 日志的存档归类 

结语 


前言:

        在Linux下实现一个日志系统,该日志系统主要用于打印和记录程序的格式化信息,还可以对信息做分析处理,比如把信息分为五种类型:正常运行信息、测试信息,警告信息,错误信息、致命信息,当然还可以显示信息产生的具体时间,并且能够对这些信息进行存档归类。

1、认识可变参数

        因为日志系统记录的是程序往显示器上打印的信息,所以肯定离不开printf系列函数,可以得知他的底层肯定是调用了printf系列函数来实现的,而这个过程会涉及到可变参数的传递,所以实现日志系统前要认识可变参数。

        可变参数的形式如下:

int printf(const char *format, ...);
//第二个参数是三个点,...表示可变参数,即可以传多个实参给到printf

        所以可变参数表示可以接收任意数量的参数。

2、解析可变参数

        当我们拿到了一个可变参数,如何对其进行分析拿到具体的每一个值呢?要解析可变参数必须从函数栈帧的角度来理解,示意图如下: 

        从上图可得,只要拿到了format处的地址,后续就可以通过下一步的操作拿到format+1处的内容了,然后让指针继续“向上走”就可以遍历整个可变参数了。以上的逻辑也规定了一点:若想使用可变参数则必须得有一个具体参数,这个规则体现在下面代码处。

3、打印可变参数

        有了上述的解析规则,则可以用代码打印可变参数的每个值,代码如下:

#include <iostream>
#include <stdarg.h>

using namespace std;

void print(int n,.../*6 8 9 2 3*/)
{
    va_list s;
    va_start(s,n);

    while (n--)
    {
        printf("%d\n",va_arg(s,int));
    }
    va_end(s);

}

int main()
{
    print(5,6,8,9,2,3);
    return 0;
}

         运行结果:

        从结果可以看到,打印的顺序和上述分析的逻辑一模一样。下面就来解释上述代码中出现的4个字段:va_list、va_start、va_arg、va_end。

3.1 va_list

        va_list可以看成是一个类型,用他定义的变量就像是创建一个char*的指针。

3.2 va_start

        va_start是一个宏函数,他的作用是对va_list定义的变量进行初始化,他的参数介绍如下:

void va_start(va_list ap, last);
// ap表示用va_list定义的变量
// last表示可变参数的前一个固定参数

        比如上述代码中可变参数的前一个参数是n,所以va_start的第二个参数是n,这一动作就是上述逻辑中让指针定位到format处(这也解释了为什么可变参数前面必须得有固定参数,因为要让指针定位)。

3.3 va_arg

        va_arg也是一个宏函数,他的功能是拿到下一个参数的值(注意是拿下一个,而不是拿当前位置的值),其参数介绍如下:

type va_arg(va_list ap, type)
//第一个参数是va_list定义的变量
//第二个参数表示ap指向下一个参数的类型
//返回下一个参数的值

         从当前地址开始,通过计算拿到下一个参数的地址,然后在根据type类型,对该地址解引用拿到该参数的值并返回,此时会更新ap的位置,以便继续遍历后续的列表。

3.4 va_end 

         va_end是对va_list创建的变量进行清理工作,他的参数介绍如下:

void va_end(va_list ap);
//ap表示清理的目标

3.5 小结 

        按照va_list(创建变量)、va_start(初始化变量)、va_arg(遍历列表)、va_end(清理工作)这四个步骤,就可以拿到可变列表中的每一个值了。

4、实现日志 

        日志信息实际上就是一串字符,日志的目的就是打印到屏幕上给用户观看(或者存档归类方便查看),只不过日志信息更加规范且完善,因为他还记录了信息的时间和类型,所以可以把日志信息分成两个部分:左半部分和右半部分,其中右半部分就是程序的输出信息,而左半部分是记录信息的类型和时间。

        信息类型:信息分为五种类型:info,debug、warning、error、fatal,让用户手动对信息进行分类。

        记录时间:可以用time函数得到一个时间戳,然后再把该时间戳传给localtime函数,localtime会返回一个结构体,里面包含了年月日时分秒等精确时间。

4.1 日志左半部分 

        日志左半部分由信息类型和时间构成,代码如下:

#include <time.h>
#include <iostream>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>

using namespace std;
#define SIZE 1024
//对信息类型进行define
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

class log
{
public:
    log()
    {}
    const char* option(int point)//手动选择信息类型
    {
        switch (point)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    void logprint(int point, const char *format, ...)
    {
        //日志左边字符串
        char leffbuff[SIZE];
        time_t t = time(nullptr);
        struct tm *pt = localtime(&t);

        snprintf(leffbuff, SIZE, "[%s][%d-%d-%d-%d-%d-%d]",option(point), 
        pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour, 
        pt->tm_min, pt->tm_sec);

        printf("%s\n",leffbuff);
    }
    ~log()
    {}

private:
};

int main()
{
    log l;

    l.logprint(Debug,nullptr);
    return 0;
}

        运行结果:

        上述中将功能都写进类log内,目的是更好的对日志系统进行封装。

4.2 日志右半部分 

        日志右半部分就是程序格式化的信息,程序的格式化信息一般由printf系列的函数完成,所以要想在logprint内完成printf操作,则必须传递可变参数,因此可以用一个功能极其强大的接口vsnprintf函数,该函数介绍如下:

int vsnprintf(char *str, size_t size, const char *format, va_list ap);
//str表示数组首元素地址,将printf的结果写进str数组中

//size表示要写入数组内容的大小,并且在末尾处自动添加\0,即最多写size-1个字符,
//他会自动留个位置给到\0

//format表示格式化的字符串

//ap表示va_list创建的变量

        所以可以用va_list定义的变量指向可变参数列表,然后把该变量传给vsnprintf,这样就等同于将可变参数传给vsnprintf,则vsnprintf可以将可变参数的值打印出来,只不过不是打印到屏幕上而是写进str内,而这个str就是日志右半部分的字符串,最后在合并左右部分就得到最终的日志信息了。

        代码如下:

#include <time.h>
#include <iostream>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>

using namespace std;
#define SIZE 1024
//对信息类型进行define
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

class log
{
public:
    log()
    {}
    const char* option(int point)//手动选择信息类型
    {
        switch (point)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    void logprint(int point, const char *format, ...)
    {
        //日志左边字符串
        char leffbuff[SIZE];
        time_t t = time(nullptr);
        struct tm *pt = localtime(&t);

        snprintf(leffbuff, SIZE, "[%s][%d-%d-%d-%d-%d-%d]",option(point), 
        pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour, 
        pt->tm_min, pt->tm_sec);

        //日志右边字符串
        char rightbuff[SIZE];
        va_list s;
        va_start(s,format);
        vsnprintf(rightbuff,SIZE,format,s);

        //合并成为最终日志信息
        char logmessege[SIZE*2];
        snprintf(logmessege,SIZE*2,"%s%s\n",leffbuff,rightbuff);
        printf("%s",logmessege);
    }
    ~log()
    {}

private:
};

int main()
{
    log l;
    int a = 10;
    int b = 12;

    l.logprint(Debug,"a=%d,b=%d",a,b);
    return 0;
}

         运行结果:

4.3 日志的存档归类 

        上面实现的日志只能打印在屏幕上,而日志的主要功能是可以存档归类,方便后续查看,所以上面代码在打印日志这一步时可以做一个判断:1、打印到屏幕,2、打开文件将日志信息写入文件中,3、写入文件时根据文件类型进行文件归类,因此可以在log类中加上两个成员变量,一个是指定日志存放的路径,一个是输出方式,具体成员如下:

private:
    string path;//即路径目录
    int printmethod;//输出方式:1、显示器 2、文件 3、文件归类

         完整日志代码如下:

#include <time.h>
#include <iostream>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>

using namespace std;
#define SIZE 1024
//对信息类型进行define
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
//三种方式
#define Screen 1
#define Onefile 2
#define Classfile 3 

#define logfile "log.txt"

class log
{
public:
    log()//初始化成员变量
    {
        path = "./log/";
        printmethod = 2;//选取方式2
    }
    const char* option(int point)//手动选择信息类型
    {
        switch (point)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    //打印日志信息的三种方式具体实现
    void printscreen(const char* logmessege)
    {
        printf("%s",logmessege);              
    }
    void printonefile(const string& filename,const char* logmessege)
    {
        string logpath = path+filename;
        int fd = open(logpath.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        if (fd<0)
        {
            perror("open");
            exit(-1);
        }
        //写
        write(fd,logmessege,strlen(logmessege));
        close(fd);
    }
    void printclassfile(int point, const char* logmessege)
    {
        string logpath = logfile;
        (logpath+='.')+=option(point);

        printonefile(logpath,logmessege);
    }

    //选取打印日志的方式
    void printflow(int point, const char* logmessege)
    {
        switch (printmethod)
        {
        case Screen:
            printscreen(logmessege);//打印屏幕
            break;
        case Onefile:
            printonefile(logfile,logmessege);//写入文件
            break;
        case Classfile:
            //实际上就是改了文件名然后复用printonefile
            printclassfile(point,logmessege);//文件归类
            break;
        
        default:
            break;
        }
    }

    void logprint(int point, const char *format, ...)
    {
        //日志左边字符串
        char leffbuff[SIZE];
        time_t t = time(nullptr);
        struct tm *pt = localtime(&t);

        snprintf(leffbuff, SIZE, "[%s][%d-%d-%d-%d-%d-%d]",option(point), 
        pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour, 
        pt->tm_min, pt->tm_sec);

        //日志右边字符串
        char rightbuff[SIZE];
        va_list s;
        va_start(s,format);
        vsnprintf(rightbuff,SIZE,format,s);

        //合并成为最终日志信息
        char logmessege[SIZE*2];
        snprintf(logmessege,SIZE*2,"%s%s\n",leffbuff,rightbuff);
        //printf("%s\n",logmessege);
        printflow(point, logmessege);//调用具体输出方法

    }
    ~log()
    {
    }

private:
    string path;//即路径目录
    int printmethod;//输出方式:1、显示器 2、文件 3、文件归类
};

int main()
{
    log l;
    int a = 10;
    int b = 12;

    l.logprint(Debug,"a=%d,b=%d",a,b);
    return 0;
}

        运行结果:

        从结果可以看到,方式2是将格式化的内容写进文件里,但是在此之前要先在当前目录下创建一个log目录,因为成员变量path初始化的内容是"./log/",表示日志文件都存放在这个路径下。 

结语 

         以上就是关于Linux下实现日志系统的讲解,实现日志系统的核心点在于了解可变参数的使用,以及对字符串的操作运用。

        最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1903084.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Meta低头,库克认错,XR回归第一性原理

图片&#xff5c;Photo by Maxim Hopman on Unsplash ©自象限原创 作者丨罗辑 2024年&#xff0c;XR的故事应该怎么讲&#xff1f; 如果从数据上看&#xff0c;这应该是个沉重的话题。 根据 IDC 报告&#xff0c;2023 年全球 VR 市场出货量下滑了 10.7%。2024 年第一…

【Python】已解决:nltk.download(‘stopwords‘) 报错问题

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;nltk.download(‘stopwords’) 报错问题 一、分析问题背景 在使用Python的自然语言处理库NLTK&#xff08;Natural Language Toolkit&#xff09;时&#xff0c…

昇思MindSpore学习笔记4-05生成式--Pix2Pix实现图像转换

摘要&#xff1a; 记录昇思MindSpore AI框架使用Pix2Pix模型生成图像、判断图像真实概率的原理和实际使用方法、步骤。包括环境准备、下载数据集、数据加载和处理、创建cGAN神经网络生成器和判别器、模型训练、模型推理等。 一、概念 1.Pix2Pix模型 条件生成对抗网络&#x…

浅谈进程隐藏技术

前言 在之前几篇文章已经学习了解了几种钩取的方法 浅谈调试模式钩取浅谈热补丁浅谈内联钩取原理与实现导入地址表钩取技术 这篇文章就利用钩取方式完成进程隐藏的效果。 进程遍历方法 在实现进程隐藏时&#xff0c;首先需要明确遍历进程的方法。 CreateToolhelp32Snapsh…

ViewController 生命周期

ViewController 生命周期 ViewController 生命周期测试程序&#xff1a;ViewControllerLifeCircle ViewController 生命周期 ViewController 是 iOS 开发中 MVC 框架中的 C&#xff0c;ViewColllecter 是 View&#xff08;视图&#xff09;的 Collecter&#xff08;控制器&…

kafka中

Kafka RocketMQ概述 RabbitMQ概述 ActiveMQ概述 ZeroMQ概述 MQ对比选型 适用场景-从公司基础建设力量角度出发 适用场景-从业务场景出发 Kafka配置介绍 运行Kafka 安装ELAK 配置EFAK EFAK界面 KAFKA常用术语 Kafka常用指令 Kafka中消息读取 单播消息 group.id 相同 多播消息 g…

LabVIEW在图像处理中的应用

abVIEW作为一种图形化编程环境&#xff0c;不仅在数据采集和仪器控制领域表现出色&#xff0c;还在图像处理方面具有强大的功能。借助其Vision Development Module&#xff0c;LabVIEW提供了丰富的图像处理工具&#xff0c;广泛应用于工业检测、医学影像、自动化控制等多个领域…

LabVIEW在自动化测试项目中的推荐架构

在自动化测试项目中&#xff0c;推荐使用LabVIEW的生产者-消费者&#xff08;Producer-Consumer&#xff09;架构。这种架构利用队列实现数据的异步传输和处理&#xff0c;提供了高效、稳定和可扩展的解决方案。其主要优点包括&#xff1a;实现数据采集与处理的解耦、提高系统响…

SKF轴承故障频率查询

1&#xff0c;第一步&#xff1a;搜索轴承型号 skf官网 2&#xff0c;第二步&#xff1a;查询故障频率。 第三步&#xff1a;

《基于 defineProperty 实现前端运行时变量检测》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; 近期刚转战 CSDN&#xff0c;会严格把控文章质量&#xff0c;绝不滥竽充数&#xff0c;欢迎多多交流~ &am…

SpringBoot实现多数据源切换

1. 概述 仓库地址&#xff1a;https://gitee.com/aopmin/multi-datasource-demo 随着项目规模的扩大和业务需求的复杂化&#xff0c;单一数据源已经不能满足实际开发中的需求。在许多情况下&#xff0c;我们需要同时操作多个数据库&#xff0c;或者需要将不同类型的数据存储在不…

MyBatis-Plus-实用的功能自动填充字段

前言: java项目用到了mybatis-plus&#xff0c;在一些类里面需要在更新时候&#xff0c;统一设置&#xff0c;修改人&#xff0c;修改ID&#xff0c;修改时间。新增时候设置 创建人&#xff0c;创建时间等 基础类&#xff1a; Data public abstract class BaseModel implements…

昇思25天学习打卡营第18天 | K近邻算法实现红酒聚类

1、实验目的 了解KNN的基本概念&#xff1b;了解如何使用MindSpore进行KNN实验。 2、K近邻算法原理介绍 K近邻算法&#xff08;K-Nearest-Neighbor, KNN&#xff09;是一种用于分类和回归的非参数统计方法&#xff0c;最初由 Cover和Hart于1968年提出(Cover等人,1967)&#…

身体(body)的觉醒

佛&#xff0c;是一个梵文的汉语音译词&#xff0c;指觉醒者。 何谓觉醒&#xff1f;什么的觉醒&#xff1f;其实很简单&#xff0c;就是身体的觉醒。 佛的另一个名字&#xff0c;叫菩提&#xff0c;佛就是菩提&#xff0c;菩提老祖&#xff0c;就是佛祖。 body&#xff0c;即…

如何优化 PostgreSQL 中对于复杂数学计算的查询?

文章目录 一、理解复杂数学计算的特点二、优化原则&#xff08;一&#xff09;索引优化&#xff08;二&#xff09;查询重写&#xff08;三&#xff09;数据库配置调整&#xff08;四&#xff09;使用数据库内置函数的优势 三、具体的优化方案和示例&#xff08;一&#xff09;…

数据结构算法-排序(一)-冒泡排序

什么是冒泡排序 冒泡排序&#xff1a;在原数组中通过相邻两项元素的比较&#xff0c;交换而完成的排序算法。 算法核心 数组中相邻两项比较、交换。 算法复杂度 时间复杂度 实现一次排序找到最大值需要遍历 n-1次(n为数组长度) 需要这样的排序 n-1次。 需要 (n-1) * (n-1) —…

基于springboot+vue+uniapp的高校宿舍信息管理系统小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

几款电脑端能够运行的AI大模型聊天客户端

Ollama Ollama 是一个用于在本地运行和管理大型语言模型的工具。它支持多种流行模型的下载和本地运行&#xff0c;包括 LLaMA-2、CodeLLaMA、Falcon 和 Mistral 。Ollama 提供了一个简单、轻量级和可扩展的解决方案&#xff0c;使得用户可以以最简单快速的方式在本地运行大模型…

LabVIEW透视变换

透视变换概述源程序在www.bjcyck.com下载 透视变换是一种几何变换&#xff0c;用于对图像进行扭曲&#xff0c;使其看起来从不同角度拍摄。这在计算机视觉和图像处理领域非常重要&#xff0c;例如在投影校正和图像配准中。LabVIEW提供了强大的图像处理工具&#xff0c;利用其V…

阿里通义音频生成大模型 FunAudioLLM 开源!

01 导读 人类对自身的研究和模仿由来已久&#xff0c;在我国2000多年前的《列子汤问》里就描述了有能工巧匠制作出会说话会舞动的类人机器人的故事。声音包含丰富的个体特征及情感情绪信息&#xff0c;对话作为人类最常使用亲切自然的交互模式&#xff0c;是连接人与智能世界…