Linux 之 【日志】(实现一个打印日志的类)

目录

1.日志的简介

1.1日志的概念

1.2日志的常见格式

2.实现日志类

包含所需头文件,定义所需宏

类成员

levelToString

operator()

printLog

printOneFile&printClassFile

完整呈现


1.日志的简介

1.1日志的概念

日志是软件运行过程中产生的带时间戳的、结构化的诊断记录,是调试的重要助手

1.2日志的常见格式

一条日志信息常包括默认部分与自定义部分

默认部分常包括:日志等级与日志时间

自定义部分则包括用户自定义的日志消息
常见的日志等级为:

(1)Info:常规消息(2)Warning:报警信息(3)Error:比较严重了,可能需要立即出来(4)Fatal:致命的(5)Debug:调试

2.实现日志类

使用下述类时,注意确保 ./log/ 目录存在

包含所需头文件,定义所需宏

复制代码
#pragma once

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

#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 LOGFILE "log.txt"

类成员

复制代码
class Log {
public:
    //构造函数:初始化打印方法与路径
    Log() : printMethod(Screen) , path("./log/"){
    }
    //用户自定义打印方法
    void Enable(int method) {
         printMethod = method;
    }
    //将日志等级转化为字符串
    std::string levelToString(int level) {
    }
    //运算符重载以记录日志消息
    void operator()(int level, const char *format, ...) {
    }
    //打印函数
    void printLog(int level, const std::string &logtxt) {
    }
    //向一个文件打印日志消息
    void printOneFile(const std::string &logname, const std::string &logtxt) {
    }
    //分类打印日志消息
    void printClassFile(int level, const std::string &logtxt) {
    }
    //无实际意义,让类看着更完整
    ~Log() {
    }

private:
    int printMethod;//打印方法:显示器;一个文件;多个文件
    std::string path;//日志文件存放的路径
};

levelToString

复制代码
    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";
        }
    }

根据宏定义直接转化

operator()

复制代码
    void operator()(int level, const char *format, ...) {
        // 默认部分:【日志等级】【年-月-日 时:分:秒】
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%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);
        // 自定义部分
        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);
        // 整合
        char logtxt[SIZE * 2 + 1];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
        // 存储或者打印
        printLog(level, logtxt);
    }

(1)重载 operator(),使得写日志像函数调用一样自然

复制代码
Log log;
log(Info, "Server started on port %d", 8080); // 输出:[Info][2024-...] Server started on port 8080

(2)默认部分包含日志等级和日志时间:将时间戳改造为年月日时分秒的格式。指的注意的是年是从1900开始的,月的范围是0-11,打印时需注意

(3)对于自定义部分日志信息,需要处理可变参数:

复制代码
void Log(int level, const char *format, ...) // 1. 函数声明接受可变参数
{
    // 2. 定义遍历指针
    va_list args_ptr; // char* 指针,用于逐个"指向"内存中的可变参数
    
    // 3. 初始化指针,定位到第一个可变参数
    va_start(args_ptr, format); 
    // 关键:编译器知道'format'是最后一个固定参数,
    // 通过它的地址计算出第一个可变参数在内存中的位置,
    // 让 args_ptr 指向那里。
    
    // 4. 使用指针进行格式化
    char buffer[256];
    vsnprintf(buffer, sizeof(buffer), format, args_ptr);
    // vsnprintf 内部会:
    //   a) 解析 format 字符串中的占位符(如 %d, %s)
    //   b) 根据占位符类型,从 args_ptr 指向的内存位置"取出"相应大小的数据
    //   c) 每取一个参数,自动将 args_ptr 向内存高位移动相应距离,指向下一个参数
    //   d) 循环直到所有占位符处理完毕
    
    // 5. 清理指针(防止野指针)
    va_end(args_ptr); // 通常将 args_ptr 置为 NULL
}

(4)将默认部分与自定义部分整合后调用日志打印函数

printLog

复制代码
    void printLog(int level, const std::string &logtxt) {
        switch (printMethod) {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LOGFILE, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }

日志打印函数通过打印方法,调用对应打印方法的函数

printOneFile&printClassFile

复制代码
    void printOneFile(const std::string &logname, const std::string &logtxt) {
        std::string filename = path + logname;
        int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666); // log.txt
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }

    void printClassFile(int level, const std::string &logtxt) {
        std::string filename = LOGFILE;
        filename += ".";
        filename += levelToString(level); // log.txt./Info/Debug.......
        printOneFile(filename, logtxt);
    }

(1)向一个文件打印所有日志信息时,首先为默认文件名添加路径(让日志信息处于新建目录中),然后调用系统调用open打开文件并写入

(2)分类打印日志消息,只需要为每种日志定义好文件名,然后复用向一个文件打印日志信息的代码就可

完整呈现

使用下述类时,注意确保 ./log/ 目录存在

复制代码
#pragma once

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

#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 LOGFILE "log.txt"

class Log {
public:
    Log() : printMethod(Screen) , path("./log/"){
    }

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

    void operator()(int level, const char *format, ...) {
        // 默认部分:【日志等级】【年-月-日 时:分:秒】
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%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);
        // 自定义部分
        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);
        // 整合
        char logtxt[SIZE * 2 + 1];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
        // 存储或者打印
        printLog(level, logtxt);
    }

    void printLog(int level, const std::string &logtxt) {
        switch (printMethod) {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LOGFILE, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }

    void printOneFile(const std::string &logname, const std::string &logtxt) {
        std::string filename = path + logname;
        int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666); // log.txt
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }

    void printClassFile(int level, const std::string &logtxt) {
        std::string filename = LOGFILE;
        filename += ".";
        filename += levelToString(level); // log.txt./Info/Debug.......
        printOneFile(filename, logtxt);
    }

    ~Log() {
    }

private:
    int printMethod;
    std::string path;
};
相关推荐
EmbedLinX2 小时前
一文理解后端核心概念:同步/异步、阻塞/非阻塞、进程/线程/协程
linux·服务器·c语言·网络
zhangrelay2 小时前
linux下如何通过与AI对话设置thinkpad电池充电阈值
linux·运维·笔记·学习
小王努力学编程2 小时前
LangChain——AI应用开发框架(核心组件2)
linux·服务器·c++·人工智能·python·langchain·信号
郝学胜-神的一滴3 小时前
深入理解TCP协议:数据格式与核心机制解析
linux·服务器·网络·c++·网络协议·tcp/ip
数据知道3 小时前
一文掌握 MongoDB 详细安装与配置(Windows / Linux / macOS 全平台)
linux·数据库·windows·mongodb·macos
济6173 小时前
linux 系统移植(第十三期)---Linux 内核移植(2)- CPU 主频修改--- Ubuntu20.04
linux·运维·服务器
生活很暖很治愈3 小时前
Linux基础指令——【2】
linux·服务器·后端·ubuntu
zl_dfq3 小时前
Linux 之 【进程间通信】
linux
zl_dfq4 小时前
Linux 之 【进程间通信】(匿名管道与命名管道、pipe、进程池、mkfifo、unlink)
linux