Skip to content

008_limou_2024_05_27_日志库

约 5520 字大约 18 分钟

2025-04-09

在有些情况下我们需要使用日志,一个标准的日志库应该具有以下的功能:

  1. 多级别日志支持,应支持常见的日志级别,如 DEBUG(调试信息)、INFO(一般信息)、WARN(警告)、ERROR(错误)、FATAL(致命错误)
  2. 日志过滤,能够基于日志级别过滤日志,只输出高于某一等级的日志信息。例如,在生产环境中只输出 ERROR 及以上级别的日志
  3. 日志格式化自定义格式,支持自定义日志输出格式,如时间戳格式、日志级别、消息内容、文件名、行号等信息的组合方式
  4. 支持结构化日志,可以以结构化的方式记录日志(如 JSON 格式),便于机器解析和分析
  5. 多输出渠道,例如控制台输出、文件输出、远程日志系统、日志轮转等
  6. 异步日志记录,支持异步方式记录日志,以避免同步写入对性能的影响,特别是在高并发的场景下
  7. 线程安全,日志库应能在多线程环境下安全使用,确保日志记录不丢失、不混乱

2.日志的第三方库

2.1.Boost.Log

2.1.1.Boost.Log 的下载

关于 Boost.Log 的下载,我推荐您直接下载整个 Boost 库。

# 下载 Boost 所有的开发包
$ sudo apt update
$ sudo apt install libboost-all-dev # 默认下载 1.74.0 版本的
$ dpkg -s libboost-dev | grep version # 或者使用 apt-cache policy libboost-all-dev 查看
$ sudo apt autoremove libboost-all-dev # 可以使用该指令把相关安装包进行卸载

待补充:下面文本没有经过验证,以后验证

对于那些想要亲自安装最新的 Boost C++ 库的人来说,您必须通过其源代码来安装它们。

首先访问 Boost C++ 网站并打开“下载”部分。找到“加速下载”选项,然后单击“当前版本”。

将显示您可以下载的可用当前版本的列表。右键单击“tar.gz”选项并复制其链接。您也可以单击它开始下载。

在您的终端上,使用 wget 通过终端下载文件。使用通过 wget 命令复制的链接来启动下载。

下载完成后,导航至下载目录。在这里,我们将文件移至“下载”以验证我们是否下载了包含源代码的 tar 文件。

使用以下命令提取存档文件:

$ tar xvf boost_1_81_0_tar.gz

导航到提取的“Boots”目录。

$ cd boost_1_81_0/

我们必须安装所需的依赖项来支持 Boost C++ 的安装。运行以下命令,所有依赖项将安装:

$ sudo apt install build-essential python3-dev g++ autotools-dev libicu-dev libbz2-dev -y

然后我们需要设置 Boost 的引导程序。以下命令启动 Bootstrap 脚本并构建 B2 引擎:

$ sudo ./bootstrap.sh --prefix =/usr/

您现在可以运行生成的 B2 引擎。

$ ./b2

最后,使用 B2 安装 Boost C++。

$ sudo ./b2 install

程序运行后,安装将完成,您的系统上将拥有可用的 Boost C++ 库。然后,您可以在编写 C++ 项目时调用它们。

来自连接 https://cn.linux-console.net/?p = 13955

2.1.2.Boost.Log 的使用

关于 Boost.Log 的使用,您可以 查阅 Boost.Log 的官方文档,但是这个文档有些晦涩难懂。并且使用过 Boost.Log 的新手几乎都会被这个 Boost logger linking issue 坑到。

解决方案就是将静态库和目标程序做分离编译,或者干脆直接作为一条指令。

# 尝试使用 Boost.Log 避开编译坑点
$ g++ -DBOOST_LOG_DYN_LINK -c mylog.cpp
$ g++ mylog.o -lboost_log -lpthread -o mylog
# 或者直接使用 g++ mylog.cpp -o mylog -DBOOST_LOG_DYN_LINK -lboost_log -lpthread 一条语句
# 或者直接在 CMakelist.txt 中使用 add_definitions(-DBOOST_LOG_DYN_LINK)

吐槽:我就被坑过,以后再来看看是什么原因...

先来使用最基础的宏指令 BOOST_LOG_TRIVIAL(日志等级) << "...";,这是一个启动日志记录的指令,后面的字符串是对应的日志消息。而您也有必要了解一下内部的日志等级:

  1. trace(跟踪):用于记录最为详细的调试信息,如 函数的进入和退出、循环中的变量取值等。适用于需要非常详细的调试信息、对系统执行流程进行深入了解的场景。不过这也设置日志消息时非常繁琐,使用起来几乎和某些 IDE 调试过程的输出日志毫无差异。
  2. debug(调试):用于记录一般的调试信息,如 关键变量的取值、状态变化等。适用于正常的调试过程中需要关注的重要信息,能够帮助理解系统状态和执行流程。
  3. info(信息):用于记录程序正常执行时的重要信息,如 提示启动、关闭、关键操作的成功完成等。适用于记录程序的运行状态、关键事件的发生以及执行流程的重要节点。
  4. warning(警告):用于记录可能会导致问题或错误的情况,但不会影响系统继续运行的信息。适用于标识一些潜在的问题或异常情况,需要引起关注但不会导致系统崩溃或故障。
  5. error(错误):用于记录发生了错误但程序仍能够继续执行的情况,如 某个功能无法正常工作、某个文件无法打开等。适用于记录导致程序功能受损或部分失败的错误情况。
  6. fatal(致命):用于记录导致程序无法继续执行的严重错误,如 内存分配失败、关键组件初始化失败等。适用于记录程序遇到无法恢复的致命错误,需要立即引起开发人员的关注。

这个宏输消息中,包含用户自定义的日志消息、时间戳、当前线程标识(以十六进制地址形式给出)、严重性级别。并且 Boost.Log 保证写入日志是线程安全的。

吐槽:但是 Boost.Log 有些难用,我个人推荐使用 Spdlog...

2.2.Spdlog

Spdlog 非常强大,可以是 C++ 的最佳日志库,支持以下功能:

  • 非常快(见 基准测试
  • 仅标头和已编译,使用简单
  • 使用出色的 fmt 库,所以有功能丰富的格式,高可自定义格式
  • 可选的日志异步模式,多线程/单线程记录器以及多种日志目标
  • 动态支持日志过滤,可以在运行时和编译时修改日志级别,也支持从 argvenvironment var 加载日志级别。
  • 回溯支持,将调试消息存储在环形缓冲区中,并在以后根据需要显示它们,可在性能损失小的情况下避免频繁 IO 操作
  • 支持 trace, debug, info, warn, error, critical, off7 种日志级别,可以非常方便过滤不同级别的日志
  • 跨平台,支持 Linux、Windows、MacOS 等多个平台
  • 社区活跃,反响较好

2.2.1.Spdlog 的下载

关于 Spdlog 的下载,我推荐您直接使用包管理器 sudo apt install libspdlog-dev 来下载,或者获取 前往 github 使用 cmake 进行构建获取(不过删除也比较麻烦...)。

# 包管理安装 Spdlog
sudo apt install libspdlog-dev
# 构建安装 Spdlog 
$ git clone https://github.com/gabime/spdlog.git
$ cd spdlog && mkdir build && cd build
$ cmake ..
$ sudo install spdlog # 安装到系统的安装目录下
$ ls /usr/local/include/ | grep "spdlog" # 检查是否安装成功对应的头文件

2.2.2.Spdlog 的使用

稍微测试一下 在 README.md 里的例子(有删改)或者 前往对应的 github wiki,看看是否安装顺利,顺便告诉您常见的使用方法。

2.2.2.1.输出基本的消息

# 尝试使用 Spdlog
$ cat slog.cpp 
#include <spdlog/spdlog.h>

int main() 
{
    spdlog::info("欢迎来到 spdlog!");
    spdlog::error("一些 error 消息和参数: {}", 1);
    
    spdlog::warn("很容易填充数字,比如 {:08d}", 12);
    spdlog::critical("支持 int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
    spdlog::info("支持 floats {:03.2f}", 1.23456);
    spdlog::info("支持位置参数 {1} {0}..", "argv0", "argv1");
    spdlog::info("{:<30}", "支持对齐(这里是左对齐)");
    
    spdlog::set_level(spdlog::level::debug); // 设置全局日志级别为debug
    spdlog::debug("应该显示此消息..");    
    
    // 更改日志格式
    spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
    
    // 也可以使用另外一种风格的宏日志, 不过我个人不推荐...
    spdlog::trace("一些 trace 消息和参数 {}", 42);
    SPDLOG_TRACE("一些 trace 消息和参数 {}", 42);
    SPDLOG_DEBUG("一些 debug 消息");
    SPDLOG_INFO("一些 info 消息");
    SPDLOG_CRITICAL("一些 critical 消息");

    return 0;
}

$ g++ slog.cpp -lspdlog -lpthread
$ ./a.out 
# 尝试使用 Spdlog
$ cat slog.cpp 
#include <spdlog/spdlog.h>

int main() 
{
    spdlog::info("欢迎来到 spdlog!");
    spdlog::error("一些 error 消息和参数: {}", 1);
    
    spdlog::warn("很容易填充数字,比如 {:08d}", 12);
    spdlog::critical("支持 int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
    spdlog::info("支持 floats {:03.2f}", 1.23456);
    spdlog::info("支持位置参数 {1} {0}..", "argv0", "argv1");
    spdlog::info("{:<30}", "支持对齐(这里是左对齐)");
    
    spdlog::set_level(spdlog::level::debug); // 设置全局日志级别为debug
    spdlog::debug("应该显示此消息..");    
    
    // 更改日志格式
    spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
    
    // 也可以使用另外一种风格的宏日志, 不过我个人不推荐...
    spdlog::trace("一些 trace 消息和参数 {}", 42);
    SPDLOG_TRACE("一些 trace 消息和参数 {}", 42);
    SPDLOG_DEBUG("一些 debug 消息");
    SPDLOG_INFO("一些 info 消息");
    SPDLOG_CRITICAL("一些 critical 消息");

    return 0;
}

$ g++ slog.cpp -lspdlog -lpthread
$ ./a.out 
[2024-05-28 01:04:35.802] [info] Welcome to spdlog!
[2024-05-28 01:04:35.802] [error] Some error message with arg: 1
[2024-05-28 01:04:35.802] [warning] Easy padding in numbers like 00000012
[2024-05-28 01:04:35.802] [critical] Support for int: 42;  hex: 2a;  oct: 52; bin: 101010
[2024-05-28 01:04:35.802] [info] Support for floats 1.23
[2024-05-28 01:04:35.802] [info] Positional args are supported too..
[2024-05-28 01:04:35.802] [info] left aligned                  
[2024-05-28 01:04:35.802] [debug] This message should be displayed..
[01:04:35 +08:00] [] [---I---] [thread 652737] Some info message

2.2.2.2.创建日志记录器

Spdlog 库提供了几种不同的日志记录对象,可以根据需要选择合适的日志记录器类型。

  1. 控制台日志记录器(spdlog/sinks/stdout_sinks.h

    • spdlog::stdout_logger_mt:标准输出日志记录器,输出到控制台

    • spdlog::stderr_logger_mt:标准错误输出日志记录器,输出到控制台

    • spdlog::stdout_color_mt:彩色标准输出日志记录器,以颜色区分不同级别的日志消息,并输出到控制台

    • spdlog::stderr_color_mt:彩色标准错误输出日志记录器,以颜色区分不同级别的日志消息,并输出到控制台

  2. 文件日志记录器(spdlog/sinks/basic_file_sink.h、spdlog/sinks/rotating_file_sink.h、spdlog/sinks/daily_file_sink.h

    • spdlog::basic_logger_mt:基本文件日志记录器,将日志消息写入到文件中
    • spdlog::rotating_logger_mt:带有日志文件轮转功能的日志记录器,可以限制日志文件大小并自动进行轮转(带有日志文件轮转功能的日志记录器是指能够在日志文件大小达到一定阈值时,自动创建新的日志文件,并将旧的日志文件重命名或移动到备份目录,从而避免单个日志文件过大,提高了日志文件的可读性和管理性)
    • spdlog::daily_logger_mt:每日生成一个新的日志文件的日志记录器
  3. 系统日志记录器(spdlog/sinks/syslog_sink.h

    • spdlog::syslog_loggersyslog 日志记录器,将日志消息发送到系统日志守护进程里。syslogUnix 和类 Unix 操作系统中用于记录系统事件的标准方法,syslogd 是负责接收、记录和处理系统日志消息的守护进程,这个记录器可以方便日志消息与系统级别的日志进行统一管理和监控

下面展示记录器的使用方法。

# 使用控制台日志记录器
$ cat slog.cpp 
// 使用基本日志记录器
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>

int main()
{
    // 创建一个彩色标准输出日志记录器
    auto console = spdlog::stdout_color_mt("console"); // 默认日志等级为 info

    // 输出不同级别的日志消息
    console->trace("This is a trace message");  // 这条日志不会被输出
    console->debug("This is a debug message");  // 这条日志不会被输出
    console->info("This is an info message");    // 这条日志会被输出
    console->warn("This is a warning message");  // 这条日志会被输出
    console->error("This is an error message");  // 这条日志会被输出
    
    // 重置日志等级后在输出日志
    console->set_level(spdlog::level::trace);
    console->trace("This is a trace message");  // 这条日志会被输出
    console->debug("This is a debug message");  // 这条日志会被输出
    console->info("This is an info message");    // 这条日志会被输出
    console->warn("This is a warning message");  // 这条日志会被输出
    console->error("This is an error message");  // 这条日志会被输出
    
    auto err_logger = spdlog::stderr_color_mt("stderr");    
    spdlog::get("stderr")->debug("some debug message"); // 这会输出到 stderr
    spdlog::get("stderr")->info("some info message"); // 这会输出到 stderr
    
    return 0;
}

$ g++ slog.cpp -lpthread
$ ./a.out 
[2024-05-28 11:47:38.953] [console] [info] This is an info message
[2024-05-28 11:47:38.953] [console] [warning] This is a warning message
[2024-05-28 11:47:38.953] [console] [error] This is an error message
[2024-05-28 11:47:38.953] [console] [trace] This is a trace message
[2024-05-28 11:47:38.953] [console] [debug] This is a debug message
[2024-05-28 11:47:38.953] [console] [info] This is an info message
[2024-05-28 11:47:38.953] [console] [warning] This is a warning message
[2024-05-28 11:47:38.953] [console] [error] This is an error message
[2024-05-28 11:47:38.953] [stderr] [info] some info message
    
$ ./a.out 2>&1 1>/dev/null # 只要 stderr 的输出
[2024-05-28 11:48:00.702] [stderr] [info] some info message
# 使用文件的日志记录器
$ cat slog.cpp 
#include <spdlog/sinks/basic_file_sink.h>
int main()
{
    // 创建基本文件记录器
    auto basic_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt");
    basic_logger->debug("This is a debug message");  // 这条日志不会被输出到 logs/basic-log.txt
    basic_logger->info("This is an info message");    // 这条日志会被输出到 logs/basic-log.txt
    basic_logger->warn("This is a warning message");  // 这条日志会被输出到 logs/basic-log.txt
    basic_logger->error("This is an error message");  // 这条日志会被输出到 logs/basic-log.txt
    
    // 创建一个最大大小为 5 MB的文件旋转记录器和 3 个旋转文件的旋转记录器
    auto max_size = 1048576 * 5; // 最大大小
    auto max_files = 3; // 最大文件个数
    auto some_logger_name = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", max_size, max_files); // 这个您可以自己测试一些, 测试起来有点麻烦我就不演示了...
    
    return 0;
}

$ g++ slog.cpp -lpthread
$ ./a.out 

$ ls
logs   slog.cpp

$ cd logs/

/logs$ ls
basic-log.txt

/logs$ cat basic-log.txt # 也是默认日志等级为 info
[2024-05-28 11:55:33.086] [basic_logger] [info] This is an info message
[2024-05-28 11:55:33.086] [basic_logger] [warning] This is a warning message
[2024-05-28 11:55:33.086] [basic_logger] [error] This is an error message
# 使用每日的日志记录器
$ cat slog.cpp 
#include "spdlog/sinks/daily_file_sink.h"
int main()
{
    // 创建一个每日日志——每天凌晨 2:30 创建一个新文件
    auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
    return 0;
}
# 演示起来也有点麻烦, 这里我也偷个懒...

2.2.2.3.使用环形缓冲区

# 使用环形缓冲区
$ cat slog.cpp
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_sinks.h>
#include <unistd.h>

int main() {
    // 创建一个名为 "example" 的日志记录器
    auto logger = spdlog::stdout_logger_mt("example");

    // 启用回溯功能,并设置缓冲区大小为 100
    logger->enable_backtrace(100);

    // 生成一些调试消息,但它们不会立即被记录到日志中
    int i = 0;
    for (; i < 20; i++) {
        logger->debug("Backtrace message {}", i);
    }

    // 生成一些调试消息,但它们不会立即被记录到日志中
    for (; i < 40; i++) {
        logger->debug("Backtrace message {}", i);
    }
    
    // 需要时再将缓冲区中的多条消息记录到日志中
    sleep(3);
    logger->dump_backtrace(); // 刷新转储在环形队列中的所有日志消息(如果有溢出就会导致日志消息溢出)

    return 0;
}

$ g++ slog.cpp
$ ./a.out
# 这里被阻塞了 3 秒然后才开始输出
[2024-05-28 14:23:58.480] [example] [info] ****************** Backtrace Start ******************
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 0
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 1
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 2
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 3
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 4
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 5
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 6
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 7
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 8
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 9
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 10
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 11
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 12
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 13
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 14
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 15
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 16
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 17
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 18
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 19
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 20
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 21
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 22
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 23
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 24
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 25
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 26
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 27
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 28
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 29
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 30
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 31
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 32
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 33
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 34
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 35
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 36
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 37
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 38
[2024-05-28 14:23:55.480] [example] [debug] Backtrace message 39
[2024-05-28 14:23:58.480] [example] [info] ****************** Backtrace End ********************
# 使用定时刷新策略
$ cat slog.cpp
#include <iostream>
#include <chrono>
#include <thread>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
int main() {
    auto logger = spdlog::basic_logger_mt("console", "logs/log.txt");
    spdlog::flush_every(std::chrono::seconds(5)); // 5 秒刷新一次

    int count = 100;
    while (count--) {
        auto now = std::chrono::system_clock::now();
        auto now_c = std::chrono::system_clock::to_time_t(now);
        logger->info("当前时间: {}", std::ctime(&now_c));
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    return 0;
}
# 这里我也偷个懒, 您可以查看一下这个代码运行后, 日志文件的内部结果

2.3.MyLog

其实就是自己手搓一个,我搓了一个简单的。

// 自定义日志库
/*!
 * @file
 * @brief	项目的日志打印工具
 * @author  limou3434
 * @date    2024-05-11
 */

#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <mutex>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

namespace CouldBackup {
    // 日志级等级
    const int DEBUG = 0;    // 调试
    const int INFO = 1;     // 正常
    const int WARNING = 2;  // 警告
    const int ERROR = 3;    // 错误
    const int FATAL = 4;    // 致命

    // 日志打印模式
    enum WriteMode {
        SCREEN = 5,
        ONE_FILE,
        CLASS_FILE
    };

    /*!
	 * @brief   管理日志的类
	 */
    class Log {
    public:
        // 日志等级和日志字符的映射数组
        // TODO: 有时间研究一下, 看看这里的数组无法使用类内 const 变量的原因
        const char* gLevelMap[5] = {
            "debug",    //debug 模式
            "info",   //正常
            "warning",  //警告
            "error",    //非致命错误
            "fatal"     //严重错误
        };
        const std::string logdir = "log_dir"; //日志文件名

        
    private:
        // 日志信息统一写入文件
        void _writeLogToOneFile(std::string logFileName, const std::string& message) const {
            std::ofstream out(logFileName, std::ios::app);
            if (!out.is_open())
                return;
            out << message;
            out.close();
        }

        // 日志信息分类写入文件
        void _writeLogToClassFile(const int& level, const std::string& message) const {
            std::string logFileName = "./";
            logFileName += logdir;
            logFileName += "/";
            logFileName += _logFileName;
            logFileName += "_";
            logFileName += gLevelMap[level];

            _writeLogToOneFile(logFileName, message);
        }

        // 按照不同模式输出日志
        void _writeLog (const int& level, const std::string& message) const {
            switch (_writeMode) {
            case SCREEN: //向屏幕输出
                std::cout << message;
                break;
            case ONE_FILE: //向单个日志文件输出
                _writeLogToOneFile("./" + logdir + "/" + _logFileName, message);
                break;
            case CLASS_FILE: //向多个日志文件输出
                _writeLogToClassFile(level, message);
                break;
            default:
                std::cout << "write mode error!!!" << std::endl;
                break;
            }
        }

        
    public:
        // 构造函数, debugShow 为是否显示 debug 消息, writeMode 为日志打印模式, logFileName 为日志文件名
        Log(bool debugShow = true, const WriteMode& writeMode = SCREEN, std::string logFileName = "log")
            : _debugShow(debugShow)
            , _writeMode(writeMode)
            , _logFileName(logFileName) {
            mkdir(logdir.c_str(), 0775); // 创建目录
        }

        // TODO: 有时间研究一下, 这里如果有静态成员, 使用 default 的赋值重载貌似无效
        // 拷贝构造, 允许拷贝传递
        Log(const Log& other) = default;

        // 赋值重载, 允许赋值(注意类的静态成员没有被重载)
        Log& operator=(const Log& other) { // TODO: 这点值得补充学习, 赋值重载的语义就导致 *this 对象无法被赋值, 因此就不应该成为 const 函数
            if (this != &other) {
                this->_debugShow = other._debugShow;
                this->_writeMode = other._writeMode;
                this->_logFileName = other._logFileName; 
            }
            return *this;
        }

        // 调整日志打印方式
        void adjustment(bool debugShow = true, const WriteMode& writeMode = SCREEN, std::string logFileName = "log") {
            //申请智能锁, 防止多线程情况下, 在日志对象修改属性期间输出日志信息
            std::lock_guard<std::mutex> lock(_mtx);
            _debugShow = debugShow;
            _writeMode = writeMode;
            _logFileName = logFileName;
        }

        // 拼接日志消息并且输出, level = DEBUG | INFO | WARNING | ERROR | FATAL
        void logMessage (const int& level, const char* format, ...) const {
            // 申请智能锁, 防止多线程情况下, 日志消息混乱
            std::lock_guard<std::mutex> lock(_mtx);

            // 1.若不是 debug 模式, 且 level == DEBUG 则不做任何事情
            if (_debugShow == false && level == DEBUG)
                return;

            // 2.收集日志标准部分信息
            char stdBuffer[1024];
            time_t timestamp = time(nullptr); //获得时间戳
            struct tm* local_time = localtime(&timestamp); //将时间戳转换为本地时间

            snprintf(stdBuffer, sizeof stdBuffer, "[%s][pid:%s][%d-%d-%d %d:%d:%d]",
                gLevelMap[level],
                std::to_string(getpid()).c_str(),
                local_time->tm_year + 1900, local_time->tm_mon + 1, local_time->tm_mday,
                local_time->tm_hour, local_time->tm_min, local_time->tm_sec
            );

            // 3.收集日志自定义部分信息
            char logBuffer[1024];
            va_list args; //声明可变参数列表, 实际时一个 char* 类型
            va_start(args, format); //初始化可变参数列表
            vsnprintf(logBuffer, sizeof logBuffer, format, args); //int vsnprintf(char *str, size_t size, const char *format, va_list ap); 是一个可变参数函数, 将格式化后的字符串输出到缓冲区中。类似带 v 开头的可变参数函数有很多
            va_end(args); //清理可变参数列表, 类似 close() 和 delete

            // 4.拼接为一个完整的消息
            std::string message;
            message += "--> 标准日志:"; message += stdBuffer;
            message += "\t 用户日志:"; message += logBuffer;
            message += "\n";

            // 5.打印日志消息
            _writeLog(level, message);
        }

        
private:
        bool            _debugShow;     //> 是否显示 DEBUG 的信息
        WriteMode       _writeMode;     //> 日志显示模式
        std::string     _logFileName;   //> 日志文件名称
        static std::mutex      _mtx;    //> 全局日志互斥锁
    }; // class Log end
    std::mutex Log::_mtx; // 静态成员定义, 相当于全局类域内的锁, 让项目的所有日志输出都原子化
} // namespace CouldBackup end

更新日志

2025/11/24 07:31
查看所有更新日志
  • 0843b-修改语言目录结构,同时归纳新的语言目录
  • 6fa6e-迁移所有有效的文章
  • 01111-保存所有的学习
  • 7ffa6-学习使用 Sentinel、Sa-token、Nacos、ES
  • 1a85c-临时迁移文件,开始部署博客
  • b7955-修改资源目录
  • 1d7f9-数据备份
  • 9c3ea-数据备份

本站公告

本网站正在不断升级中,若有 bug 可以在本开发者的对应项目下提交 issues