【C++】spdlog光速入门,C++logger最简单最快的库
参考文档: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最简单最快的库的更多相关文章
- Hadoop基础-MapReduce入门篇之编写简单的Wordcount测试代码
Hadoop基础-MapReduce入门篇之编写简单的Wordcount测试代码 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本文主要是记录一写我在学习MapReduce时的一些 ...
- 【0】TensorFlow光速入门-序
本文地址:https://www.cnblogs.com/tujia/p/13863181.html 序言: 对于我这么一个技术渣渣来说,想学习TensorFlow机器学习,实在是太难了: 百度&qu ...
- 【4】TensorFlow光速入门-保存模型及加载模型并使用
本文地址:https://www.cnblogs.com/tujia/p/13862360.html 系列文章: [0]TensorFlow光速入门-序 [1]TensorFlow光速入门-tenso ...
- git光速入门
git的使用和讲解 版本控制 说到版本控制,脑海里总会浮现大学毕业是写毕业论文的场景,你电脑上的毕业论文一定出现过这番景象! 1 2 3 4 5 6 7 8 9 10 11 毕业论文_初稿.doc ...
- 【6】TensorFlow光速入门-python模型转换为tfjs模型并使用
本文地址:https://www.cnblogs.com/tujia/p/13862365.html 系列文章: [0]TensorFlow光速入门-序 [1]TensorFlow光速入门-tenso ...
- 【1】TensorFlow光速入门-tensorflow开发基本流程
本文地址:https://www.cnblogs.com/tujia/p/13862339.html 系列文章: [0]TensorFlow光速入门-序 [1]TensorFlow光速入门-tenso ...
- 【2】TensorFlow光速入门-数据预处理(得到数据集)
本文地址:https://www.cnblogs.com/tujia/p/13862351.html 系列文章: [0]TensorFlow光速入门-序 [1]TensorFlow光速入门-tenso ...
- 【3】TensorFlow光速入门-训练及评估
本文地址:https://www.cnblogs.com/tujia/p/13862357.html 系列文章: [0]TensorFlow光速入门-序 [1]TensorFlow光速入门-tenso ...
- 【5】TensorFlow光速入门-图片分类完整代码
本文地址:https://www.cnblogs.com/tujia/p/13862364.html 系列文章: [0]TensorFlow光速入门-序 [1]TensorFlow光速入门-tenso ...
随机推荐
- Docker 好用的镜像
Docker 官方镜像 1.个人博客空间wordpress 2.开源管理系统odoo 3.开发文档生成工具star7th/showdoc.(启动说明文档https://www.showdoc.com. ...
- ABP vNext系列文章03---依赖注入
一.依赖注入的类型注册 ABP的依赖注入系统是基于Microsoft的依赖注入扩展库(Microsoft.Extensions.DependencyInjection nuget包)开发的.因此,它的 ...
- Redis 17 缓存穿透 缓存击穿 缓存雪崩
参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 使用缓存的问题 ...
- 「雅礼集训 2017 Day2」线段游戏(线段树懒标记“启发式下传”,李超树)
题面 题解 加入一条线段,可以把它转化为在[L,R]区间内加一条线 y=ax+b (如果原线段与y轴平行,就相当于在{x1}处加一条线 y=max(y1,y2)) 我们可以把它加到线段树上,线段树上每 ...
- [Noi2010]能量采集 (莫比乌斯反演)
[Noi2010]能量采集 Description 栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量.在这些植物采集能量后, 栋栋再使用一个能量汇集机器把这些植物采集到的能 ...
- 【java】学习路线8-cmd带命令编译包
/*java类包(package)package XX.XX.XX; 包名命名规则:(以域名开头,都是小写) com.remooo.xx 编译:javac -d . ...
- 「题解报告」 P3167 [CQOI2014]通配符匹配
「题解报告」 P3167 [CQOI2014]通配符匹配 思路 *和?显然无法直接匹配,但是可以发现「通配符个数不超过 \(10\) 」,那么我们可以考虑分段匹配. 我们首先把原字符串分成多个以一个通 ...
- Android同屏、摄像头RTMP推送常用的数据接口设计探讨
前言 好多开发者在调用Android平台RTMP推送或轻量级RTSP服务接口时,采集到的video数据类型多样化,如420sp.I420.yv12.nv21.rgb的,还有的拿到的图像是倒置的,如果开 ...
- Nginx超时问题解决
在 nginx.conf 中配置以下内容 ... http { ... server { # 这里表示upstream 的连接.读取.发送超时时间都是300秒 proxy_connect_timeou ...
- 硬核剖析Redis单线程为什么那么快?
(本文首发于"数据库架构师"公号,订阅"数据库架构师"公号,一起学习数据库技术,助力职业发展) Redis目前是使用率最高的内存库数据库,是企业应用开发的必备, ...