Embedded linux learning notes -- Notes on the use of open source library spdlog

Posted by tofi84 on Tue, 08 Mar 2022 19:37:56 +0100

Recently, I sorted out the code and saw the company's code on log. I gained a lot, so I sorted out the instructions for the use of spdlog library.

https://github.com/gabime/spdlog.git

This paper mainly arranges the following aspects. The main basis for reference is the official routine, which is located in github

example\example.cpp

1. Log level division and several functions related to log level

The log is divided into the following levels. The specific level is the meaning, and the most serious time is
critical level, the least serious is trace

              /* spdlog::level::trace      */
              /* spdlog::level::debug      */
              /* spdlog::level::info       */
              /* spdlog::level::warn       */
              /* spdlog::level::err        */
              /* spdlog::level::critical   */
              /* spdlog::level::off        */

In actual use, this method can be used to directly output logs

  spdlog::info("Welcome to spdlog version {}.{}.{}  !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH);
//[2021-04-07 22:46:35.840] [info] Welcome to spdlog version 1.8.5  !

It will be used in development
m_logger->info("xxxxxxxxxxxxxxx")
Output in this form. Adding pointers is mainly to facilitate the output of different logs to different locations, such as M_ Output the content corresponding to Log1 to file 1; m_ Output the contents of log2 to file 2;

1.1 spdlog::set_level(spdlog::level::trace)

This function is mainly used to set the output level of the log. In the debugging stage, we want to output more debugging information. When the program is stable, we only need to output a small amount of important information, such as error reporting. At this time, you need to set the log output level. spdlog::set_level is a function that sets the output level.

1.2 logger->flush_on(spdlog::level::err);

When an error or above is encountered, the cached buffer will be written to the file immediately. The underlying call is std::fflush(_fd)

2. Introduction to log character format

Because a lot of template functions are used in spdlog, the use of functions is very flexible
All format parameters can be replaced by {}.

2.1 disorder the order of parameters

The variables in brackets can be divided into two parts. If only one number is filled in, it represents the sequence number of the parameter

spdlog::info("Positional args are {1} {0}..", "too", "supported");
//[2021-04-07 23:49:24.916] [info] Positional args are supported too..

In the above example, the order of parameters is obtained according to the sequence number in parentheses.

2.2 formatted digital output

Like printf, spdlog also supports formatted character output.

    spdlog::warn("Easy padding in numbers like {:08d}", 12);
//  [2021-04-07 23:49:24.916] [warning] Easy padding in numbers like 00000012
    spdlog::critical("Support for int: {0:d};  hex: {0:x};  oct: {0:o}; bin: {0:b}", 42);
//  [2021-04-07 23:49:24.916] [critical] Support for int: 42;  hex: 2a;  oct: 52; bin: 101010
    spdlog::info("Support for floats {1:06.4f}", 1.23456,9.8754321);
//  [2021-04-07 23:49:24.916] [info] Support for floats 9.8754    
    spdlog::info("Positional args are {1} {0}..", "too", "supported");
//  [2021-04-07 23:49:24.916] [info] Positional args are supported too..
    spdlog::info("{:>8} aligned, {:<8} aligned", "right", "left");
//  [2021-04-07 23:49:24.916] [info]    right aligned, left     aligned

2.3 method of removing log time

Do you think the log printing time takes up too much storage space, In this way, the time of log printing can be removed

m_log->set_pattern("%v");

2.4 some methods of recording hexadecimal digits

The following is from the official readme file

// many types of std::container<char> types can be used.
// ranges are supported too.
// format flags:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output to lines.
// {:a} - show ASCII if :n is not set.

#include "spdlog/fmt/bin_to_hex.h"

void binary_example()
{
    auto console = spdlog::get("console");
    std::array<char, 80> buf;
    console->info("Binary example: {}", spdlog::to_hex(buf));
    console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
    // more examples:
    // logger->info("uppercase: {:X}", spdlog::to_hex(buf));
    // logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
    // logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
}

2.5 output format of time, thread, etc

The big guys on the Internet here write very well

https://blog.csdn.net/shizheng163/article/details/79418190

In addition to the above boss's summary, I also found the code segment about format analysis. You can directly see the meaning of format from the code!

//  The code is taken from include\spdlog\pattern_formatter-inl.h position of about 1100 rows
 switch (flag)
    {
    case ('+'): // default formatter
        formatters_.push_back(details::make_unique<details::full_formatter>(padding));
        break;

    case 'n': // logger name
        formatters_.push_back(details::make_unique<details::name_formatter<Padder>>(padding));
        break;

    case 'l': // level
        formatters_.push_back(details::make_unique<details::level_formatter<Padder>>(padding));
        break;

    case 'L': // short level
        formatters_.push_back(details::make_unique<details::short_level_formatter<Padder>>(padding));
        break;

    case ('t'): // thread id
        formatters_.push_back(details::make_unique<details::t_formatter<Padder>>(padding));
        break;

    case ('v'): // the message text
        formatters_.push_back(details::make_unique<details::v_formatter<Padder>>(padding));
        break;

    case ('a'): // weekday
        formatters_.push_back(details::make_unique<details::a_formatter<Padder>>(padding));
        break;

    case ('A'): // short weekday
        formatters_.push_back(details::make_unique<details::A_formatter<Padder>>(padding));
        break;

    case ('b'):
    case ('h'): // month
        formatters_.push_back(details::make_unique<details::b_formatter<Padder>>(padding));
        break;

    case ('B'): // short month
        formatters_.push_back(details::make_unique<details::B_formatter<Padder>>(padding));
        break;

    case ('c'): // datetime
        formatters_.push_back(details::make_unique<details::c_formatter<Padder>>(padding));
        break;

    case ('C'): // year 2 digits
        formatters_.push_back(details::make_unique<details::C_formatter<Padder>>(padding));
        break;

    case ('Y'): // year 4 digits
        formatters_.push_back(details::make_unique<details::Y_formatter<Padder>>(padding));
        break;

    case ('D'):
    case ('x'): // datetime MM/DD/YY
        formatters_.push_back(details::make_unique<details::D_formatter<Padder>>(padding));
        break;

    case ('m'): // month 1-12
        formatters_.push_back(details::make_unique<details::m_formatter<Padder>>(padding));
        break;

    case ('d'): // day of month 1-31
        formatters_.push_back(details::make_unique<details::d_formatter<Padder>>(padding));
        break;

    case ('H'): // hours 24
        formatters_.push_back(details::make_unique<details::H_formatter<Padder>>(padding));
        break;

    case ('I'): // hours 12
        formatters_.push_back(details::make_unique<details::I_formatter<Padder>>(padding));
        break;

    case ('M'): // minutes
        formatters_.push_back(details::make_unique<details::M_formatter<Padder>>(padding));
        break;

    case ('S'): // seconds
        formatters_.push_back(details::make_unique<details::S_formatter<Padder>>(padding));
        break;

    case ('e'): // milliseconds
        formatters_.push_back(details::make_unique<details::e_formatter<Padder>>(padding));
        break;

    case ('f'): // microseconds
        formatters_.push_back(details::make_unique<details::f_formatter<Padder>>(padding));
        break;

    case ('F'): // nanoseconds
        formatters_.push_back(details::make_unique<details::F_formatter<Padder>>(padding));
        break;

    case ('E'): // seconds since epoch
        formatters_.push_back(details::make_unique<details::E_formatter<Padder>>(padding));
        break;

    case ('p'): // am/pm
        formatters_.push_back(details::make_unique<details::p_formatter<Padder>>(padding));
        break;

    case ('r'): // 12 hour clock 02:55:02 pm
        formatters_.push_back(details::make_unique<details::r_formatter<Padder>>(padding));
        break;

    case ('R'): // 24-hour HH:MM time
        formatters_.push_back(details::make_unique<details::R_formatter<Padder>>(padding));
        break;

    case ('T'):
    case ('X'): // ISO 8601 time format (HH:MM:SS)
        formatters_.push_back(details::make_unique<details::T_formatter<Padder>>(padding));
        break;

    case ('z'): // timezone
        formatters_.push_back(details::make_unique<details::z_formatter<Padder>>(padding));
        break;

    case ('P'): // pid
        formatters_.push_back(details::make_unique<details::pid_formatter<Padder>>(padding));
        break;

    case ('^'): // color range start
        formatters_.push_back(details::make_unique<details::color_start_formatter>(padding));
        break;

    case ('$'): // color range end
        formatters_.push_back(details::make_unique<details::color_stop_formatter>(padding));
        break;

    case ('@'): // source location (filename:filenumber)
        formatters_.push_back(details::make_unique<details::source_location_formatter<Padder>>(padding));
        break;

    case ('s'): // short source filename - without directory name
        formatters_.push_back(details::make_unique<details::short_filename_formatter<Padder>>(padding));
        break;

    case ('g'): // full source filename
        formatters_.push_back(details::make_unique<details::source_filename_formatter<Padder>>(padding));
        break;

    case ('#'): // source line number
        formatters_.push_back(details::make_unique<details::source_linenum_formatter<Padder>>(padding));
        break;

    case ('!'): // source funcname
        formatters_.push_back(details::make_unique<details::source_funcname_formatter<Padder>>(padding));
        break;

    case ('%'): // % char
        formatters_.push_back(details::make_unique<details::ch_formatter>('%'));
        break;

    case ('u'): // elapsed time since last log message in nanos
        formatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::nanoseconds>>(padding));
        break;

    case ('i'): // elapsed time since last log message in micros
        formatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::microseconds>>(padding));
        break;

    case ('o'): // elapsed time since last log message in millis
        formatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::milliseconds>>(padding));
        break;

    case ('O'): // elapsed time since last log message in seconds
        formatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::seconds>>(padding));
        break;

3. Output form of log: file / print / remote print

After solving the problem of the format of the previous log generation, let's analyze the destination of the log.
spdlog provides a variety of log destinations. In order to be compatible with different platforms, it implements log destinations under different platforms, but these are roughly divided into these categories

  1. Print local console directly
  2. Print to remote console
  3. Write to file
  4. Custom type (you can define where your log should be output according to the standard format!!!)
    For the first type of printing to the console, it is the simplest one. The most direct is to use printf. spdlog also realizes color printing for us, that is, the color information of the console is attached to the printing information, which can control the color of the printed characters!
    Their corresponding implementation class is
include\spdlog\sinks\stdout_color_sinks.h    # Color printout
include\spdlog\sinks\stdout_sinks.h          # Colorless printout

For the second category, spdlog mainly provides an example of a tcp client, which can write local logs to remote tcp clients
His realization is

include\spdlog\sinks\tcp_sink.h    # Note that the old version does not have this file. The author is in version 1.8.5

For the third kind of writing files, the realization is more diversified.
① The simplest and crudest way is to write a log file directly without limiting the size. This has great defects, but it is also simple.

# include\spdlog\sinks\basic_file_sink.h

② Rolling file type: set the maximum size and the maximum number of files of a file, and the system can automatically write the file. If the file size exceeds the set maximum size of a single file, the current file will be renamed, and then another file will continue to write the log.

//include\spdlog\sinks\rotating_file_sink.h
 // Rotate files:
    // log.txt -> log.1.txt
    // log.1.txt -> log.2.txt
    // log.2.txt -> log.3.txt
    // log.3.txt -> delete

③ Is to write files regularly, and then name them by time. According to the cycle, it is divided into daily update and hourly update

# include\spdlog\sinks\daily_file_sink.h
# Generator of daily log file names in format basename.YYYY-MM-DD.ext
# include\spdlog\sinks\hourly_file_sink.h
# Generator of Hourly log file names in format basename.YYYY-MM-DD-HH.ext

④ Finally, simply record the customized log type. This log library is used in the project, but we hope that the product of the project can be used as the TCP server. When our debugger is connected to the product, the product will print the real-time log to the debugger (computer).
In fact, the main thing is to realize it_ sink_it () and_ flush () function, I won't go into detail here.

4 . Multiple output of logs

When it is necessary to use the log output function on the console, it is also necessary to use the log output function.
This is mentioned in the default demo

// spdlog\example\example.cpp
// A logger with multiple sinks (stdout and file) - each with a different format and log level.
void multi_sink_example()
{
    auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    console_sink->set_level(spdlog::level::warn);
    console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v");

    auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true);
    file_sink->set_level(spdlog::level::trace);

    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");
}

I also need to learn more skills.

Topics: C++ Linux