Skip to content

Introducing: KDSPDSetup Initialize loggers with simple config files and focus on your code's functionality

KDAB’s newest open-source library is now publicly available: KDSPDSetup!

KDSPDSetup is a small library written in modern C++ (≥ 20) that initializes objects from the spdlog library by reading a toml configuration file. This makes setting up loggers more convenient and results in cleaner-looking code.

Let’s take a look at an example to see how useful it can be. We’ll read the config from a file logConfig.toml and do some stuff with the loggers KDSPDSetup creates for us.

#include <KDSpdSetup/kdspdsetup.h>

void doStuff() {
    auto log1 = spdlog::get("log1");
    log1->info("both console and basic file sink get this message");
    log1->debug("only basic file sink gets this message");
}

void doOtherStuff(std::shared_ptr<spdlog::logger> logger) {
    logger->error("both basic and daily sinks get this message");
    logger->info("only basic file sink gets this message");
}

int main() {
    KDSPDSetup::setupFrom("logConfig.toml");
    doStuff();
    doOtherStuff(spdlog::get("log2"));
}

The config file looks like this:

[[pattern]]
name = "examplePattern1"
value = "[%^%l%$] %v"

[[pattern]]
name = "examplePattern2"
value = "%c-%L: %v"

[[sink]]
name = "consoleSinkSt"
type = "stdout_color_sink_st"
level = "info"

[[sink]]
name = "fileSinkMt"
type = "basic_file_sink_st"
filename = "logs/basicFile"
truncate = true
level = "debug"

[[sink]]
name = "dailySinkMt"
type = "daily_file_sink_mt"
base_filename = "logs/dailyFile"
rotation_hour = 4
rotation_minute = 0
level = "warn"

[[thread_pool]]
name = "threadpool"
queue_size = 2048
num_threads = 4

[[logger]]
name = "log1"
sinks = ["consoleSinkSt", "fileSinkMt"]
level = "trace"
pattern = "examplePattern1"

[[logger]]
name = "log2"
sinks = ["fileSinkMt", "dailySinkMt"]
overflow_policy = "overrun_oldest"
level = "debug"
pattern = "examplePattern2"

It’s pretty easy to read and edit this, as there’s not much syntactic noise. Plus, we get to detach all these details from our code, where we focus on functionality and use the loggers where needed.

Without KDSPDSetup, we might have something like this:

#include <spdlog/async.h>
#include <spdlog/async_logger.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/daily_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>

void initLoggers() {
    auto const examplePattern1 = "[%^%l%$] %v";
    auto const examplePattern2 = "%c-%L: %v";

    auto consoleSinkSt =
        std::make_shared<spdlog::sinks::stdout_color_sink_st>();
    
    consoleSinkSt->set_level(spdlog::level::info);

    auto fileSinkMt = std::make_shared<spdlog::sinks::basic_file_sink_st>(
        "logs/basicFile", true);
    
    fileSinkMt->set_level(spdlog::level::debug);

    auto dailySinkMt = std::make_shared<spdlog::sinks::daily_file_sink_mt>(
        "logs/dailyFile", 4, 0);
    
    dailySinkMt->set_level(spdlog::level::warn);

    auto static threadpool =
        std::make_shared<spdlog::details::thread_pool>(2048, 4);

    auto logger1 = std::make_shared<spdlog::logger>(
        spdlog::logger("log1", {consoleSinkSt, fileSinkMt}));
    
    logger1->set_level(spdlog::level::trace);
    logger1->set_pattern(examplePattern1);

    auto logger2 = std::make_shared<spdlog::async_logger>(
        spdlog::async_logger("log2", {fileSinkMt, dailySinkMt}, threadpool,
                             spdlog::async_overflow_policy::overrun_oldest));
    
    logger2->set_level(spdlog::level::debug);
    logger2->set_pattern(examplePattern2);

    spdlog::register_logger(logger1);
    spdlog::register_logger(logger2);
}

void doStuff() {
    auto log1 = spdlog::get("log1");
    log1->info("both console and basic file sink get this message");
    log1->debug("only basic file sink gets this message");
}

void doOtherStuff(std::shared_ptr<spdlog::logger> logger) {
    logger->error("both basic and daily sinks get this message");
    logger->info("only basic file sink gets this message");
}

int main() {
    initLoggers();
    doStuff();
    doOtherStuff(spdlog::get("log2"));
}

There’s a lot of code to accidentally write slightly wrong, and it takes a bit longer to look at references and remember to include all the right headers. KDSPDSetup will do all this for you, so you can spend your time and energy on your program’s functionality.

KDSPDSetup’s development was motivated by KDGpu dropping the dependency spdlog_setup, which served the same purpose, but had not been updated in four years. Although KDGpu developers removed the dependency, it was useful, so they wanted a replacement.

KDSPDSetup was designed to work with the same existing config files that were previously used with spdlog_setup, so it could be used as a drop-in replacement.

These files allow one to fully configure:

  • Sinks of several types, formatting patterns, and thread pools (for async loggers), with names so they can be referenced by name when configuring loggers.
  • A global pattern and global thread pool that loggers will use when these are not specified in the logger’s configuration.
  • Loggers (async or not) with names to register with spdlog, along with the sinks, patterns, and, for async loggers, thread pools to use.

The unstable branch is now officially available on GitHub, and can be used under the terms of the MIT License. Consider giving it a try if you use spdlog and think this would be helpful!

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

Categories: C++ / KDAB Blogs

Tags:
Leave a Reply

Your email address will not be published. Required fields are marked *