spdlog高性能日志系统

news2024/12/26 23:29:02

spdlog高性能日志系统

spdlog 是一个快速、简单、功能丰富的 C++ 日志库,专为现代 C++ 开发设计。它支持多种日志后端(如控制台、文件、syslog 等),并提供灵活的格式化和线程安全的日志输出。

1. 特点

  • 极高的性能:大量的编译时运算、使用fmt库提高格式化打印性能

  • 零成本的抽象:通过模板和内联函数,将运算放到编译时

  • 支持异步日志和同步日志

2. 问题

  1. 多线程使用日志库,跟同步和异步是否有关联?

    没有关联。多线程指的是日志使用者同时有多个,而同步和异步指的是打印日志的方式。

    在多线程情况下,如果往同一个文件中输出日志,日志库需要考虑线程安全,包括日志写入操作的线程安全和异步方式下日志消息队列的线程安全。

  2. 同一个线程处理,是不是就是同步?

    不一定。例如在协程中,IO操作是在同一个线程中处理的,但是中间发生了协程上下文切换,等epoll发出事件通知后才继续处理,所以是异步的。

3. 输出控制

3.1 多种日志级别

trace、debug、info、warn、error和critical

不同日志级别反应日志信息的不同重要程度。

最低日志级别:低于最低日志级别的日志将不会被打印。

3.2 多种输出目标

控制台、文件、通过网络发送到远程服务器等。

3.3 格式化输出

使用fmt进行格式化输出,比C++标准库和snprintf等性能高30%。

4. spdlog处理流程

在这里插入图片描述

日志时间乱序问题

如果是写入文件中,可以用命令行工具排序。如果输出到数据库,可以使用索引。

4.1 registry

使用了单例模式

SPDLOG_INLINE registry &registry::instance() {
    static registry s_instance;
    return s_instance;
}

registry中有一个thread_pool,负责异步写入日志,里面包含一个多生产者多消费者的阻塞队列。

4.2 logger

class SPDLOG_API logger {
public:
    void log();
protected:
    ...
	virtual void sink_it_(const details::log_msg &msg);
    virtual void flush_();
    ...
}

logger的sink_it_会调用所有sinks的log方法,后者会调用其自身的sink_it_方法,sink_it_会调用flush_方法。

4.3 sink

template <typename Mutex>
class basic_file_sink final : public base_sink<Mutex> {
public:
    explicit basic_file_sink(const filename_t &filename,
                             bool truncate = false,
                             const file_event_handlers &event_handlers = {});
    const filename_t &filename() const;
    void truncate();

protected:
    void sink_it_(const details::log_msg &msg) override;
    void flush_() override;

private:
    details::file_helper file_helper_;
};

主要是重写sink_it_和flush_两个方法。

5. spdlog的使用

5.1 创建logger

5.1.2 工厂方法创建

工厂方法

工厂方法是把创建对象的接口抽象出来,让子类负责创建具体的产品对象。一般当产品类的创建流程比较复杂、产品类的依赖关系比较复杂或者客户没有必要知道创建哪个具体的产品类时可以使用此设计模式。

工厂方法:

在这里插入图片描述

调用这个工厂方法来创建logger对象。

在这里插入图片描述

异步工厂的create

在这里插入图片描述

工厂方法的目的是方便对象的创建,尤其是当对象具有复杂的创建流程与依赖关系时。

#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/async.h>

int main()
{
    spdlog::info("default setting");

    // 工厂方法创建logger
    auto logger1 = spdlog::basic_logger_mt("sync_logger", "basic.txt");
    auto logger2 = spdlog::basic_logger_mt<spdlog::async_factory>("async_logger", 
        "basic.txt");

    logger1->info("factory method setting");
    logger2->info("async factory method setting");

    spdlog::get("sync_logger")->error("there is an error");

    return 0;
}
5.1.3 手动创建

好处是方便直接为logger绑定多个sink。手动创建的流程和工厂方法中调用的create中的创建流程类似。

下面是手动创建一个sync logger的代码。

 // sync logger
    auto sink1 = std::make_shared<spdlog::sinks::ansicolor_stdout_sink_mt>();
    auto sink2 = std::make_shared<spdlog::sinks::basic_file_sink_mt>("manual.txt", true);
    auto logger3 = std::make_shared<spdlog::logger>("manual_logger", 
        spdlog::sinks_init_list{sink1, sink2});
    spdlog::register_logger(logger3);

    logger3->info("good");

如果要手动创建一个async logger,就需要保证registry中的线程池已经被初始化,需要手动加锁检查:

auto &mutex = registry_inst.tp_mutex();
        std::lock_guard<std::recursive_mutex> tp_lock(mutex);
        auto tp = registry_inst.get_tp();
        if (tp == nullptr) {
            tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1U);
            registry_inst.set_tp(tp);
        }

之后,分别构造sink和logger即可

        auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
        auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink),std::move(tp), OverflowPolicy);
        registry_inst.initialize_logger(new_logger);

5.2 创建sink

使用了模板方法设计模式,将从log函数的骨架中抽象出sink_it_和flush_两个方法供子类实现。sink_it_负责把日志写到用户缓冲区,flush_负责把日志刷到内核缓冲区。

模板方法

模板方法定义了一个算法框架,并将其中容易变化的步骤抽象出来交给子类去实现。通过这种方式,模板方法允许子类在不改变算法结构的情况下重新定义算法的某些特定步骤。

该设计模式适用于当一个过程中的部分步骤容易发生变化的场景。

在这里插入图片描述

5.3 自定义格式化

参考spdlog wiki。

5.3.1 set_pattern
// 自定义输出格式
    spdlog::set_pattern("[%^L%$] %v");  // 全局
    logger3->set_pattern("[%Y/%m/%d %H:%M:%S] [%^%L%$] %v");    // logger范围
    sink1->set_pattern("[%Y/%m/%d %H:%M:%S] [%^%L%$] %v [OK]"); // sink范围
    logger3->info("test");

5.3.2 自定义pattern flags
#include "spdlog/pattern_formatter.h"
class my_formatter_flag : public spdlog::custom_flag_formatter
{
public:
    void format(const spdlog::details::log_msg &, const std::tm &, spdlog::memory_buf_t &dest) override
    {
        std::string some_txt = "custom-flag";
        dest.append(some_txt.data(), some_txt.data() + some_txt.size());
    }

    std::unique_ptr<custom_flag_formatter> clone() const override
    {
        return spdlog::details::make_unique<my_formatter_flag>();
    }
};

void custom_flags_example()
{    
    auto formatter = std::make_unique<spdlog::pattern_formatter>();
    formatter->add_flag<my_formatter_flag>('*').set_pattern("[%n] [%*] [%^%l%$] %v");
    spdlog::set_formatter(std::move(formatter));
}

5.4 创建异步logger

5.4.1 使用async factory工厂
5.4.2 使用create_async

只是对前一个方法的简单封装:

template <typename Sink, typename... SinkArgs>
inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name, SinkArgs &&...sink_args) {
    return async_factory::create<Sink>(std::move(logger_name), 			std::forward<SinkArgs>(sink_args)...);
}
5.4.3 使用create_async_nb

创建非阻塞的异步日志,与前者的区别在于,其设置了日志消息的淘汰策略。

using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>;


template <typename Sink, typename... SinkArgs>
inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name, SinkArgs &&...sink_args) {
    return async_factory_nonblock::create<Sink>	(std::move(logger_name), std::forward<SinkArgs>(sink_args)...);
}
5.4.4 手动构造async_logger

参照:

在这里插入图片描述

这种方式过于繁杂,不推荐,即使想要自定义OverflowPolicy,也可以选择使用async_factory_impl

5.5 刷新策略

 // 刷新策略
    // 1. 手动flush
    logger5->flush();   // 对于异步日志,只是将消息放进队列
    // 2. 条件flush,设置最小的触发flush的日志等级
    logger5->flush_on(spdlog::level::debug);  
    // 3. 间隔flush,会开启一个线程来每隔一段时间flush一次
    spdlog::flush_every(std::chrono::seconds(5));

学习参考

学习更多相关知识请参考零声 github。

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

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

相关文章

FPGA在线升级 -- Multiboot

简介 本章节主要描述关于如何从Golden Image转换到Multiboot Image程序。 升级方案 Golden Image转换到Multiboot Image的方法主要又两种 1、使用ICAPE2 原语&#xff1b; 2、在XDC文件中加入升级约束命令&#xff1b; 以上两种方案都可以实现在线升级&#xff0c;第一种升级…

守护进程化

目录 一、进程组 二、会话 &#xff08;1&#xff09;什么是会话 &#xff08;2&#xff09;如何创建一个会话 三、守护进程 一、进程组 之前我们学习过进程&#xff0c;其实每一个进程除了有一个进程 ID(PID)之外 还属于一个进程组。进程组是一个或者多个进程的集合&…

QML插件扩展

https://note.youdao.com/ynoteshare/index.html?id294f86c78fb006f1b1b78cc430a20d74&typenote&_time1706510764806

RabbitMQ七种工作模式之 RPC通信模式, 发布确认模式

文章目录 六. RPC(RPC通信模式)客户端服务端 七. Publisher Confirms(发布确认模式)1. Publishing Messages Individually(单独确认)2. Publishing Messages in Batches(批量确认)3. Handling Publisher Confirms Asynchronously(异步确认) 六. RPC(RPC通信模式) 客⼾端发送消息…

ArcGIS字符串补零与去零

我们有时候需要 对属性表中字符串的补零与去零操作 我们下面直接视频教学 下面看视频教学 ArcGIS字符串去零与补零 推荐学习 ArcGIS全系列实战视频教程——9个单一课程组合 ArcGIS10.X入门实战视频教程&#xff08;GIS思维&#xff09; ArcGIS之模型构建器&#xff08;Mod…

前端面试如何出彩

1、原型链和作用域链说不太清&#xff0c;主要表现在寄生组合继承和extends继承的区别和new做了什么。2、推荐我的两篇文章&#xff1a;若川&#xff1a;面试官问&#xff1a;能否模拟实现JS的new操作符、若川&#xff1a;面试官问&#xff1a;JS的继承 3、数组构造函数上有哪些…

大模型应用编排工具Dify之构建专属FQA应用

1.前言 ​ 通过 dify可以基于开源大模型的能力&#xff0c;并结合业务知识库、工具API和自定义代码等构建特定场景、行业的专属大模型应用。本文通过 dify工作室的聊天助手-工作流编排构建了一个基于历史工作日志回答问题的助手&#xff0c;相比原始的大模型答复&#xff0c;通…

前端node环境安装:nvm安装详细教程(安装nvm、node、npm、cnpm、yarn及环境变量配置)

需求&#xff1a;在做前端开发的时候&#xff0c;有的时候 这个项目需要 node 14 那个项目需要 node 16&#xff0c;我们也不能卸载 安装 。这岂不是很麻烦。这个时候 就需要 一个工具 来管理我们的 node 版本和 npm 版本。 下面就分享一个 nvm 工具 用来管理 node 版本。 这个…

c基础加堆练习题

1】思维导图&#xff1a; 2】在堆区空间连续申请5个int类型大小空间&#xff0c;用来存放从终端输入的5个学生成绩&#xff0c;然后显示5个学生成绩&#xff0c;再将学生成绩升序排序&#xff0c;排序后&#xff0c;再次显示学生成绩。显示和排序分别用函数完成 要求&#xff…

嵌入式Linux 设备树 GPIO详解 示例分析 三星 NXP RK

GPIO设备树用于在Linux内核中定义与GPIO相关的硬件资源&#xff0c;它使操作系统可以识别、配置和使用GPIO引脚。设备树中通常会指定GPIO控制器的基地址、GPIO引脚的中断配置、时钟和其他相关信息。 目录 RK相关案例代码 NXP相关案例代码 三星相关案例代码 在设备树中&…

【日记】不想随礼欸(926 字)

正文 今天忙了一天。感觉从早上就开始在救火。客户经理迎接检查&#xff0c;要补资料&#xff0c;找我们问这样要那样&#xff0c;我自己的事情几乎完全开展不了。虽说也没什么大事就是了。 晚上行长还让我重装系统…… 难绷。看来这个爹味新行长懂得还挺多。 中午趁着不多的休…

Spring 源码学习(七)——注解后处理器

通过之前对注解式配置的解析&#xff08;Spring 源码学习&#xff08;三&#xff09;—— 注解式配置解析_spring源码学习-CSDN博客&#xff09;可以发现其使用 AnnotationConfigUtils 类的 registerAnnotationConfigProcessors 静态方法对象注解后处理器对象进行注册&#xff…

如何避免缓存击穿?超融合常驻缓存和多存储池方案对比

作者&#xff1a;SmartX 解决方案专家 钟锦锌 很多运维人员都知道&#xff0c;混合存储介质配置可能会带来“缓存击穿”的问题&#xff0c;尤其是大数据分析、数据仓库等需要频繁访问“冷数据”的应用场景&#xff0c;缓存击穿可能会更频繁地出现&#xff0c;影响业务运行。除…

Scala的正则表达式二

验证用户名是否合法 规则 1.长度在6-12之间 2.不能数字开头 3.只能包含数字&#xff0c;大小写字母&#xff0c;下划线def main(args: Array[String]): Unit {val name1 "1admin"//不合法&#xff0c;是数字开头val name2 "admin123"//合法val name3 &quo…

【CKA】Kubernetes(k8s)认证之CKA考题讲解

CKA考题讲解 0.考试101 0.1 kubectl命令⾃动补全 在 bash 中设置当前 shell 的⾃动补全&#xff0c;要先安装 bash-completion 包。 echo "source <(kubectl completion bash)" >> ~/.bashrc还可以在补全时为 kubectl 使⽤⼀个速记别名&#xff1a; al…

导入kotlin

android studio 导入kotlin项目 android studio kotlin教程 或者直接拿一个kt文件进来&#xff0c;在顶部会显示一个config&#xff0c;然后设置version&#xff0c;点击OK就可以了自动导了

《CSS 知识点》大屏卡片布局思路:弹性布局 flex-grow

思路 大屏左右两侧高宽一致&#xff0c;内部卡片可按比例设置&#xff01; 使用弹性布局和属性 flex-grow 设置比例&#xff1b;间隔使用 margin-bottom 设置&#xff0c;最后一个卡片不设置&#xff1b; 效果如图 代码说明 CSS代码 26 - 30&#xff0c;左右两侧设置弹性布…

责任链模式的理解和实践

责任链模式&#xff08;Chain of Responsibility&#xff09;是行为型设计模式之一&#xff0c;它通过将多个对象连成一条链&#xff0c;并沿着这条链传递请求&#xff0c;直到有对象处理它为止。这个模式的主要目的是将请求的发送者和接收者解耦&#xff0c;使请求沿着处理链传…

如何在 Ubuntu 上安装开源监控工具 Uptime Kuma

简介 Uptime Kuma&#xff08;或简称 Kuma&#xff09;是一个开源监控工具&#xff0c;用于监控 HTTP、HTTPS、DNS 等协议的服务。Uptime Kuma 提供多种功能&#xff0c;如多语言支持、多个状态页面、代理支持等。 接下来&#xff0c;我将一步一步教大家如何进行安装和部署&am…

go语言zero框架对接阿里云消息队列MQ的rabbit的配置与调用

在 Go 语言中对接阿里云消息队列&#xff08;MQ&#xff09;的 RabbitMQ 配置与调用&#xff0c;首先需要安装和配置相关的 Go 库&#xff0c;并了解如何通过 RabbitMQ 与阿里云消息队列进行交互。 ### 步骤一&#xff1a;安装 RabbitMQ Go 客户端库 阿里云的消息队列&#x…