【Linux】简易日志系统

目录

一、概念

二、可变参数

三、日志系统


一、概念

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

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

如果系统或程序发生了错误或存在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.mfbz.cn/a/881382.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【路径规划】 红嘴蓝鹊优化器:一种用于2D/3D无人机路径规划和工程设计问题的新型元启发式算法

摘要 本文提出了一种新型元启发式算法——红嘴蓝鹊优化器&#xff08;RBMO&#xff09;&#xff0c;用于解决2D和3D无人机路径规划以及复杂工程设计问题。RBMO灵感来源于红嘴蓝鹊的群体合作行为&#xff0c;包括搜索、追逐、捕猎和食物储藏。该算法通过模拟这些行为&#xff0…

prober found high clock drift,Linux服务器时间不能自动同步,导致服务器时间漂移解决办法。

文章目录 一、场景二、问题三、解决办法&#xff08;一&#xff09;给服务器添加访问网络能力&#xff08;二&#xff09;手动同步1. 检查有没有安装ntp2. 没有安装ntp则离线安装ntp2.1 下载安装包2.2 安装2.3 启动 ntp 3. 设置内部时钟源3.1 编辑/etc/ntp.conf3.1 重启ntp服务…

低代码平台后端搭建-阶段完结

前言 最近又要开始为跳槽做准备了&#xff0c;发现还是写博客学的效率高点&#xff0c;在总结其他技术栈之前准备先把这个专题小完结一波。在这一篇中我又试着添加了一些实际项目中可能会用到的功能点&#xff0c;用来验证这个平台的扩展性&#xff0c;以及总结一些学过的知识。…

【C++】关键字auto详解

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:C: 探索C编程精髓&#xff0c;打造高效代码仓库 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、前言 二、类型别名思考 三、auto简介 四…

python 运行其他命令行工具,实时打印输出内容

起因&#xff0c; 目的: python 运行一个命令&#xff0c;最简洁的写法是: import os # 转换视频格式。 cmd "ffmpeg -i a1.ts -c copy a1.mp4"os.system(cmd)问题&#xff1a; 如果上面的视频比较大&#xff0c;需要运行很长时间&#xff0c;那么感觉就像是卡住…

向日葵和这三款远程控制神器,让你轻松掌控一切!

向日葵远程控制&#xff0c;作为科技控们的最佳良伴&#xff0c;一定是我们居家、办公必备的神器啦&#xff01;别看咱们工作、学习有时候烦得心都碎成了二八瓣&#xff0c;但有了向日葵远程控制&#xff0c;咱们的效率绝对能飞起来&#xff01;今天&#xff0c;咱们就一起走进…

C++11 lambda表达式

前言 上几期我们介绍了类的新功能&#xff0c;右值引用、完美转发语法特性&#xff0c;本期继续介绍C11的新语法特性&#xff0c;即lambda表达式&#xff01; 目录 前言 lambda表达式 lambda的引入 什么是lambda 表达式 lambda表达式的语法 捕捉列表说明 lambda的底层…

卡西欧相机SD卡格式化后数据恢复指南

在数字摄影时代&#xff0c;卡西欧相机以其卓越的性能和便携性成为了众多摄影爱好者的首选。然而&#xff0c;随着拍摄量的增加&#xff0c;SD卡中的数据管理变得尤为重要。不幸的是&#xff0c;有时我们可能会因为操作失误或系统故障而将SD卡格式化&#xff0c;导致珍贵的照片…

Linux笔记---简单指令

1. 使用的环境 博主使用的是华为云服务器xshell终端的方式学习的&#xff0c;因为据说这样的方式比较接近以后的工作环境。 其中云服务器安装的是Ubuntu操作系统(以Linux为内核&#xff0c;适合新手学习Linux的一个版本) 这里的云服务器不一定使用华为的&#xff0c;但是我在…

后台数据管理系统 - 项目架构设计-Vue3+axios+Element-plus(0920)

十三、文章分类页面 - [element-plus 表格] Git仓库&#xff1a;https://gitee.com/msyycn/vue3-hei-ma.git 基本架子 - PageContainer 功能需求说明&#xff1a; 基本架子-PageContainer封装文章分类渲染 & loading处理文章分类添加编辑[element-plus弹层]文章分类删除…

win7自带壁纸丢失主题丢失

有时候盗版破解或者其他美化工具会导致win7自带的壁纸丢失&#xff0c;从个性化管理里面无法恢复原始的壁纸&#xff08;如下图&#xff09;&#xff0c;但是由于工作原因公司的电脑又不方便设置第三方的壁纸&#xff0c;所以找了一下解决方案。 经典问题&#xff0c;百度找到的…

用户态缓存:高效数据交互与性能优化

目录 1. 用户态缓存区工作背景 1.1 为什么每条连接都需要读写缓存区 1.1.1 读缓存区&#xff08;Read Buffer&#xff09; 1.1.2 写缓存区&#xff08;Write Buffer&#xff09; 1.2 用户态缓存区的工作流程 1.3 用户态缓存区的重要性 2. UDP 和 TCP 的设计差异 2.1 UD…

机器翻译与数据集_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录介绍机器翻译下载和预处理数据集词元化词表加载数据集训练模型对上述代码中出现的Vocab进行总体解释和逐行解释使用场景 小结练习答案1. num_examples 参数对词表大小的影响2. 对于没有单词边界的语言&#xff0c;单词级词元化的有效性 介绍…

关于 Visual Studio Code 如何插入自定义快捷方式

第一步&#xff1a;打开控制面板&#xff0c;也可以使用快捷键ctrlshiftp 然后点击命令面板 第二步&#xff1a;输入snippets搜索&#xff0c;选择配置用户代码片段 第三步&#xff1a;选择新建全局代码片段文件&#xff0c;然后输入文件名&#xff0c;这里我因为创建的是vue的…

解决uniapp开发的app,手机预览,上下滑动页面,页面出现拉伸,抖动的效果问题,

在pages.json文件里“globalStyle”下面的"app-plus"里加入"bounce": "none"即可 "app-plus": { "bounce": "none", //关闭窗口回弹效果 }

2024年华为杯数学建模研赛(C题) 建模解析| 磁芯损耗建模 | 小鹿学长带队指引全代码文章与思路

我是鹿鹿学长&#xff0c;就读于上海交通大学&#xff0c;截至目前已经帮2000人完成了建模与思路的构建的处理了&#xff5e; 本篇文章是鹿鹿学长经过深度思考&#xff0c;独辟蹊径&#xff0c;实现综合建模。独创复杂系统视角&#xff0c;帮助你解决研赛的难关呀。 完整内容可…

2024华为杯研究生数学建模竞赛(研赛)选题建议+初步分析

提示&#xff1a;C君认为的难度&#xff1a;DE<C<F&#xff0c;开放度&#xff1a;CDE>F。 华为专项的题目&#xff08;A、B题&#xff09;暂不进行选题分析&#xff0c;不太建议大多数同学选择&#xff0c;对自己专业技能有很大自信的可以选择华为专项的题目。后续会…

Mysql_使用简介

课 程 推 荐我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448;入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448;虚 拟 环 境 搭 建 &#xff1a;&#x1…

2024华为杯研赛D题保姆级教程思路分析+教程

2024年中国研究生数学建模竞赛D题保姆级教程思路分析 D题&#xff1a;大数据驱动的地理综合问题&#xff08;数学分析&#xff0c;统计学&#xff09; 关键词&#xff1a;地理、气候、统计&#xff08;细致到此题&#xff1a;统计指标、统计模型、统计结果解释&#xff09; …

Linux通过yum安装Docker

目录 一、安装环境 1.1. 旧的docker包卸载 1.2. 安装常规环境包 1.3. 设置存储库 二、安装Docker社区版 三、解决拉取镜像失败 3.1. 创建文件目录/etc/docker 3.2. 写入镜像配置 https://docs.docker.com/engine/install/centos/ 检测操作系统版本&#xff0c;我操作的…