[C++]-日志记录库SPDLog简介[通俗易懂]

2022-09-10 09:47:53 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

文章目录

  • spdlog库
    • 日志记录槽sink
    • 日志记录器logger
    • 输出格式pattern
      • 对齐方式
      • 截断
    • 字符串格式化fmt
      • Format Specification
  • spdlog使用
    • 异常处理
    • logger
      • 基础用法
      • stdout日志
      • 文件日志
        • 基本文件
        • 循环文件
        • 每日文件
    • 示例

spdlog是一款开源的、快速的日志库。

spdlog库

spdlog是基于C 11实现的一款纯头文件的日志管理库(git地址:https://github.com/gabime/spdlog,API说明:https://spdlog.docsforge.com/v1.x/1.quickstart/):

  • 配置特别简单,仅包含头文件即可;
  • 写日志方式简单明了;
  • 可实现自动按日期创建日志文件/定时创建日志文件;
  • 可自定义日志格式;
  • 可以输出当前输出日志所在的文件及函数;
  • 可自定义文档大小;
  • 可将不同级别的信息输出到不同日志文件;
  • 多平台等。

spdlog中各对象都分为多线程与单线程版本:

  • *_st:单线程版本,不用加锁,效率更高。
  • *_mt:多线程版本,用于多线程程序是线程安全的。

日志记录槽sink

spdlog定义了几种sinks用于不同场景(也可自定义)下的日志输出,sink中主要包含:

  • set_pattern(const std::string&):设置日志输出的内容格式。
  • set_level(level_enum): 设置日志输出的最低等级。
  • log(log_msg):由logger自动调用,外部不会主动调用。

日志记录器logger

一个logger对象中存储有多个sink,当调用logger的日志输出函数时,logger会调用自身存储的所有sink对象的log(log_msg) 函数进行输出。logger中主要包括:

  • set_pattern(const std::string&):设置logger包含的所有sink的日志输出内容格式。
  • set_level(level_enum):设置logger日志输出最低等级,如果logger包含的sink没有设置日志等级的话,则会为其设置日志等级。
  • log(level_enum level,log_msg content):按照level等级进行输出content,logger其中日志输出最低等级小于或等于level的sink会进行执行输出操作。
  • trace(content,arg1,arg2…):按照trace等级进行输出,输出内容由content与后面的参数格式化而成。同类的函数还包括:debug/info/warn…。

输出格式pattern

通过set_pattern可设定日志格式,如set_pattern("[%Y-%m-%d %H:%M:%S.%e][%l](%@): %v");

输出标记flag:

flag

meaning

example

%v

日志内容

“my log test content”

%t

线程ID

“123”

%P

进程ID

“234”

%n

记录器Logger名

“basicLogger”

%l

日志级别

“debug”, “info”, etc

%L

日志级别简称

“D”, “I”, etc

%a

星期几(简称)

“Thu”

%A

星期几

“Thursday”

%b

月份简称

“Aug”

%B

月份

“August”

%c

日期时间

“Thu Aug 23 15:35:46 2014”

%C

年(两位)

“14”

%Y

“2014”

%D %x

日期简写

“08/23/14”

%m

月份(数字)

“11”

%d

日(数组)

“29”

%H

小时(24制)

“23”

%I

小时(12制)

“11”

%M

分钟

“59”

%S

“58”

%e

毫秒

“678”

%f

微秒

“056789”

%F

纳秒

“256789123”

%p

AM/PM

“AM”

%r

时间(12制)

“02:55:02 pm”

%R

时分(24制)

“23:55”

%T %X

时间(24制)

“23:55:59”

%z

时区(偏移)

“ 02:00”

%E

epoch(秒)

“1528834770”

%%

百分号

“%”

%

默认格式

“[2014-10-31 23:46:59.678] [mylogger] [info] Some message”

%^

start color range (can be used only once)

“[mylogger] [info(green)] Some message”

%$

end color range (for example %^[ ]%$ %v) (can be used only once)

[ ] Some message

%@

文件名与行数

my_file.cpp:123

%s

文件名

my_file.cpp

%g

文件名(含路径)

/some/dir/my_file.cpp

%#

行数

123

%!

函数名

my_func

%o

相对上一条记录的时间间隔(毫秒)

456

%i

相对上一条记录的时间间隔(微秒)

456

%u

相对上一条记录的时间间隔(纳秒)

11456

%O

相对上一条记录的时间间隔(秒)

4

日志输出中要携带文件名、行数或函数名时,必须使用SPDLOG_LOGGER_*宏,且要激活对应的级别(哪些级别以上的日志会被记录):

代码语言:javascript复制
// 记录INFO及以上级别日志
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
#include "spdlog/spdlog.h"

SPDLOG_LOGGER_INFO(myLogger, "Support for floats {:03.2f}", 1.23456);
SPDLOG_LOGGER_WARN(myLogger, "Easy padding in numbers like {:08d}", 12);

对齐方式

每个flag都可携带对齐方式(最多支持64字符),

align

meaning

example

result

%<width><flag>

右对齐

%8l

” info”

%-<width><flag>

左对齐

%-8l

“info “

%=<width><flag>

居中

%=8l

” info “

截断

通过!可设定对应输出的最大长度:

align

meaning

example

result

%<width>!<flag>

右对齐且截断

%3!l

“inf”

%-<width>!<flag>

左对齐且截断

%-2!l

“in”

%=<width>!<flag>

居中且截断

%=1!l

“i”

字符串格式化fmt

spdlog中字符串格式化使用fmt(https://github.com/fmtlib/fmt)库。

格式化方式:{ [arg_id] [: (format_spec | chrono_format_spec)] }

  • arg_id:参数标识;
    • 忽略(为空时),依次对应每一个参数;
    • 索引(数字,从0开始),引用第几个索引;
    • 名称,命名参数;
  • format_spec:参数格式化方式(类型、对齐、填充等);

Format Specification

格式化符说明:

代码语言:javascript复制
format_spec ::=  [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
fill        ::=  <a character other than '{' or '}'>
align       ::=  "<" | ">" | "^" // 左、右、居中对齐
sign        ::=  " " | "-" | " "
width       ::=  integer | {[arg_id]} // 宽度:数字或指定的参数
precision   ::=  integer | {[arg_id]} // 精度:数字或指定的参数
type        ::=  "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" 
                 | "g" | "G" | "o" | "p" | "s" | "x" | "X"

#不同的转换下有不同的意义:

  • 整数时,表示前面添加进制前缀,如0x, 0b等;
  • 浮点数时:总是有小数点(即使没有小数部分);

L只对数字有效,根据本地设置来输出:如,

代码语言:javascript复制
auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890);
// s == "1,234,567,890"

格式化类型:

type

meaning

s

字符串

c

字符

b/B

二进制

d

数字(十进制)

o

八进制

x/X

十六进制

a/A

十六进制浮点数(p表示指数)

e/E

科学计数

f/F

浮点数(包括NAN,INF),固定小数位数输出

g/G

浮点数输出

p

指针

示例:

代码语言:javascript复制
fmt::format("{:*^30}", "centered");  // use '*' as a fill char
// Result: "***********centered***********"

fmt::format("{:#04x}", 0);
// Result: "0x00"

fmt::print(
  "┌{0:─^{2}}┐n"
  "│{1: ^{2}}│n"
  "└{0:─^{2}}┘n", "", "Hello, world!", 20);
┌────────────────────┐
│   Hello, world!    │
└────────────────────┘

spdlog使用

spdlog默认日志输出级别是INFO。

默认情况下,日志是同步模式的,可通过以下方法开启异步模式:

代码语言:javascript复制
size_t q_size = 4096; //queue size must be power of 2
spdlog::set_async_mode(q_size);

在异步模式下,日志先存入队列(队列占用的内存 = 设置的队列大小 * slot的大小, 64位系统下slot大小为104字节。),再由工作者线程从队列中取出并输出。当队列满时,会根据设定策略处理:

异常处理

当输出日志时发生异常时,spdlog会向std::err 打印一条语句,为了避免输出的异常语句刷屏,打印频率被限制在每分钟一条。可通过set_error_handler来设定异常处理函数:

代码语言:javascript复制
    //can be set globaly or per logger(logger->set_error_handler(..))
    spdlog::set_error_handler([](const std::string& msg)
    {
        std::cerr << "my err handler: " << msg << std::endl;
    });

logger

默认情况下,spdlog的默认logger为输出到stdout:

代码语言:javascript复制
#    ifdef _WIN32
    auto color_sink = std::make_shared<sinks::wincolor_stdout_sink_mt>();
#    else
    auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>();
#    endif

在使用完logger后,要关闭掉以释放(否则无再建立同名logger)

代码语言:javascript复制
spdlog::drop_all();	// 关闭所有logger
spd::drop("basic_logger"); // 关闭指定logger

基础用法

spdlog中使用{}(里面可指定格式)作为格式化符

以下方式把日志输出到默认logger上:

代码语言:javascript复制
//#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
#include "spdlog/spdlog.h"

int main() 
{
    spdlog::info("{:<30}", "left aligned");    
    spdlog::warn("Easy padding in numbers like {:08d}", 12);
    spdlog::error("Some error message with arg: {}", 1);    
    spdlog::critical("Support for int: {0:d};  hex: {0:x};  oct: {0:o}; bin: {0:b}", 42);
    
    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");
}

stdout日志

以彩色方式输出到标准输出设备上:

代码语言:javascript复制
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"

void stdout_example()
{
    // create color multi threaded logger
    auto console = spdlog::stdout_color_mt("console");    
    auto err_logger = spdlog::stderr_color_mt("stderr");    
    spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)");
}

文件日志

基本文件

最简单的日志文件:

代码语言:javascript复制
#include "spdlog/sinks/basic_file_sink.h"
void basic_logfile_example()
{
    try 
    {
        auto logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt");
    }
    catch (const spdlog::spdlog_ex &ex)
    {
        std::cout << "Log init failed: " << ex.what() << std::endl;
    }
}
循环文件

日志文件超过指定大小后,自动生成一个新的;并且只保留最多指定数量的日志文件:

代码语言:javascript复制
#include "spdlog/sinks/rotating_file_sink.h"
void rotating_example()
{
    // Create a file rotating logger with 5mb size max and 3 rotated files
    auto max_size = 1024*1024 * 5;
    auto max_files = 3;
    auto logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", max_size, max_files);
}
每日文件

每天指定时间生成一个新的日志文件:

代码语言:javascript复制
#include "spdlog/sinks/daily_file_sink.h"
void daily_example()
{
    // Create a daily logger - a new file is created every day on 2:30am
    auto logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
}

示例

设定默认日志记录文件并在不同地方获取使用:

代码语言:javascript复制
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO

#include "spdlog/spdlog.h"
#include "spdlog/sinks/rotating_file_sink.h"


void writeLog(int n) {
    for (int i = 0; i < n;   i) {
        // 获取logger后输出日志
        auto myLogger = spdlog::get("baseLogger");
        myLogger->info("{}: Hello, {}!", i   1, "World");
        myLogger->info("Welcome to spdlog!");
        myLogger->error("Some error message with arg: {}", 1);

        // 带文件名与行号的日志输出
        SPDLOG_LOGGER_INFO(myLogger, "Support for floats {:03.2f}", 1.23456);
        SPDLOG_LOGGER_WARN(myLogger, "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::error("Some error message with arg: {}", 1);
        spdlog::warn("Easy padding in numbers like {:08d}", 12);
        spdlog::info("Support for floats {:03.2f}", 1.23456);
    }
}

void testSPDLog() {
    // 设定日志最大100k,且最多保留10个
    auto myLogger = spdlog::rotating_logger_mt("baseLogger", "logs/basic.log", 1024 * 100, 10);
    spdlog::set_default_logger(myLogger);
    myLogger->set_pattern("[%Y-%m-%d %H:%M:%S.%e][%l](%@): %v"); // 非通过宏输出的日志%@输出为空
    myLogger->set_level(spdlog::level::info);

    myLogger->info("Hello, {}!", "World");

    writeLog(10);
}

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/152339.html原文链接:https://javaforall.cn

0 人点赞