草庐IT

【C++】spdlog光速入门,C++logger最简单最快的库

jinyunshaobing 2023-04-15 原文

参考文档:https://spdlog.docsforge.com/master/

spdlog简介

Very fast, header only, C++ logging library.
一个header-only的C++日志库,十分高效且易用。

获取安装方式

https://github.com/gabime/spdlog
使用时只需要将git项目内的/include/spdlog文件夹整个放入项目的include目录下即可

使用样例

#include "spdlog/spdlog.h"

int main() 
{
    spdlog::info("Welcome to spdlog!");
    spdlog::error("Some error message with arg: {}", 1);
    
    spdlog::warn("Easy padding in numbers like {:08d}", 12);
    spdlog::critical("Support for int: {0:d};  hex: {0:x};  oct: {0:o}; bin: {0:b}", 42);
    spdlog::info("Support for floats {:03.2f}", 1.23456);
    spdlog::info("Positional args are {1} {0}..", "too", "supported");
    spdlog::info("{:<30}", "left aligned");
    
    spdlog::set_level(spdlog::level::debug); // Set global log level to debug
    spdlog::debug("This message should be displayed..");    
    
    // change log pattern
    spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
    
    // Compile time log levels
    // define SPDLOG_ACTIVE_LEVEL to desired level
    SPDLOG_TRACE("Some trace message with param {}", 42);
    SPDLOG_DEBUG("Some debug message");
}

快速入门

几个核心概念

  • logger:日志对象,每个日志内包含一个sink组成的vector,每个sink可以分别设置优先级,logger本身也可设置优先级
  • sink:直译是水槽,实际上是引流的对象或者可以认为是输出目标,spdlog库内置了多种不同类型的logger可供选择
  • formatter:格式化对象,绝大部分情况下spdlog默认的格式就足够用了,但是如果有个性化需求,可以进行自定义格式
  • level:日志级别,不同的日志库可能会有不同的设置,但是基本情况下都会有debug、info、warn、error等的级别划分来处理不同的情况,具体各个级别的情况可以根据自己的实际情况选取

逻辑关系:每个logger包含一个vector,该vector由一个或多个std::shared_ptr<sink>组成,logger的每条日志都会调用sink对象,由sink对象按照formatter的格式输出到sink指定的地方(有可能是控制台、文件等),接下来我们从内到外的讲解spdlog的这三个核心组件

formatter

formatter也即格式化对象,用于控制日志的输出格式,spdlog自带了默认的formatter,一般情况下,我们无需任何修改,直接使用即可。注意,每个sink会有一个formatter

默认formatter

默认formatter的格式为:[日期时间] [logger名] [log级别] log内容

[2022-10-13 17:00:55.795] [sidecar] [debug] found env KAFKA_PARTITION_VALUE : -1
[2022-10-13 17:00:55.795] [sidecar_config] [debug] kafka_config.kafka_brokers : localhost:9092
[2022-10-13 17:00:55.795] [sidecar_config] [debug] kafka_config.kafka_main_topic : workflow_queue
[2022-10-13 17:00:55.795] [sidecar_config] [debug] kafka_config.kafka_partition_value : -1
[2022-10-13 17:00:55.795] [sidecar] [info] SidecarConfig initialized

自定义formatter

如果默认的formatter不符合需求,可以自定义formatter,具体方式如下

  • set_parrtern(pattern_string);
    • 例如:
    • 全局级别的:spdlog::set_pattern(" [%H:%M:%S %z] [thread %t] %v ");
    • 单个logger级别的:some_logger->set_parttern(">>> %H:%M:%S %z %v <<<");
    • 单个sink级别的:some_sink-> set_parttern(".. %H: %M ..");
      其中用到了%H %M这些占位符,事实上它们都是预先设定好的,想要查看所有的占位符情况,可以参考以下网站:
      https://spdlog.docsforge.com/v1.x/3.custom-formatting/#pattern-flags

sink

每个sink对应着一个输出目标和输出格式,它内部包含一个formatter,输出目标可以是控制台、文件等地方。
所有的sink都在命名空间spdlog::sinks下,可以自行探索

控制台sink

spdlog中创建控制台sink非常简单,该方式创建的sink会输出到命令行终端,且是彩色的(也可以选非彩色的,但是有彩色的应该都会选彩色的吧……)。后缀的_mt代表多线程,_st代表单线程

auto sink1 = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();

文件sink

文件sink的类型有很多,这里展示几种经典类型

auto sink1 = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file_name);//最简单的文件sink,只需要指定文件名

auto sink2 = std::make_shared<spdlog::sinks::daily_file_sink_mt>(log_file_name, path, 14, 22);//每天的14点22分在path下创建新的文件

auto sink3 = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_file_name, 1024 * 1024 * 10, 100, false);//轮转文件,一个文件满了会写到下一个文件,第二个参数是单文件大小上限,第三个参数是文件数量最大值

其他sink

ostream_sink
syslog_sink
......
也可以通过继承base_sink创建子类来自定义sink,具体可以参考:
https://spdlog.docsforge.com/v1.x/4.sinks/#implementing-your-own-sink

sink的flush问题

创建好sink后建议设置flush方式,否则可能无法立刻在file中看到logger的内容
以下为两种重要的flush方式设置(直接设置全局)

spdlog::flush_every(std::chrono::seconds(1));
spdlog::flush_on(spdlog::level::debug);

logger

日志对象,每个logger内包含了一个vector用于存放sink,每个sink都是相互独立
因此一个日志对象在输出日志时可以同时输出到控制台和文件等位置

使用默认logger

如果整个项目中只需要一个logger,spdlog提供了最为便捷的默认logger,注意,该logger在全局公用,输出到控制台、多线程、彩色

//Use the default logger (stdout, multi-threaded, colored)
spdlog::info("Hello, {}!", "World");

创建特定的logger

大部分情况下默认logger是不够用的,因为我们可能需要做不同项目模块各自的logger,可能需要logger输出到文件进行持久化,所以创建logger是很重要的一件事。好在创建logger也是非常简单的!

方式一:直接创建

与创建sink类似,我们可以非常便捷的创建logger
由于大部分时候一个logger只会有一个sink,所以spdlog提供了创建logger的接口并封装了创建sink的过程

auto console = spdlog::stdout_color_mt("some_unique_name");//一个输出到控制台的彩色多线程logger,可以指定名字
auto file_logger = spdlog::rotating_logger_mt("file_logger", "logs/mylogfile", 1048576 * 5, 3);//一个输出到指定文件的轮转文件logger,后面的参数指定了文件的信息

方式二:组合sinks方式创建

有时候,单sink的logger不够用,那么可以先创建sink的vector,然后使用sinks_vector创建logger
以下样例中,首先创建了sink的vector,然后创建了两个sink并放入vector,最后使用该vector创建了logger,其中,set_level的过程不是必须的,register_logger一般是必须的,否则只能在创建logger的地方使用该logger,关于register的问题可以往下看

    std::vector<spdlog::sink_ptr> sinks;

    auto sink1 = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    sink1->set_level(SidecarLoggers::getGlobalLevel());
    sinks.push_back(sink1);

    auto sink2 = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_file_name, 1024 * 1024 * 10, 100, false);
    sink2->set_level(spdlog::level::debug);
    sinks.push_back(sink2);
    
    auto logger = std::make_shared<spdlog::logger>("logger_name", begin(sinks), end(sinks));
    logger->set_level(spdlog::level::debug);
    spdlog::register_logger(logger);

logger的注册与获取

在一个地方创建了logger却只能在该处使用肯定是不好用的,所以spdlog提供了一个全局注册和获取logger,我们只需要在某处先创建logger并注册,那么后面在其他地方使用时直接获取就可以了
注册:spdlog::register_logger()
获取:spdlog::get()

//上面的代码中我们注册了一个logger,名字是logger_name,接下来尝试获取
auto logger = SidecarLoggers::getLogger("logger_name");

关于注册与获取需要注意的事

  • 必须先创建注册才能获取,建议每个模块的logger都在整个模块最开始初始化时创建并注册。如果在全局尝试获取不存在的logger,会返回空指针,如果恰好又使用空指针尝试输出logger,会造成整个程序的崩溃(访问非法内存了,segment fault)
  • 通过上述的方式一创建的logger是自动注册的,不需要手动注册,但是方式二创建的logger需要手动注册
  • 一旦注册,全局使用,名字标识logger,在各个模块获取同一个名字的logger会获取到同一个logger的指针

logger的使用

获取到一个logger之后,就可以愉快的使用它了,使用起来很简单

logger->debug("this is a debug msg");
logger->warn("warn!!!!");
logger->info("hello world");
logger->error("烫烫烫烫");

logger的level设置

logger的默认level是info,如果处于开发环境或者生产环境,会只需要debug级别以上或者warn级别以上的log
要设置logger的级别,很简单:

logger->set_level(spdlog::level::debug);

可以设置全局logger级别

spdlog::set_level(spdlog::level::warn);

可以设置sink级别的logger

sink1->set_level(spdlog::level::info);

注意:一个logger内假如有多个sink,那么这些sink分别设置level是可以不同的,但是由于logger本身也有level,所以真正使用时,logger的level如果高于某个sink,会覆盖该sink的level,所以建议此时把logger的level手动设置为debug(默认为info)

样例代码

以下代码为本人对spdlog的简单使用封装,主要功能有:

  • 一键初始化,根据环境变量可设置修改logger级别等
  • 一键创建双sink的logger(控制台和文件)
  • 一键获取logger,假如logger不存在则创建

头文件

#ifndef SIDECAR_LOGGER_H
#define SIDECAR_LOGGER_H

#include <stdlib.h>
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/stdout_sinks.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/daily_file_sink.h"
#include <vector>

class SidecarLoggers
{
public:
    static void init();
    static spdlog::level::level_enum getGlobalLevel();
    static std::vector<spdlog::sink_ptr> createSinks(const std::string &log_file_name);
    static void createLogger(const std::string &logger_name);
    static std::shared_ptr<spdlog::logger> getLogger(const std::string &logger_name);

private:
    static spdlog::level::level_enum global_level;
};

#endif

源文件

#include "sidecar_logger.h"

spdlog::level::level_enum SidecarLoggers::global_level = spdlog::level::info;

spdlog::level::level_enum SidecarLoggers::getGlobalLevel()
{
    return global_level;
}

std::vector<spdlog::sink_ptr> SidecarLoggers::createSinks(const std::string &log_file_name)
{
    std::vector<spdlog::sink_ptr> sinks;

    auto sink1 = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    sink1->set_level(SidecarLoggers::getGlobalLevel());
    sinks.push_back(sink1);

    auto sink2 = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_file_name, 1024 * 1024 * 10, 100, false);
    sink2->set_level(spdlog::level::debug);
    sinks.push_back(sink2);
    return sinks;
}

void SidecarLoggers::createLogger(const std::string &logger_name)
{
    std::string log_file_name = logger_name + "_log.txt";
    auto sinks = SidecarLoggers::createSinks(log_file_name);

    auto logger = std::make_shared<spdlog::logger>(logger_name, begin(sinks), end(sinks));
    logger->set_level(spdlog::level::debug);
    spdlog::register_logger(logger);
}

std::shared_ptr<spdlog::logger> SidecarLoggers::getLogger(const std::string &logger_name){
    auto logger = spdlog::get(logger_name);
    if(!logger){//looger指向为空
        createLogger(logger_name);
        logger = spdlog::get(logger_name);
    }
    return logger;
}


void SidecarLoggers::init()
{
    auto level = spdlog::level::debug;
    if (std::getenv("STAGE") != NULL)
    {
        std::string stage = std::getenv("STAGE");
        if (stage == "dev")
            level = spdlog::level::debug;
    }
    SidecarLoggers::global_level = level;

    spdlog::flush_every(std::chrono::seconds(1));
    spdlog::flush_on(spdlog::level::debug);

    SidecarLoggers::createLogger("sidecar");
}

有关【C++】spdlog光速入门,C++logger最简单最快的库的更多相关文章

  1. ruby - 简单获取法拉第超时 - 2

    有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

  2. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  3. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

  4. ruby - 使用 Ruby 通过 Outlook 发送消息的最简单方法是什么? - 2

    我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=

  5. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

  6. Qt Designer的简单使用 - 2

    在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q

  7. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  8. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  9. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  10. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

随机推荐