spdlog source code reading (2): creation and use of sinks

Keywords: C++ Fragment

2. sink creation

2.1 or rotating_file_sink

We still take rotating_file_sink as an example to illustrate the process of sink creation in spdlog.
The file_log.cpp file can be found in spdlog-master/tests. The example code for rotate is as follows:

TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
{
    1. prepare_logdir();
    2. std::string basename = "logs/rotating_log";
    3. auto logger = spdlog::rotating_logger_mt("logger", basename, 1024, 0);
    //......
}

line: 3. Create a file named "logger" and "rotating_log". Under the folder logs, the maximum file size is 1024 (bytes).
And there can only be one file. The logger object is returned, and it supports multiple threads (mt).

2.2 create

Continue to view rotating_logger_mt as follows:

inline std::shared_ptr<spdlog::logger> spdlog::rotating_logger_mt(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files)
{
    1. return create<spdlog::sinks::rotating_file_sink_mt>(logger_name, filename, SPDLOG_FILENAME_T("txt"), max_file_size, max_files);
}

line: 1. Actually create objects. Create is a template function, as follows:

template <typename Sink, typename... Args>
inline std::shared_ptr<spdlog::logger> spdlog::create(const std::string& logger_name, Args... args)
{
    2. sink_ptr sink = std::make_shared<Sink>(args...);
    3. return details::registry::instance().create(logger_name, { sink });
}

In this example, Sink is spdlog::sinks::rotating_file_sink_mt, and the second template parameter is
The constructor parameter of rotating_file_sink_mt. For each sink, there will be a corresponding create function.
Of course, the actual creation of sink occurs in this function (line2).

2.3 Single registry

line:3 in 1.2 continues to call the creation of registry execution object. Let's first look at what registry is.

#ifdef SPDLOG_NO_REGISTRY_MUTEX
typedef registry_t<spdlog::details::null_mutex> registry;
#else
typedef registry_t<std::mutex> registry;
#endif

It can be seen that in the case of multi-threading, class registry needs lock support.
Here's where we need to be most concerned, that is, the final create, which is coded as follows:

template<class It>
std::shared_ptr<logger> create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end)
{
    1. std::lock_guard<Mutex> lock(_mutex);
    throw_if_exists(logger_name);
    std::shared_ptr<logger> new_logger;
    2. if (_async_mode)
        new_logger = std::make_shared<async_logger>(logger_name, sinks_begin, sinks_end, _async_q_size, _overflow_policy, _worker_warmup_cb, _flush_interval_ms, _worker_teardown_cb);
    else
        new_logger = std::make_shared<logger>(logger_name, sinks_begin, sinks_end);

    3. if (_formatter)
        new_logger->set_formatter(_formatter);

    if (_err_handler)
        new_logger->set_error_handler(_err_handler);

    new_logger->set_level(_level);


    //Add to registry
    4. _loggers[logger_name] = new_logger;
    return new_logger;
}
  • line1: In the case of multithreading, creating a logger object requires locking.
  • line2: Distinguish between synchronous and asynchronous log writing, where the actual logger object creation occurs.
  • line3: When you have a custom format, use a custom format.
  • line4: Adding newly created objects to global management (registry is a singleton), which is why locks are required.

2.4 summary

From the above process, we can see that the actual creation of sink takes place in create, and the sink object created participates as a parameter.
Creation of logger objects. What's the role of sink in logger? That's what we're going to discuss next.

3. Use of sink

Still continue with the 2.1 example. Look at the code that was omitted at that time.

TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
{
    //......
    for (int i = 0; i < 10; ++i)
        1. logger->info("Test message {}", i);
    //......
}

line1: Call info to output the log and trace the code to see what info is doing.

//Fragment 1
template <typename... Args>
inline void spdlog::logger::log(level::level_enum lvl, const char* msg)
{
    details::log_msg log_msg(&_name, lvl);
    log_msg.raw << msg;
    1. _sink_it(log_msg);
}

//Fragment 2
inline void spdlog::logger::_sink_it(details::log_msg& msg)
{
    _formatter->format(msg);
    2. for (auto &sink : _sinks)
    {
    if( sink->should_log( msg.level))
    {
        3. sink->log(msg);
    }
    }

    if(_should_flush_on(msg))
    flush();
}
  • line1: Create a log_msg object and store the MSG into the object to execute _sink_it
  • line2: Traverses through all sinks in the current object and executes its log function for each sink.

So far, we have joined the sink in the previous article.

3.1 summary

This article is nearing the end. From the previous analysis, we can get the following key information:

  1. log_msg is the location where spdlog really stores log information and is naturally the final output object.
  2. Sik distinguishes mt/st by base_sink.
  3. registry is a singleton that stores logger objects, and logger is the real executor of log output.
  4. spdlog determines the final instantiated logger is mt/st by creating different sink s, and it can also determine the final output target.
  5. User-defined formats take effect when _sink_it is in logger.

The next article starts with log_msg.

Posted by releasedj on Mon, 08 Apr 2019 18:36:30 -0700