【Linux】简易日志系统

news/2024/9/21 18:52:20 标签: linux, windows, 运维

目录

一、概念

二、可变参数

三、日志系统


一、概念

一个正在运行的程序或系统就像一个哑巴,一旦开始运行我们很难知晓其内部的运行状态。

但有时在程序运行过程中,我们想知道其内部不同时刻的运行结果如何,这时一个日志系统可以有效的帮助我们监控程序的运行状态。

如果系统或程序发生了错误或存在bug,通过日志的内容我们也可以很快的知道故障的原因并定位错误的位置

一个成熟的日志至少需要包含以下信息:

  • 日志时间
  • 日志等级

根据情况可将日志划分为不同的等级,例如常规信息、警告信息、严重错误、致命错误、调试信息

  • 日志内容
  • 文件名称或行号


二、可变参数

日志的内容需要我们指定格式并传参,而参数的个数是不确定的。因此在学习编写日志系统之前,我们先了解一下可变参数的用法

以下是对可变参数进行操作时需要用到的函数/宏

#include <stdarg.h>

void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);

我们以一个可以同时累加多个变量的函数为例:

int sum(int n, ...)
{}

形参在实例化时会从右向左进行压栈,也就是说多个参数在函数栈帧中是连续的,因此我们可以通过地址的偏移来依次访问到所有的参数

首先:

int sum(int n, ...)
{
    va_list s;
    va_start(s, n);
}

其中va_list实际上就是char*, 而va_start可以让s指向参数n的下一个参数,也就是可变参数的第一个参数的位置。此时我们就有了获取第一个参数内容的前提

这也是为什么printf等支持可变参数的函数中必须至少要有一个确定的参数,有了该参数才能找到可变参数的起始地址

int sum(int n, ...)
{
    va_list s;
    va_start(s, n);

    int sum = 0;
    while(n--)
    {
        sum += va_arg(s, int);
    }
    va_end(s);
    return sum;
}

其中,va_arg传入s和可变参数的类型,用于提取s指向的参数,并且移动s到下一个参数的位置

va_end将s置为空

测试效果:

拓展问题:如果可变参数中,不同参数有不同的类型怎么办?

这也是为什么printf的第一个参数需要传入一个用于控制格式的字符串,通过遍历字符串就能知道可变参数中有哪些类型了


三、日志系统

本文实现的日志系统具备以下功能: 

  • 包含日志等级、日志时间、日志内容
  • 将日志功能封装成类,并重载了函数调用运算符
  • 可以选择将日志输出到终端、输出到同一文件或按照日志等级分类输出到不同文件
  • 用户可自定义日志内容格式

如果要让日志包含文件名和行号,则可以通过宏定义__FILE__和__LINE__获取文件名和行号

接下来是完整代码(附注释)

#pragma once

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

// 日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define SIZE 1024 // 缓冲区大小

// 日志的输出方式
#define Screen 1    // 输出到显示器
#define Same_file 2 // 输出到同一文件
#define Diff_file 3 // 按照等级输出到不同文件

#define Filename "log.txt"

class Log
{
public:
    Log()
    {
        _method = Screen; // 默认输出到显示器
    }

    void output(int method) // 更改输出方式
    {
        _method = method;
    }

    std::string level2string(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";
        }
    }

    void operator()(int level, const char *format, ...)
    {
        va_list s;
        va_start(s, format); // s指向可变参数
        messagehandle(level, format, s);
    }

    void messagehandle(int level, const char *format, va_list s) // 整合日志字符串
    {
        time_t t = time(nullptr);         // 获取时间戳
        struct tm *ctime = localtime(&t); // 将时间戳转换为时间
        char levelAndtime[SIZE];          // 日志等级和时间部分
        snprintf(levelAndtime, sizeof(levelAndtime), "[%s][%d-%d-%d %02d:%02d:%02d]", level2string(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        char content[SIZE]; // 用户自定义的内容部分
        vsnprintf(content, sizeof(content), format, s);
        va_end(s);

        char message[SIZE * 2]; // 整合所有部分
        snprintf(message, sizeof(message), "%s %s\n", levelAndtime, content);

        OutputLog(level, message); // 将整合后的日志输出
    }

    void OutputLog(int level, const std::string &logmessage)
    {
        switch (_method) // 根据输出方式进行调整
        {
        case Screen: // 输出到显示器
            std::cout << logmessage << std::endl;
            break;
        case Same_file: // 输出到同一文件
            SamefileOutput(Filename, logmessage);
            break;
        case Diff_file: // 输出到不同文件
            DiffileOutput(level, logmessage);
            break;
        default:
            break;
        }
    }

    void SamefileOutput(const std::string &filename, const std::string &logmessage)
    {
        int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); //打开文件
        if(fd < 0) //打开失败
            return;
        write(fd, logmessage.c_str(), logmessage.size()); //写入日志
        close(fd); //关闭文件描述符
    }

    void DiffileOutput(int level, const std::string &logmessage)
    {
        std::string filename = Filename;
        filename += ".";
        filename += level2string(level); //根据日志等级调整文件名
        SamefileOutput(filename, logmessage); //复用SamefileOutput函数
    }

    ~Log()
    {}

private:
    int _method; // 输出方式
};

测试:

向显示器输出日志(n%5用于模拟不同日志等级)

向同一文件中输出日志

向不同文件中输出日志

完.


http://www.niftyadmin.cn/n/5669282.html

相关文章

集成学习详细介绍

以下内容整理于&#xff1a; 斯图尔特.罗素, 人工智能.现代方法 第四版(张博雅等译)机器学习_温州大学_中国大学MOOC(慕课)XGBoost原理介绍------个人理解版_xgboost原理介绍 个人理解-CSDN博客 集成学习(ensemble)&#xff1a;选择一个由一系列假设h1, h2, …, hn构成的集合…

【Python】Anaconda插件:Sublime Text中的Python开发利器

上班的时候没人问我苦不苦&#xff0c;下班的时候总有人问为什么走这么早。 Anaconda 是一个专为Sublime Text打造的开源Python开发插件&#xff0c;旨在为开发者提供类似于IDE的丰富功能&#xff0c;提升Python编码效率。该插件提供了代码补全、语法检查、代码片段提示等多项…

基于FPGA+GPU异构平台的遥感图像切片解决方案

随着遥感和成像技术的不断进步和普及&#xff0c;获取大量高分辨率的遥感图像已成为可能。这些大规模的遥感图像数据需要进行有效的处理和分析&#xff0c;以提取有用的信息&#xff0c;进行进一步的应用。遥感图像切片技术应运而生&#xff0c;该技术可以将大型遥感图像分割成…

C++初始化列表详解 + explicit关键字

构造函数初始化列表 构造函数&#xff1a; class Date { public:// 构造函数Date(int year 0, int month 1, int day 1){_year year;_month month;_day day;//可以修改值_year 2024;_month 9;_day 21;} private:int _year;int _month;int _day; }; 这样的构造函数和…

神经网络 归一化层

为什么要进行网络归一化层&#xff1f; 神经网络训练过程中&#xff0c;当网络层数较多的时候&#xff0c;每一轮训练每个网络层的参数都会发生变化&#xff0c;那么网络层参数变化会有什么影响呢&#xff1f; 1. 向网络中输入相同分布的样本时&#xff0c;由于每一层网络的参…

PCF8563驱动源码

提供年、月、日、星期&#xff0c;时、分、秒计时&#xff0c;使用外置32.768Khz晶振。低后备电流&#xff1a;0.25uA&#xff0c;VDD3.0V&#xff0c;温度25℃。IIC接口&#xff0c;速度最高400KHz。可编程时钟输出&#xff0c;可以供其他设备使用&#xff0c;可输出的时钟频率…

Java 19 新特性-结构化并发(Structured Concurrency)[Preview]

Java 19 新特性&#xff1a;结构化并发&#xff08;Structured Concurrency&#xff09;[预览] Java 19 在并发编程领域引入了一个全新的概念——结构化并发&#xff08;Structured Concurrency&#xff09;&#xff0c;作为预览功能发布。这一特性旨在简化并发任务的管理&…

[Python数据可视化]探讨数据可视化的实际应用:三个案例分析

数据可视化是理解复杂数据集的重要工具&#xff0c;通过图形化的方法&#xff0c;可以直观地展示信息、趋势和模式。本文将深入探讨三个实际案例&#xff0c;包括健康数据分析、销售趋势分析、城市交通流量分析。每个案例将提供假设数据、详细注释的代码及分析结果。 案例 1: …