【Linux取经路】一个简单的日志模块

news2024/11/18 6:21:20

文章目录

  • 一、可变参数的使用
  • 二、Log
    • 2.1 日志打印
      • 2.1.1 时间获取
      • 2.1.2 日志分块打印
    • 2.2 打印模式选择
    • 2.3 Log 使用样例
    • 2.4 Log 完整源码
  • 三、结语

在这里插入图片描述

一、可变参数的使用

int sum(int n, ...)
{
    va_list s; // va_list 本质上就是一个指针
    va_start(s, n); 

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

    va_end(s);

    return sum;
}

va_list 本质上就是一个 char * 类型,va_start 的作用就是让指针 s 指向函数栈帧中第一个非可变形参。因为函数的实参是按照从右向左的顺序进行压栈的,因此只要知道了第一个非可变形参的位置,就可以找到第一个可变参数的位置,进而去解析所有的可变参数。这就要求:可变参数的左边必须要有一个具体的参数va_arg 是根据第二个参数所确定的类型来提取可变参数,va_end 是将 s 置空。

image-20240305154610604

二、Log

2.1 日志打印

日志一般包括:日志的时间、日志的等级、日志的内容、文件名称和行号。

2.1.1 时间获取

时间获取介绍time 返回值是时间戳、gettimeofday 系统调用接口、localtime 将一个时间戳转化成我们看得懂的格式。

gettimeofday:

image-20240305163715736

localtime:

image-20240305170401246

void logmessage(int level, const char *format, ...)
{
    time_t t = time(nullptr);
    struct tm *ctime = localtime(&t);
    printf("%d-%d-%d %d:%d:%d", ctime->tm_year+1900, ctime->tm_mon+1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec); 
}

2.1.2 日志分块打印

根据日志的内容,可以将一条日志信息分成两部分:默认部分自定义部分

  • 默认部分:日志的时间、等级。

  • 自定义部分:日志的内容,需要进行格式化控制的内容。

日志等级转字符串模块:

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";
    }
}

默认部分代码:通过 snprintf 函数将默认部分的信息写入到一个字符数组中。

void DefaultMessage(int level, char *defaultbuffer, int size)
{
    time_t t = time(nullptr);
    struct tm *ctime = localtime(&t);
    snprintf(defaultbuffer, size, "[%s][%d-%d-%d %d:%d:%d]", LevelToString(level), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
}

自定义部分代码:通过 vsnprintf 函数来帮助我们解析用户的格式化输入,将用户自定义的信息写入到一个字符数组中。

void logmessage(int level, const char *format, ...)
{
    char defaultbuffer[SIZE];  // 存储默认内容
    DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));

    char userbuffer[SIZE]; // 存储自定义内容
    va_list s;
    va_start(s, format);
    vsnprintf(userbuffer, sizeof(userbuffer), format, s);
    va_end(s);
}

将默认内容和自定义内容合并

void logmessage(int level, const char *format, ...)
{
    char defaultbuffer[SIZE];  // 存储默认内容
    DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));

    char userbuffer[SIZE]; // 存储自定义内容
    va_list s;
    va_start(s, format);
    vsnprintf(userbuffer, sizeof(userbuffer), format, s);
    va_end(s);

    char logtxt[SIZE*2];
    snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);
    std::cout << logtxt << std::endl;
}

2.2 打印模式选择

可以通过设置选项,将日志信息打印到:**显示器、一个文件、多个文件(同等级的在一个文件)。**抽象一个 Log 类,在里面设置一个成员变量来选择日志的输出。

模式设置:

void SetStyle(int style)
{
    _outputstyle = style;
}

模式选择:

void OutPutLog(int level, const std::string &message)
{
    switch (_outputstyle)
    {
        case Screen: // 向显示器打印
            OutPutToScreen(message);
            return;
        case Onefile: // 向一个文件中打印
            OutPutToOnefile(_logpath, message);
            return;
        case Classfile: // 向多个文件中打印
            OutPutToClassfile(level, message);
            return;
    }
}

向显示器打印:

// 将日志信息打印到屏幕
void OutPutToScreen(const std::string &message)
{
    std::cout << message << std::endl;
}

向一个文件中写入:

// 将日志信息保存到一个文件中
void OutPutToOnefile(const std::string &path, const std::string &message)
{
    // 打开文件
    std::fstream fp;
    fp.open(path, std::ios::app);
    if (!fp.is_open())
    {
        std::cout << path << " open faile" << std::endl;
    }

    // 向文件写入
    fp << message << std::endl;

    // 关闭文件
    fp.close();
}

向多个文件中写入:

// 将日志信息按照等级保存到不同文件中
void OutPutToClassfile(int level, const std::string &message)
{
    switch (level)
    {
        case Info:
            OutPutToOnefile(INFO_LOG_PATH, message);
            break;
        case Debug:
            OutPutToOnefile(DEBUG_LOG_PATH, message);
            break;
        case Warning:
            OutPutToOnefile(WARING_LOG_PATH, message);
            break;
        case Error:
            OutPutToOnefile(ERROR_LOG_PATH, message);
            break;
        case Fatal:
            OutPutToOnefile(FATAL_LOG_PATH, message);
            break;
        default:
            break;
    }

    return;
}

operator() 让调用显得更加优雅:

void operator()(int level, const char *format, ...)
{
    char defaultbuffer[SIZE]; // 存储默认内容
    DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));

    char userbuffer[SIZE]; // 存储自定义内容
    va_list s;
    va_start(s, format);
    vsnprintf(userbuffer, sizeof(userbuffer), format, s);
    va_end(s);

    char logtxt[SIZE * 2];
    snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);
    // std::cout << logtxt << std::endl;

    OutPutLog(level, logtxt);
}

2.3 Log 使用样例

#include "log.hpp"
#include <stdlib.h>
#include <unistd.h>

int main()
{
    Log log;
    int cnt = 10;
    while (cnt--)
    {
        if(cnt == 5) log.SetStyle(Classfile);
        log(Info, "I am %d %s %f", 2, "wuchengyang", 3.14);
        log(Debug, "I am %d %s %f", 3, "wuchengyang", 4.78);
        log(Fatal, "I am %d %s %f", 4, "wuchengyang", 5.32);
        sleep(1);
    }

    return 0;
}

2.4 Log 完整源码

#pragma once
#include <stdarg.h>
#include <iostream>
#include <time.h>
#include <fstream>

#define SIZE 1024
// 定义日志等级
#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 LOG_PATH "./Log/log.txt"
#define INFO_LOG_PATH "./Log/InfoLog.txt"
#define DEBUG_LOG_PATH "./Log/DebugLog.txt"
#define WARING_LOG_PATH "./Log/WaringLog.txt"
#define ERROR_LOG_PATH "./Log/ErrorLog.txt"
#define FATAL_LOG_PATH "./Log/FatalLog.txt"

class Log
{
public:
    Log(const std::string &logpath = LOG_PATH, int style = Onefile)
        : _logpath(logpath),
          _outputstyle(style)
    {
    }

private:
    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";
        }
    }

    void DefaultMessage(int level, char *defaultbuffer, int size)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        snprintf(defaultbuffer, size, "[%s][%d-%d-%d %d:%d:%d]", LevelToString(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
    }

    // 将日志信息打印到屏幕
    void OutPutToScreen(const std::string &message)
    {
        std::cout << message << std::endl;
    }

    // 将日志信息保存到一个文件中
    void OutPutToOnefile(const std::string &path, const std::string &message)
    {
        // 打开文件
        std::fstream fp;
        fp.open(path, std::ios::app);
        if (!fp.is_open())
        {
            std::cout << path << " open faile" << std::endl;
        }

        // 向文件写入
        fp << message << std::endl;

        // 关闭文件
        fp.close();
    }

    // 将日志信息按照等级保存到不同文件中
    void OutPutToClassfile(int level, const std::string &message)
    {
        switch (level)
        {
        case Info:
            OutPutToOnefile(INFO_LOG_PATH, message);
            break;
        case Debug:
            OutPutToOnefile(DEBUG_LOG_PATH, message);
            break;
        case Warning:
            OutPutToOnefile(WARING_LOG_PATH, message);
            break;
        case Error:
            OutPutToOnefile(ERROR_LOG_PATH, message);
            break;
        case Fatal:
            OutPutToOnefile(FATAL_LOG_PATH, message);
            break;
        default:
            break;
        }

        return;
    }

    void OutPutLog(int level, const std::string &message)
    {
        switch (_outputstyle)
        {
        case Screen: // 向显示器打印
            OutPutToScreen(message);
            return;
        case Onefile: // 向一个文件中打印
            OutPutToOnefile(_logpath, message);
            return;
        case Classfile: // 向多个文件中打印
            OutPutToClassfile(level, message);
            return;
        }
    }

public:
    void SetStyle(int style)
    {
        _outputstyle = style;
    }

    // void logmessage(int level, const char *format, ...)
    // {
    //     char defaultbuffer[SIZE]; // 存储默认内容
    //     DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));

    //     char userbuffer[SIZE]; // 存储自定义内容
    //     va_list s;
    //     va_start(s, format);
    //     vsnprintf(userbuffer, sizeof(userbuffer), format, s);
    //     va_end(s);

    //     char logtxt[SIZE * 2];
    //     snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);
    //     // std::cout << logtxt << std::endl;

    //     OutPutLog(level, logtxt);
    // }

    void operator()(int level, const char *format, ...)
    {
        char defaultbuffer[SIZE]; // 存储默认内容
        DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));

        char userbuffer[SIZE]; // 存储自定义内容
        va_list s;
        va_start(s, format);
        vsnprintf(userbuffer, sizeof(userbuffer), format, s);
        va_end(s);

        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);
        // std::cout << logtxt << std::endl;

        OutPutLog(level, logtxt);
    }

    ~Log()
    {
    }

private:
    int _outputstyle;
    std::string _logpath;
};

三、结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!

在这里插入图片描述

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

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

相关文章

vue3 + antd-vue@4 a-table单元格合并,rowSpan(行合并),colSpan(列合并)详解, 表头合并详解, 表头自定义详解

一、解释 1、rowSpan 1&#xff09;、行合并 2&#xff09;、当为0时&#xff1a;去掉边框 3&#xff09;、当为1时&#xff1a;不合并 4&#xff09;、大于1的x时&#xff1a;包含当前单元格行合并x个单元格 2、colSpan 1&#xff09;、列合并 2&#xff09;、当为0时&#xf…

反序列化漏洞(JBoss、apache log4、apache Shiro、JWT)Weblogic未授权访问、代码执行、任意上传

1.1什么是反序列化 就是把一个对象变成可以传输的字符串&#xff0c;目的就是为了方便传输。假设&#xff0c;我们写了一个class&#xff0c;这个class里面存有一些变量。当这个class被实例化了之后&#xff0c;在使用过程中里面的一些变量值发生了改变。以后在某些时候还会用到…

CTF网络安全大赛简单web题目:eval

题目来源于&#xff1a;bugku 题目难度&#xff1a;简单 一道简单web的题目 题目源代码&#xff1a; <?phpinclude "flag.php";$a $_REQUEST[hello];eval( "var_dump($a);");show_source(__FILE__); ?> 这个PHP脚本有几个关键部分&#xff0c;但…

Salesforce ADFS SSO 配置

1.set up 中 Certificate and Key Management创建认证 2.setup中Single Sign-On Settings新建SSO&#xff0c;输入必填 3.setup中My Domain Settings设置登录跳转&#xff1a; 最后将Single Sign-On Settings中metadata.xml下载下来给ADFS端使用&#xff1a; 最后效果&#xff…

快手二面准备【面试准备】

快手二面准备【面试准备】 前言版权快手二面准备秋招一面中的问题实习一面中的问题计算机网络和操作系统论坛项目登录注册ThreadLocal代替session存储用户秒杀项目登录注册->阿里验证码->rpcsession为什么改为token实现&#xff0c;redis存储用户信息由binlog的用法->…

新火种AI|净利润上升628%,英伟达财报说明AI热潮还将持续

作者&#xff1a;一号 编辑&#xff1a;美美 AI大潮仍未放缓&#xff0c;英伟达再次超越预期。 今天凌晨&#xff0c;全球AI算力芯片龙头&#xff0c;被称为“AI时代卖铲人”的英伟达&#xff0c;正式公布了截至2024年4月28日的2025财年第一财季财报&#xff0c;其中第一财季…

【九十三】【算法分析与设计】719. 找出第 K 小的数对距离,N 台电脑的最长时间,二分答案法

719. 找出第 K 小的数对距离 - 力扣&#xff08;LeetCode&#xff09; 数对 (a,b) 由整数 a 和 b 组成&#xff0c;其数对距离定义为 a 和 b 的绝对差值。 给你一个整数数组 nums 和一个整数 k &#xff0c;数对由 nums[i] 和 nums[j] 组成且满足 0 < i < j < nums.le…

uniapp星空效果

uniapp星空效果 背景实现思路代码实现尾巴 背景 之前在网上看到过一个视频&#xff0c;使用纯css实现过一个星空效果。具体出处找不到了&#xff0c;我们按照他那个思路来实现一个类似的效果&#xff0c;还是先上一张图镇楼&#xff1a; 实现思路 首先我们这个效果使用的是…

es数据备份和迁移Elasticsearch

Elasticsearch数据备份与恢复 前提 # 注意&#xff1a; 1.在进行本地备份时使用--type需要备份索引和数据&#xff08;mapping,data&#xff09; 2.在将数据备份到另外一台ES节点时需要比本地备份多备份一种数据类型&#xff08;analyzer,mapping,data,template&#xff09; …

JAVA开发面试超详细

一、Java 基础 1.JDK 和 JRE 有什么区别&#xff1f; jdk&#xff1a;java development kit jre&#xff1a;java runtime Environment jdk是面向开发人员的&#xff0c;是开发工具包&#xff0c;包括开发人员需要用到的一些类。 jre是java运行时环境&#xff0c;包括java虚拟机…

使用可接受gitlab参数的插件配置webhook

jenkins配置 安装Generic Webhook Trigger 配置远程触发令牌 勾选Print post content和Print contributed variables用于打印值 配置gitlab 选择新增webhook 配置webhook http://JENKINS_URL/generic-webhook-trigger/invoke,将JENKINS_URL修改成自己的jenkins地址 先保存…

Docker(四) 文件和网络

1 Dockerfile 1.1 什么是Dockerfile Dockerfile是一个文本文件&#xff0c;包含一系列命令&#xff0c;这些命令用于在 Docker 镜像中自动执行操作。Dockerfile 定义了如何构建 Docker 镜像的步骤和所需的操作。 Dockerfile 中包含的命令可以设置和定制容器的环境&#xff0c;…

30.包名的修改和新建后端模块

权限和第三方登录确实令人头疼,我们来学一点简单一点的。 另外,如果各位有属于自己的域名和ICP/IP备案,布置一个作业,自行实现第三方QQ登录。 我们所说的包名修改,是一次性修改ruoyi的全部包名,因为发现很多人有这样的需求,下载别人的代码,想要改成自己公司的包名,结…

android11屏蔽下拉菜单快捷开关

1.文件位置&#xff1a; frameworks/base/packages/SystemUI/res/values/config.xml a.这个文件定义了初始化的一些组件&#xff0c;部分代码如下&#xff1a; //overlay/frameworks/base/packages/SystemUI/res/values/config.xml<!-- The default tiles to display in …

git使用介绍

一、为什么做版本控制&#xff08;git是版本控制工具&#xff09; 为了保留之前所以的版本&#xff0c;以便回滚和修改 二、点击安装 三、基础操作 1、初步认识 想要让git对一个目录进行版本控制需要以下步骤&#xff1a; 进入要管理的文件夹进行初始化命令 git init管理…

工厂做外贸,国内公司跟香港公司有什么区别

最近有不少做内贸的工厂朋友想转型做外贸&#xff0c;问我要不要单独注册一个公司&#xff0c;以及注册哪里的公司&#xff0c;国内的公司跟香港的公司有什么区别&#xff1f;今天就简单来给大家分享一下这块的一个区别。 首先工厂转型做外贸的话&#xff0c;比较建议注册一个…

【C++】 单例设计模式的讲解

前言 在我们的学习中不免会遇到一些要设计一些特殊的类&#xff0c;要求这些类只能在内存中特定的位置创建对象&#xff0c;这就需要我们对类进行一些特殊的处理&#xff0c;那我们该如何解决呢&#xff1f; 目录 1. 特殊类的设计1.1 设计一个类&#xff0c;不能被拷贝&#xf…

算法特训,AB5 .点击消除BC.149简写单词牛客.除2!牛客.Fibonacci数列

目录 AB5 .点击消除 BC.149简写单词 牛客.除2&#xff01; 牛客.Fibonacci数列 AB5 .点击消除 点击消除&#xff0c;类似于括号匹配a(b[b]a){c{d,这种&#xff0c;利用栈去消除,这样正好可以处理&#xff0c;假如相同就不进栈&#xff0c;同时还要出栈。注意我们这么搞完他是…

Leaflet【二】图层绘制——UI图层【点线面】 矢量图层【img、svg】

layer图层 在leaflet当中使用图层比OL当中简便一点&#xff0c;我们创建的layer图层可以直接通过 addTo 方法加到地图上&#xff0c;不需要通过layer、source再去做一些区分&#xff0c; 图标 Icon 创建Marker时提供的一个Icon 详细配置–>go // 导入一张图片作为图标imp…

在 Android 上存档短信:4 种方法的终极指南

概括 无论是个人对话还是专业信件&#xff0c;我们的短信收件箱很快就会因大量线程和对话而变得混乱。为了帮助管理这种过载&#xff0c;许多 Android 用户转向了归档短信这一便捷功能。在本指南中&#xff0c;我们将探讨如何在 Android 设备上存档短信的详细信息&#xff0c;…