“深入浅出”系列之C++:(36)日志

spdlog是一款追求速度和性能的日志库。采用先进的异步日志处理技术,能够在不影响主程序性能的前提下,实现高效、实时的日志记录。同时,spdlog还提供了丰富的日志级别和灵活的日志格式设置,满足不同场景下的日志记录需求。 spdlog为header only的日志库,无需编译,只需添加到项目中即可。如果需要查看工程中的examples时,需要使用cmake进行编译。spdlog的主要特性如下:

高性能:采用异步日志处理技术,将日志记录操作与主程序运行分离,从而避免了日志记录对程序性能的影响。

灵活的日志级别:支持多种日志级别,如DEBUG、INFO、WARN、ERROR等,方便开发者根据不同的需求选择合适的日志级别。

可定制的日志格式:开发者可以根据自己的喜好和项目需求,定制个性化的日志格式,使日志输出更加清晰易读。

强大的扩展性:SPDLOG支持多种输出目标,如文件、控制台、网络等,同时还可以通过插件机制实现与其他系统的集成。

跨平台:在几乎所有支持C++编译器的平台上运行

spdlog地址:https://github.com/gabime/spdlog

spdlog::set_level(spdlog::level::debug);
spdlog::default_logger()->debug("hello world");
auto console_sink=std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::warn);
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true);
file_sink->set_level(spdlog::level::debug);
spdlog::logger logger("multi_sink",{console_sink,file_sink});
logger.set_level(spdlog::level::debug);
logger.warn("this should appear in both console and file");
logger.info("this message should not appear in the console, only in the file");
spdlog::set_default_logger(logger);
auto roating_logger=spdlog::rotating_logger_mt("logger","logs/rotating.txt",1048576*5,3);
auto daily_logger=spdlog::daily_logger_mt("daily_logger","logs.daily.txt",2,30);
spdlog::default_logger()->debug("{}","hello world");
spdlog::default_logger()->debug("{0},{1},{2}",'a','b',c');//Result:"a,b,c"
spdlog::default_logger()->debug("{},{},{}",'a','b','c');//Result:"a,b,c"
spdlog::default_logger()->debug("{2},{1},{0}",'a','b','c');//Result:"c,b,a"
spdlog::default_logger()->debug("{0}{1}{0}","abra", "cad"); // arguments'indices can be repeated
spdlog::default_logger()->debug("{:<30}","left aligned");//Result:"left aligned
spdlog::default_logger()->debug("{:>30}","right aligned");//Result:"right aligned"
spdlog::default_logger()->debug("{:^30}","centered");// Result:"centered
spdlog::default_logger()->debug("{:*^30}","centered");//use'*'as a fill char
//Result:"**********centered*********"
using namespace fmt::literals;
spdlog::default_logger()->debug("Elapsed time: {s:.2f} seconds","s" a=1.23);

 在软件开发领域,日志记录是一项至关重要的工作。它不仅是程序运行情况的实时记录者,更是故障排查和系统优化的关键依据。然而,随着项目规模的扩大和复杂性的增加,传统的日志管理方式已经难以满足开发者的需求。这时候,一个高效、灵活的日志库就显得尤为重要。

#include "spdlog/sinks/stdout_color_sinks.h"
// or #include "spdlog/sinks/stdout_sinks.h" if no colors needed.
void stdout_logger_example() {
    // Create color multi threaded logger.
    auto console = spdlog::stdout_color_mt("console");
    // or for stderr:
    // auto console = spdlog::stderr_color_mt("error-logger");
    console->debug(__FUNCTION__);
    console->info(__FUNCTION__);
}

int main()
{
    spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] \t[%P/%t] %v");
    spdlog::set_level(spdlog::level::debug);
    stdout_logger_example();
}
/*
output:
[2024-03-03 13:48:31.101] [debug]       [17564/27436] stdout_logger_example
[2024-03-03 13:48:31.102] [info]        [17564/27436] stdout_logger_example
*/

    由于内部工厂函数不可见,导致看到如上代码时,并不能理解到spdlog的架构。整体架构如下

图片

1. spdlog拥有全局唯一的管理器(registry),registry用于管理和维护所有的logger实例。它提供了一系列的函数来创建、获取、删除logger实例,并支持对logger的全局配置。

2. 管理器中可以有多个日志记录器(logger/async_logger),日志记录器分为两类:同步日志记录和异步日志记录,logger是spdlog中的基本组件,用于记录日志消息。它提供了一系列的日志记录函数(如debug()、info()、error()等),以及设置日志级别、格式化输出等功能。logger可以输出到多个sink(日志记录器),如控制台、文件、syslog等。async_logger是从logger派生而来的一种特殊logger,用于实现异步的日志记录功能。它使用异步队列来缓冲日志消息,并通过后台线程将消息写入到指定的输出目标中,以提高性能和响应速度。

3. spdlog内有多个种类的日志记录器,日志记录器负责将日志消息发送到指定的输出目标。spdlog提供了多种内置的Sink,如stdout_sink、rotating_file_sink等,以支持不同的日志输出方式。开发者也可以自定义Sink,以满足特定的日志记录需求。

4. 日志格式化器用于将日志消息格式化为指定的字符串形式。可以通过为每个日志记录器(sink)自定义独有/共有的日志格式化器来定制日志消息的输出格式,包括时间格式、日志级别、线程ID等信息。

5. 异步日志记录线程池,负责从异步队列中取出日志消息,并将其写入到指定的输出目标中。它与async_logger配合工作,实现了异步的日志记录功能。。

场景:在sdk开发中,sdk中的日志不仅需要保存到本地,同时希望通过回调函数的形式,将日志返回给sdk的调用者。日志信息形如“[年-月-日 时:分:秒.毫秒][日志等级][进程id/线程id] message”

分析如上场景可知,需要两个日志记录器,一个用于书写文件,一个用于回调;需要设置日志的格式化器。

常用的日志的格式化器标识符

flag释义
%Y四位数的年
%m
%d
%H
%M
%s
%e毫秒
%n日志记录器的名字
%l日志等级
%L日志等级(短)
%P进程ID
%t线程ID
%v待输出的日志消息

例:"[%Y-%m-%d %H:%M:%S.%e] [%P/%t] [%l] %v"

自定义回调类型的日志记录器

#ifndef MEMORYSINK_H
#define MEMORYSINK_H
#include<mutex>
#include "spdlog/sinks/base_sink.h"
#include "spdlog/details/log_msg.h"

namespace spdlog {

using memory_cb = std::function<void(const std::string&, size_t size)>;
namespace sinks{
 
template<typename Mutex>
class memory_sink final :public base_sink<Mutex> {
public:
    explicit memory_sink(memory_cb callback) : callback_(callback) {}
protected:
    void sink_it_(const spdlog::details::log_msg& msg) override {

        spdlog::memory_buf_t formatted;
        base_sink<std::mutex>::formatter_->format(msg, formatted);
        size_t msg_size = formatted.size();
        auto data = formatted.data();
        std::string clean_log_msg(data, msg_size);
        auto level = msg.level;

        clean_log_msg.erase(std::remove_if(clean_log_msg.begin(), clean_log_msg.end(), [](unsigned char c) { return !isprint(c); }), clean_log_msg.end());

        if (callback_) {
            std::stringstream ss;
            ss << clean_log_msg;
            callback_(ss.str(), msg_size);
        }
    }

    void flush_() override {}

private:
   memory_cb callback_;
};

using memory_sink_mt = memory_sink<std::mutex>;
using memory_sink_st = memory_sink<details::null_mutex>;

}//namespace sinks
}//namespace spdlog

#endif//MEMORYSINK_H

使用多个日志记录器的样例代码

int main()
{
    auto memory_sink = std::make_shared<spdlog::sinks::memory_sink_mt>(
        [](const std::string &log_message, size_t data_size) {
            std::cout << log_message << "\n";  // 此处简写,本应为回调函数给调用方
        });
    auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("d://Garden_for_programmer.txt", true);
    auto m_spdlog_log = std::make_shared<spdlog::logger>(
        "wanos_log", spdlog::sinks_init_list{file_sink, memory_sink});
    m_spdlog_log->set_level(spdlog::level::debug);
    m_spdlog_log->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%P/%t] [%l] %v");
    spdlog::register_logger(m_spdlog_log);//注册到管理类中

    m_spdlog_log->debug("hello world");
    m_spdlog_log->info("this is an info log");
}

 15. spdlog

spdlog 是一个现代 C++ 日志库,提供了高性能、多线程安全的日志记录功能。它支持多种日志级别、多种日志输出目标(如控制台、文件、网络等),并且易于集成到现有的 C++ 项目中。spdlog 提供了简洁的 API 和灵活的配置选项,使得开发者能够轻松地记录和管理应用程序的日志信息。

15.1 使用场景

  • 应用程序日志:在开发过程中记录调试信息、警告和错误信息,以便快速排查和修复问题。

  • 性能分析:记录程序的运行时间、内存占用等性能指标,用于分析和优化程序性能。

  • 数据采集:将程序输出的数据记录到日志文件中,用于后续分析和处理。

  • 分布式系统:在分布式系统中记录节点间的通信和状态信息,用于监控和故障排查。

15.2 代码示例

以下是一个经典的使用 spdlog 的简单示例,演示了如何在 C++ 项目中记录日志信息到控制台和文件:

#include <iostream>
#include "spdlog/spdlog.h"

int main() {
    // 初始化日志记录器,创建一个控制台和一个文件输出目标
    auto console_logger = spdlog::stdout_logger_mt("console");
    auto file_logger = spdlog::basic_logger_mt("file", "example.log");

    // 设置日志级别
    console_logger->set_level(spdlog::level::debug);
    file_logger->set_level(spdlog::level::info);

    // 记录日志信息
    console_logger->info("Logging to console");
    file_logger->debug("Logging to file");

    return 0;
}

在这个例子中:

  • 我们首先包含了 spdlog 头文件,并创建了一个控制台和一个文件输出目标的日志记录器。

  • 使用 set_level 方法设置了日志记录器的日志级别,控制台日志记录器的级别为 debug,文件日志记录器的级别为 info。

  • 使用 info 和 debug 方法记录了一些日志信息,分别输出到控制台和文件中。

总结

    spdlog是一个功能强大且易于使用的C++日志库,它具有高性能、灵活性和线程安全等优点。同时,支持自定义日志记录器,极大的方便用户,且扩展了spdlog的使用场景。

高频交易中常用的日志库——Quill

日志作为软件中一个非常重要的模块,开发者总是希望记录的信息越全越好,记录日志使用的性能越少越好。所以出现了很多优秀的日志库,比如spdlog、log4cpp、log4cplus等。

Quill是一个高性能的C++日志库,它提供了强大的日志记录功能和灵活的配置选项。其主要特性如下:

  • 高性能:Quill采用了异步日志处理机制,将日志的格式化和写入操作交给后台线程来完成,这样就不会阻塞主线程的执行。这种设计使得程序的响应速度不受日志记录的影响,保证了应用程序的高性能。特别是在高并发和实时性要求较高的场景下,Quill的优势尤为明显。

  • 异步处理:Quill的异步处理机制是其核心特性之一。它通过后台线程来处理日志的格式化和I/O操作,确保主线程始终保持响应式,不会因为日志记录而产生延迟。这种设计不仅提高了性能,还使得日志记录变得更加灵活和高效。

  • 最小头文件包含:Quill的设计非常注重轻量化,前端仅需要包含Logger.hLogMacros.h两个头文件,用于日志记录。这种最小化头文件依赖的设计,使得Quill非常易于集成到现有项目中,减少了不必要的复杂性和依赖项。

  • 灵活的日志级别:Quill支持多种日志级别,包括DEBUG、INFO、WARNING、ERROR和FATAL。开发者可以根据需要选择合适的日志级别,以便在开发和生产环境中进行不同的日志记录。这种灵活的日志级别设置,使得日志管理更加方便和高效。

  • 丰富的日志格式:Quill提供了丰富的日志格式选项,包括时间戳、线程ID、日志级别、文件名、行号等。开发者可以根据需要自定义日志格式,以满足不同的日志记录需求。这种灵活的日志格式设置,使得日志输出更加清晰和易于分析。其支持的字段见后文

  • 编译时优化:Quill支持在编译时消除特定的日志级别,这意味着开发者可以根据不同的场景裁剪日志级别,从而减少不必要的开销。这种编译时优化不仅提高了性能,还使得日志记录更加灵活和高效。

  • 时间戳排序:在多线程应用程序中,日志的顺序对于调试至关重要。Quill能够确保日志按时间顺序排列,简化了多线程环境下的调试过程。这种时间戳排序的特性,使得开发者可以更方便地追踪事件的顺序和流程。

  • 日志过滤:Quill支持日志过滤功能,开发者可以根据需要只处理相关的日志消息。这种过滤机制不仅提高了日志记录的效率,还减少了不必要的日志信息,使得日志更加聚焦和有用。

Quill支持输出的字段有:

格式化选项

中文解释

%(time)

表示日志语句创建时的人类可读时间戳。

%(file_name)

发出日志调用的源文件的文件名。

%(full_path)

发出日志调用的源文件的完整路径。

%(caller_function)

包含日志调用的函数的名称。

%(log_level)

日志消息的日志级别的文本表示。

%(log_level_short_code)

缩写的日志级别名称。

%(line_number)

发出日志调用的源文件中的行号。

%(logger)

用于记录日志调用的日志记录器的名称。

%(message)

日志消息本身。

%(thread_id)

发出日志调用的线程的ID。

%(thread_name)

线程的名称。必须在该线程上的第一个日志语句之前设置。

%(process_id)

发出日志调用的进程的ID。

%(source_location)

作为单个字符串的完整源文件路径和行号。

%(short_source_location)

作为单个字符串的缩短后的源文件名和行号。

%(tags)

当使用_TAGS宏时附加到消息上的其他自定义标签。

%(named_args)

附加到消息上的键值对。仅在消息具有命名参数时适用;否则为空。

安装

Quill具有最小头包含的特性,所以只需要包含头文件即可使用。

https://github.com/odygrd/quill.git下载源码,然后将其添加到你的项目中。

展示如何创建日志记录器并记录日志:

#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/ConsoleSink.h"
#include "quill/sinks/FileSink.h"

int main()
{
    quill::Backend::start();

    auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("console_sink");
    auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>("d://file_sink.log");

    quill::Logger* uniform_logger = quill::Frontend::create_or_get_logger(
        "uniform_logger",
        {console_sink, file_sink},
        quill::PatternFormatterOptions{
            "%(time)[%(process_id)/%(thread_id)] [%(log_level)] %(message)",
            "%Y_%M_%d %H:%M:%S:%Qms",
            quill::Timezone::GmtTime
        });
    uniform_logger->set_log_level(quill::LogLevel::Debug);    
    LOG_DEBUG(uniform_logger, "This is a debug message that only goes to file");
    LOG_INFO(uniform_logger, "This is an info message that goes to console {}", 123);
    LOG_INFO(uniform_logger, "This is an info message that goes to file {}", 456);

    quill::Backend::stop();
    return0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值