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

news2024/11/28 17:59:46

在这里插入图片描述

👦个人主页: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://www.coloradmin.cn/o/1815871.html

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

相关文章

权限维持--小结

权限维持 windows 域环境 基于验证 DLL 加载-SSP 基于验证 DLL 加载-HOOK 基于机制账号启用-DSRM 基于用户属性修改-SID-history 基于登录进程劫持-Skeleton-Key 单机 基于用户-隐藏用户 基于服务 TGT-黄金白银票据 基于软件-GotoHTTP&RustDesk 自启动 粘滞键 …

RERCS系统开发实战案例-Part03 创建Web Dynpro对应的FPM Application

1、通过事务码SE80 资源浏览器创建 2、通过事务码FPM_WB在WEB端创建 3、创建FPM Application步骤 1&#xff09;选择&#xff1a;在业务实体上创建FPM应用程序的向导&#xff1b; 2&#xff09;配置&#xff1a;输入平面布置对象&#xff1b; 3&#xff09;单击 下一个&#…

美业SaaS收银系统拓客系统源码分享-预约管理功能的作用和重要性

美业收银管理系统对于美容、美发、医美行业的门店来说至关重要&#xff0c;它不仅可以帮助提高管理效率和降低成本&#xff0c;还可以改善客户体验并促进业务增长。 &#xff08;私信获取源码/演示视频&#xff09; ▶ 美业系统中的【预约管理】有多种作用&#xff0c;包括&…

业务架构的位置及关系

背景 我们已经了解了业务架构的核心元素组成&#xff0c;以及各个扩展元素&#xff0c;同时对各个元素的关系协同也有了一些了解&#xff0c;那么接下来&#xff0c;我们进一步在宏观层面来看业务架构与其他架构的关系。 企业架构 企业架构有多种理解&#xff0c;也有多种叫…

kotlin 中的数字

以下均来自官方文档&#xff1a; 一、整数类型 1、kotlin中内置的整数类型&#xff0c;有四种不同大小的类型&#xff1a; 类型存储大小&#xff08;比特数&#xff09;最小值最大值Byte8-128127Short16-3276832767Int32-2,147,483,648 (-231)2,147,483,647 (231 - 1)Long64…

(Git)多人协作1

文章目录 前言总结 前言 目标&#xff1a;master分支下file.txt文件新增“aaa”,“bbb” 实现&#xff1a;开发者1新增“aaa”,开发者2新增“bbb” 条件&#xff1a;在同一个分支下协作完成 实际开发过程中&#xff0c;每个用户都与属于自己的码云账户&#xff0c;如果想要进…

闪烁与常亮的符号状态判断机制(状态机算法)

背景说明 在视觉项目中&#xff0c;经常要判断目标的状态&#xff0c;例如&#xff1a;符号的不同频率闪烁、常亮等。然而常规的视觉算法例如YOLO&#xff0c;仅仅只能获取当前帧是否存在该符号&#xff0c;而无法对于符号状态进行判断&#xff0c;然而重新写一个基于时序的卷积…

9 - 上升的温度(高频 SQL 50 题基础版)

9 - 上升的温度 -- 找出与之前&#xff08;昨天的&#xff09;日期相比温度更高的所有日期的 id -- DATEDIFF(2007-12-31,2007-12-30); # 1 -- DATEDIFF(2010-12-30,2010-12-31); # -1select w1.id from Weather w1, Weather w2 wheredatediff(w1.recordDate,w2.recordDat…

Android Studio项目升级报错:Namespace not specified

原项目升级AGP到8.0时报错&#xff1a; Namespace not specified. Specify a namespace in the modules build file: C:\Users\Administrator\Desktop\MyJetpack\app\build.gradle. See https://d.android.com/r/tools/upgrade-assistant/set-namespace for information about…

准研究生了解内容:如何挑选论文并下载

本文主要纪录自己从0开始摸索如何找论文&#xff0c;下载论文等的过程。 前言 &#xff08;一点想法&#xff09;## 作为准研究生&#xff0c;上岸后一直非常颓废&#xff0c;除了给人补课挣了点money&#xff0c;剩下时间都是打游戏&#xff0c;被老姐训诫后决定继续学习。毕…

1+x(Java)中级题库易混淆理论题(四)

Java 代码 15.2%5 的结果为0.2 super 关键字是在子类对象内部指代其父类对象的引用 IllegalAccessException 是访问权限不足构成的异常&#xff1b;ClassCastException 是类型转换异常&#xff1b;InputMismatchException 通常是使用 Scanner 输入数据时发生的异常。 List 集合…

分治法思想-归并排序案例图片详解

分治法简介 分治法思想 ​ 分治法&#xff0c;就是将一个难以解决的大问题给分成多个规模较小的子问题&#xff0c;分别解决各个子问题&#xff0c;最后合并子问题的解得到原问题的解。 分治法求解过程&#xff1a; 1、划分&#xff08;分&#xff09;&#xff1a; ​ 把规…

GEO ISP图像调试-PFC(蓝紫边校正)

目录 1、简单介绍 2、调试策略 3、输出结果 1、简单介绍 GEO中中调整图像蓝紫边可分为两步&#xff0c;第一步&#xff1a;调整蓝紫边检测区域&#xff0c;第二步&#xff1a;设置去蓝紫边强度。 2、调试策略 图1 该图像蓝紫边较严重 主要原因是由于蓝紫边检测不准导致的&…

怎么买充电宝不踩雷?六大充电宝选购攻略,1分钟选对充电宝!

充电宝是选快充好还是慢充好呢&#xff1f;充电宝的充电速度取决于多个因素&#xff0c;包括充电宝的容量、输入电流、充电线的质量等。一般来说&#xff0c;充电宝的充电速度可以通过输入电流来衡量&#xff0c;输入电流越大&#xff0c;充电速度越快。当我们面临选择充电宝的…

常说的上游服务和下游服务如何区分?

目录 上游和下游 &#xff08;upstream and downstream&#xff09;从信息的流向方向来看从依赖规则和价值规则来看 总结参考 上游和下游 &#xff08;upstream and downstream&#xff09; 一般在谈论服务和调用关系的时候&#xff0c;我们会使用上游和下游来表示服务间的相关…

【后端开发】服务开发场景之高可用(冗余设计,服务限流,降级熔断,超时重试,性能测试)

【后端开发】服务开发场景之高可用&#xff08;冗余设计&#xff0c;服务限流&#xff0c;降级熔断&#xff0c;超时重试&#xff0c;性能测试&#xff09; 文章目录 序&#xff1a;如何设计一个高可用的系统&#xff1f;可用性的判断指标是什么&#xff1f;哪些情况会导致系统…

小学姐教你HarmonyOS开发-02-ArkTS语言基础

简单介绍基于TypeScript扩展的ArkTS语言。 ArkTS是鸿蒙生态的应用开发语言&#xff0c;由ArkUI框架提供&#xff0c;以声明式开发范式来开发界面&#xff0c;让开发者可以更简洁、更自然的方式开发高性能应用。 源课程&#xff1a;d2school(第2学堂) 什么是ArkTS&#xff1f; …

免费的维吾尔语翻译工具,汉维翻译软件只推荐这一个《维汉翻译通App》,维吾尔文OCR字母识别!

免费维吾尔语翻译&#xff0c;真好用 《维汉翻译通》App提供免费的短文本翻译服务&#xff0c;支持翻译维吾尔语和汉语&#xff0c;无论是日常对话还是学术研究的内容&#xff0c;都能轻松应对。 维吾尔文OCR&#xff0c;文字识别新体验 高精度维文文字识别技术&#xff0c;让…

跟着AI学AI_11 PyTorch, TensorFlow 和JAX 功能对比简介

PyTorch, TensorFlow 和 JAX 功能对比简介 PyTorch、TensorFlow 和 JAX 是当前最流行的深度学习框架。它们各自具有独特的特性和优势&#xff0c;适合不同的应用场景和开发者需求。下面是对这三个框架的功能对比。 1. 基本概念和特性 PyTorch&#xff1a; 动态计算图&#xf…

王炸ChatGPT学术应用!从文献综述至定稿,轻松完成优质学术论文

智写论文 智写论文&#xff08;chatyy.cn&#xff09;是一款专注AI科研和学术写作的专业平台&#xff0c;AI论文写作、AI学术润色修改、科研分析、文献搜索和综述撰写&#xff0c;科研数据分析、英文学术润色&#xff0c;中英学术互译&#xff0c;文献查询和综述撰写&#xff…