Linux环境下的日志文件的实现

news2024/10/7 3:12:12

目录

日志 

相关函数

time函数 

localtime函数 

va_list类型

vsnprintf函数

宏支持可变参数 

__FILE__和__LINE__

完整代码

Log.hpp

标记黏合操作符##(重点)

LockGuard.hpp


日志 

基本概念:用于记录软件运行时的信息,可以向显示器和文件中进行打印,且有特定的格式

日志格式:[日志等级] [PID] [FileName] [FileNum] [Time]  日志内容(支持可变参数)

  • PID:产生日志的当前进程的PID
  • FileName:日志所存放的文件名称
  • FileNum:新增的日志信息在该文件的行数
  • Time:新增日志出现的时间

日志等级:DEBUG(因调式而产生的)INFO(常规输出,比如打印某文件成功)WARNING(有问题但不影响程序执行)ERROR(很严重但程序仍然勉强能运行)FATAL(致命的,程序无法运行)

相关函数

time函数 

包含头文件:<time.h> 

函数原型:time_t time(time_t *tloc)

  • tloc:一个指向 time_t 类型的指针。如果不为 NULL,函数还会将当前时间保存到该指针指向的内存中。如果为 NULL,则不进行存储操作

功能:获取当前的时间

返回值:当前的日历时间

#include <stdio.h>
#include <time.h>

int main() {
    time_t current_time;
    
    // 获取当前时间,并存储在 current_time 中
    time(&current_time);

    // 打印当前时间(以秒为单位)
    printf("Current time in seconds since 1970: %ld\n", current_time);

    // 使用 ctime 函数将时间转换为可读字符串
    printf("Current local time: %s", ctime(&current_time));

    return 0;
}

localtime函数 

包含头文件:<time.h> 

函数原型:struct tm *localtime(const time_t *timep);

功能:将给定的 time_t 时间转换为表示本地时间的 struct tm 结构体

返回值:成功时返回指向 struct tm 结构的指针,该结构包含了本地时间的详细信息,如年、月、日、小时、分钟、秒等,失败时返回NULL(比如timep无效)

struct tm {
    int tm_sec;    // 秒,范围从 0 到 60(包含闰秒)
    int tm_min;    // 分钟,范围从 0 到 59
    int tm_hour;   // 小时,范围从 0 到 23
    int tm_mday;   // 一月中的第几天,范围从 1 到 31
    int tm_mon;    // 月份,范围从 0 到 11(0 代表 1 月)
    int tm_year;   // 自 1900 年起的年份
    int tm_wday;   // 一周中的第几天,范围从 0 到 6(0 代表星期天)
    int tm_yday;   // 一年中的第几天,范围从 0 到 365
    int tm_isdst;  // 夏令时标志,正值代表启用了夏令时,0 代表未启用,负值表示未知
};

#include <stdio.h>
#include <time.h>

int main() {
    time_t current_time;
    struct tm *time_info;

    // 获取当前时间
    time(&current_time);

    // 将时间转换为本地时间
    time_info = localtime(&current_time);

    // 打印本地时间
    printf("当前本地时间: %s", asctime(time_info));

    return 0;
}

va_list类型

包含头文件:<stdarg.h>

基本概念:C 语言提供了一组宏和数据类型来帮助我们在函数中处理可变参数,常用的宏有 va_listva_startva_argva_end

  • va_list:用于声明一个保存可变参数信息的变量。
  • va_start:用于初始化 va_list 变量,以便后续的可变参数可以被访问
  • va_arg:用于获取下一个可变参数的值
  • va_end:结束对可变参数的访问,清理 va_list

注意事项:

  1. va_arg 必须知道每个参数的类型,因为它无法自动识别参数的类型
  2. 必须确保 va_startva_end 成对使用,否则可能会导致内存泄漏或其他未定义行为

使用步骤:

  1. 定义一个函数,形参列表中需要一个固定参数(通常称为“最后一个确定参数”),可变参数将出现在它之后。
  2. 声明一个 va_list 变量。
  3. 使用 va_start 初始化 va_list,并指定最后一个固定参数。
  4. 使用 va_arg 获取下一个可变参数。
  5. 使用 va_end 结束可变参数的处理
#include <stdio.h>
#include <stdarg.h>

// 可变参数函数,计算传入参数的总和
int sum(int count, ...) {
    va_list args;           // 声明 va_list 变量
    int total = 0;

    va_start(args, count);  // 初始化 va_list,传入最后一个固定参数

    // 通过循环提取每个参数
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);  // 获取下一个参数并累加
    }

    va_end(args);  // 清理 va_list

    return total;
}

int main() {
    int result = sum(4, 1, 2, 3, 4);  // 传入 4 个参数
    printf("总和: %d\n", result);  // 输出总和 10

    return 0;
}

注意事项:最后一个固定参数是第一个形参,因为函数参数压栈顺序是从右向左(所以上面va_start的第二个参数为count)

vsnprintf函数

包含头文件:<stdarg.h>

函数原型:int vsnprintf(char *str, size_t size, const char *format, va_list ap);

  • str:指向要存储生成字符串的缓冲区(字符数组)
  • size:缓冲区的大小(即最多写入 size-1 个字符,并自动在末尾添加空字符 \0
  • format:格式字符串,类似 printf() 函数的格式字符串,用于描述如何格式化输出
  • ap:类型为 va_list 的可变参数列表,它是由 va_start 宏初始化的

功能:格式化输出到字符串(省去了va_list类型例子中的循环提取的部分)

返回值:成功时返回生成的格式化字符串的长度(不包括结尾的 \0 字符),如果返回值大于或等于 size,则说明输出被截断。失败时返回负值

#include <stdio.h>
#include <stdarg.h>

void my_vsnprintf(char *buffer, size_t size, const char *format, ...) {
    va_list args;
    va_start(args, format);

    // 使用 vsnprintf 将格式化内容写入 buffer
    vsnprintf(buffer, size, format, args);

    va_end(args);
}

int main() {
    char buffer[100];

    // 调用自定义函数,格式化字符串并传入参数
    my_vsnprintf(buffer, sizeof(buffer), "Hello %s, your score is %d", "Alice", 95);

    // 打印生成的字符串
    printf("%s\n", buffer);  // 输出: Hello Alice, your score is 95

    return 0;
}

宏支持可变参数 

基本概念:在 C 语言中,通过使用__VA_ARGS__ 关键字,可以使得宏支持可变参数,该可变参数宏允许宏接收任意数量的参数,并且可以将这些参数传递给宏体中的代码

        //替换后                    替换前
#define MACRO_NAME(fixed_arg, ...)  macro_name(fixed_arg,__VA_ARGS__)
  • fixed_arg:固定参数,必须提供
  • __VA_ARGS__:可变参数,可以是任意数量的其他参数
//一个可以打印不同数量参数的宏
#include <stdio.h>

#define PRINTF(fmt, ...) printf(fmt, __VA_ARGS__)

//本例子中的fixed_arg是格式化符
int main() 
{
    PRINTF("Hello %s, your score is %d\n", "John", 90);
    PRINTF("The result is %f\n", 3.1415);
    return 0;
}

__FILE__和__LINE__

__FILE__:在代码的任何地方使用 __FILE__,都会返回该文件的完整路径或者文件名(编译时进行填充)

__LINE__:在代码的任何地方使用 __LINE__,都会返回当前代码在文件中的行号(编译时进行填充)

EnableScreen();//在屏幕上打印
LOG(DEBUG,"hello %d, world %c, hello: %f\n",1000,'A',3.14);//日志级别是DEBUG

//宏替换后的结果:
do { lg.LogMessage(__FILE__, __LINE__, DEBUG, "hello %d, world %c, hello: %f\n", 1000, 'A', 3.14); } while(0)


//lg.LogMessage(文件名, 行号, 日志级别, 在尝试调用日志信息时自家加的可变参数,由调用时决定);

完整代码

Log.hpp

#pragma once

#include <iostream>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include <pthread.h>

#include "LockGuard.hpp"

namespace log_ns
{
    enum
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    //将日志等级转换为字符串
    std::string LevelToString(int level)
    {
        switch(level)
        {
            case DEBUG:
                return "DEBUG";
            case INFO:
                return "INFO";
            case WARNING:
                return "WARNING";
            case ERROR:
                return "ERROR";
            case FATAL:
                return "FATAL";
            default:
                return "UNKNOWN";
        }
    }

    //获取当前时间(年、月、日)
    std::string GetCurrentTime()
    {
        time_t now = time(nullptr);
        struct tm *curr_time = localtime(&now);
        char buffer[128];
        snprintf(buffer,sizeof(buffer),"%d-%02d-%02d %02d:%02d:%02d",
            curr_time->tm_year + 1900,//系统提供的是当前时间减去1900年的时间,实际使用时需要加上1900
            curr_time->tm_mon + 1,//系统提供的日期是[0,11],所以也要加上1
            curr_time->tm_mday,
            curr_time->tm_hour,
            curr_time->tm_min,
            curr_time->tm_sec);
        return buffer;
    }

    class logmessage//日志信息类
    {
    public:
        std::string _level;//日志等级
        pid_t _id;//产生日志的当前进程的PID
        std::string _filename;//存放日志所存放的文件名称
        int _filenumber;//新增的日志信息在该文件的行数
        std::string _curr_time;//新增日志出现的时间
        std::string _message_info;//日志内容
    };

    #define SCREEN_TYPE 1//向显示器上打为1
    #define FILE_TYPE   2//向文件上打为2

    const std::string glogfile = "./log.txt";//打印日志到当前目录下的log.txt文件
    pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;//加锁保护日志的打印

    class Log
    {
    public:
        //向显示器或文件中打印的构造函数(提供了缺省值,保证永远都能打印到一个文件中,想要更改文件的路径就再定义一个就行)
        Log(const std::string &logfile = glogfile):_logfile(logfile),_type(SCREEN_TYPE)
        {

        }

        void Enable(int type)
        {
            _type = type;
        }

        
        void FlushLog(const logmessage &lg)
        {
            LockGuard lockguard(&glock);//加解锁
            switch(_type)
            {
                case SCREEN_TYPE:
                FlushLogToScreen(lg);
                    break;
                case FILE_TYPE:
                FlushLogToFile(lg);
                    break;
            }
        }

        void FlushLogToScreen(const logmessage &lg)
        {
            printf("[%s][%d][%s][%d][%s] %s\n",
                lg._level.c_str(),
                lg._id,
                lg._filename.c_str(),
                lg._filenumber,
                lg._curr_time.c_str(),
                lg._message_info.c_str()
            );
        }

        void FlushLogToFile(const logmessage &lg)
        {
            std::ofstream out(_logfile,std::ios::app);//向文件中打印时是以追加的方式进行的
            if(!out.is_open()) return;//打开文件失败则直接返回
            char logtxt[2048];
            
            snprintf(logtxt,sizeof(logtxt),"[%s][%d][%s][%d][%s] %s",
                lg._level.c_str(),//全部转换为c语言的字符串形式
                lg._id,
                lg._filename.c_str(),
                lg._filenumber,
                lg._curr_time.c_str(),
                lg._message_info.c_str()
            ); 
            
            out.write(logtxt,strlen(logtxt));
            out.close();   

        }

        //填写日志消息
        void LogMessage(std::string filename,int filenumber,int level,const char *format,...)
        {
            logmessage lg;
            
            lg._level = LevelToString(level);
            lg._filename = filename;
            lg._filenumber = filenumber;

            lg._id = getpid();
            lg._curr_time = GetCurrentTime();
            
            //提取可变参数
            va_list  ap;
            va_start(ap,format);
            char log_info[1024];
            vsnprintf(log_info,sizeof(log_info),format,ap);
            va_end(ap);

            lg._message_info = log_info;

            //打印日志(即打印logmessage类型的对象)
            FlushLog(lg);
        }

        ~Log(){};

    private:
        int _type;//确定要向哪里打印
        std::string _logfile;//要打印的文件的名称
    };

    Log lg;

    //__FILE__, __LINE__, level,这三个都是固定好的,在调用LOG的时候会直接将__FILE__和__LINE__进行填充,所以LOG宏的第一个参数是level
    #define LOG(level, Format, ...) do { lg.LogMessage(__FILE__, __LINE__, level, Format, ##__VA_ARGS__); } while(0)
    #define EnableScreen() do { lg.Enable(SCREEN_TYPE); } while(0)
    #define EnableFILE() do { lg.Enable(FILE_TYPE); } while(0)
}

注意事项:LOG宏函数定义中应该是##__VA_ARGS__ 而不是__VA_ARGS__,如果是前者如果在调用LOG宏函数时没有使用可变参数比如:

LOG(FATAL,"socket error\n");

 宏展开后就会多一个逗号,进而产生报错:

lg.LogMessage(__FILE__, __LINE__, FATAL, "socket error\n", );

标记黏合操作符##(重点)

基本概念:当在宏中使用可变参数(__VA_ARGS__)时,如果不传递可变参数,可能会出现多余的逗号。## 可以用来处理这种情况,避免生成错误的代码。例如:

#define LOG(level, Format, ...) printf(Format, ##__VA_ARGS__)

int main() {
    LOG("INFO", "Hello, World\n");   // 没有额外参数
    LOG("INFO", "Value: %d\n", 42);  // 带有一个额外参数
}

解释:在这个例子中,LOG("INFO", "Hello, World\n") 不带可变参数,但如果没有 ##__VA_ARGS__,宏展开后可能会生成一个多余的逗号,如 printf("Hello, World\n", );,这将导致语法错误。使用 ## 后,如果 __VA_ARGS__ 是空的,前面的逗号也会被移除,避免错误

LockGuard.hpp

#pragma once
#include <pthread.h>

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        pthread_mutex_lock(_mutex);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);
    }
private:
    pthread_mutex_t *_mutex;
};

~over~ 

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

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

相关文章

数据结构与算法——Java实现 30.合并多个有序链表 小顶堆实现

后来我们都走了很久&#xff0c;远到提及往事时&#xff0c; 总会加上once upon a time —— 24.10.6 23. 合并 K 个升序链表 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1…

linux桌面软件(wps)内嵌到主窗口后的关闭问题

程序测试环境是&#xff1a;slackware系统&#xff0c;属于linux系统&#xff0c;有桌面&#xff08;Xface Session&#xff09;。系统镜像是&#xff1a;slackware64-15.0-install-dvd.iso。qt、c代码实现。 问题描述&#xff1a;延续上一篇文章&#xff0c;将wps软件窗口内嵌…

中断系统的原理

一、介绍 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的。中断是指‌CPU在正常运行程序时&#xff0c;由于内部或外部事件的发生&#xff0c;导致CPU中断当前运行的程序&#xff0c;转而去执行其他程序的过程。‌ 中断可以是硬件产生的&#xff0c;也可以是…

神经网络激活函数列表大全及keras中的激活函数定义

一、概述 在机器学习中&#xff0c;激活函数是神经网络中的一种函数&#xff0c;用于在神经网络的每个神经元中引入非线性。没有激活函数&#xff0c;神经网络就无法学习复杂的模式&#xff0c;因为线性变换的组合仍然是线性的。 在神经网络的每层中&#xff0c;将该层所有输…

ElasticSearch备考 -- Multi match

一、题目 索引task有3个字段a、b、c&#xff0c;写一个查询去匹配这三个字段为mom&#xff0c;其中b的字段评分比a、c字段大一倍&#xff0c;将他们的分数相加作为最后的总分数 二、思考 通过题目要求对多个字段进行匹配查询&#xff0c;可以考虑multi match、bool query操作。…

计算机毕业设计 基于Python的人事管理系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

108页PPT丨OGSM战略规划框架:实现企业目标的系统化方法论

OGSM战略规划框架是一种实现企业目标的系统化方法论&#xff0c;它通过将组织的目标&#xff08;Objectives&#xff09;、目标&#xff08;Goals&#xff09;、策略&#xff08;Strategies&#xff09;和衡量指标&#xff08;Measures&#xff09;进行系统化整合&#xff0c;确…

Luminar财务造假风波:激光雷达龙头的困境与挑战

近日,美国激光雷达上市公司Luminar被爆出财务造假嫌疑,这一消息震惊了整个行业。Luminar,这家曾风光无限的激光雷达公司,最高市值一度达到120亿美元,其年轻的创始人也因此坐拥豪宅豪车无数。然而,如今在市值仅剩5亿美元左右的时候,却被爆出如此丑闻,令人不禁唏嘘。 带…

系统架构设计师-论文题(2021年下半年)

1.试题一 论面向方面的编程技术及其应用针对应用开发所面临的规模不断扩大、复杂度不断提升的问题&#xff0c;面向方面的编程Aspect Oriented Programming,AOP技术提供了一种有效的程序开发方法。为了理解和完成一个复杂的程序&#xff0c;通常要把程序进行功能划分和封装。一…

tcp/ip、以太网、mqtt、modbus/tcp复习

1.osi参考模型 2. modbus是应用层报文传输协议&#xff0c;没有规定物理层&#xff0c;只规定了协议帧&#xff0c;但是定义了控制器能够认识和使用的消息结构&#xff0c;不管它们是经过何种网络进行通信的&#xff0c;具有很强的适应性。 一主多从&#xff0c;同一时间主机…

Linux网络命令:如何查看linux系统防火墙开放的端口有哪些?多种方法来查看系统开放的网络端口号,包括TCP端口和UDP端口

目录 一、概述 二、查看防火墙开放的端口号的常用方法 &#xff08;一&#xff09;、使用firewalld&#xff08;适用于CentOS/RHEL 7&#xff09; 1. 查看firewalld状态 2. 查看所有开放的端口 3. 查看指定zone&#xff08;如public&#xff09;的开放端口 (二)、使用ipt…

基于SpringBoot+Vue+Uniapp的植物园管理小程序系统(2024最新,源码+文档+远程部署+讲解视频等)

3. 论文参考 4. 项目运行截图 5. 技术框架 5.1 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring …

眼儿媚​·秋雨绵绵窗暗暗

因患久治未愈的基础病 ——“肺气肿、哮喘、支扩吐血”等严重的呼吸道疾病&#xff0c;年近72岁的笔者于2017年初夏季节&#xff0c;情非得已地放弃半个多世纪习惯了的都市生活&#xff0c;来到成都市崇州市街子古镇寄居养病、养老&#xff0c;而今已逾七年。虽说一晃都是快要年…

【大模型 AI 学习】大模型 AI 部署硬件配置方案(本地硬件配置 | 在线GPU)

最近想部署一个开源深度学习项目&#xff0c;但是小编的笔记本电脑是8G的集成显存&#xff0c;且没有GPU&#xff0c;性能肯定是不够的。于是小编在小po站上粗浅了解了一下当前: 1. 大模型 AI本地硬件配置和 2. 云上申请GPU算力的两种方式。简单记录一下&#xff1a; 参考视频…

D29【python 接口自动化学习】- python基础之输入输出与文件操作

day29 格式化输出 学习日期&#xff1a;20241006 学习目标&#xff1a;输入输出与文件操作&#xfe63;-41 格式化输出&#xff1a;如何将执行结果通过屏幕输出&#xff1f; 学习笔记&#xff1a; 三种常用的格式化输出方式 百分号方式 format函数方式 总结 1. 格式化输出…

模拟实现消息队列(基于SpringBoot实现)

提要&#xff1a;此处的消息队列是仿照RabbitMQ实现&#xff08;参数之类的&#xff09;&#xff0c;实现一些基本的操作&#xff1a;创建/销毁交互机&#xff08;exchangeDeclare&#xff0c;exchangeDelete&#xff09;&#xff0c;队列&#xff08;queueDeclare&#xff0c;…

docker拉取镜像推送到阿里云镜像仓库

Docker拉取失败&#xff0c;利用github将镜像推送到阿里云 docker_image_pusher hub-mirror仓库 1、windows没有升级&#xff0c;用不了WSL。可以使用wsl&#xff0c;配合docker desktop直接拉取镜像&#xff0c;windows10安装WSL2及使用 2、开启安全surf the internet工具…

MQ 架构设计原理与消息中间件详解(三)

RabbitMQ实战解决方案 RabbitMQ死信队列 死信队列产生的背景 RabbitMQ死信队列俗称&#xff0c;备胎队列&#xff1b;消息中间件因为某种原因拒收该消息后&#xff0c;可以转移到死信队列中存放&#xff0c;死信队列也可以有交换机和路由key等。 产生死信队列的原因 消息投…