Verified Commit 421c5ed1 authored by Alberto Miranda's avatar Alberto Miranda ♨️
Browse files

Heavy refactor to `logger` module

- Move as much code as possible to `logger.cpp`
- Create `logger_base` with common code
- Create `logger_sync` and `logger_async` classes to allow creating
  synchronous and asynchronous loggers as needed.
- Replace all mentions to `global_logger` by `default_logger`
parent 1d6e45a2
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -299,7 +299,7 @@ find_package(RedisPlusPlus 1.3.3 REQUIRED)
# set compile flags
add_compile_options("-Wall" "-Wextra" "-Werror" "$<$<CONFIG:RELEASE>:-O3>")
add_compile_definitions("$<$<CONFIG:DEBUG,ASan>:SCORD_DEBUG_BUILD>")
add_compile_definitions("$<$<CONFIG:DEBUG,ASan>:__LOGGER_ENABLE_DEBUG__>")
add_compile_definitions("$<$<CONFIG:DEBUG,ASan>:LOGGER_ENABLE_DEBUG>")

add_subdirectory(etc)
add_subdirectory(src)
+139 −6
Original line number Diff line number Diff line
@@ -22,13 +22,25 @@
 * SPDX-License-Identifier: GPL-3.0-or-later
 *****************************************************************************/

#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/stdout_sinks.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/syslog_sink.h>
#include <utility>
#include <utils/utils.hpp>
#include <cstdarg>
#include "logger.hpp"
#include "logger.h"

////////////////////////////////////////////////////////////////////////////////
// Function implementations for the C API
////////////////////////////////////////////////////////////////////////////////

void
logger_setup(const char* ident, logger_type type, const char* log_file) {

    constexpr auto get_cxx_type = [](logger_type t) {
        switch(t) {
            case CONSOLE_LOGGER:
@@ -43,15 +55,17 @@ logger_setup(const char* ident, logger_type type, const char* log_file) {
                return logger::logger_type::console;
        }
    };
    logger::create_global_logger(ident, get_cxx_type(type), log_file);

    logger::create_default_logger(
            logger::logger_config{ident, get_cxx_type(type), log_file});
}

void
logger_log(enum logger_level level, const char* fmt, ...) {

    using logger::logger;
    using logger::logger_base;

    if(const auto logger = logger::get_global_logger(); logger) {
    if(const auto logger = logger_base::get_default_logger(); logger) {

        std::array<char, LOGGER_MSG_MAX_LEN> msg; // NOLINT
        va_list args;
@@ -81,9 +95,128 @@ logger_log(enum logger_level level, const char* fmt, ...) {

void
logger_destroy() {
    using logger::logger;
    using logger::logger_base;

    if(logger_base::get_default_logger()) {
        ::logger::destroy_default_logger();
    }
}

////////////////////////////////////////////////////////////////////////////////
// Function implementations for the C++ API
////////////////////////////////////////////////////////////////////////////////

namespace {

/**
 * @brief Creates a logger of the given type.
 *
 * @tparam Logger Type of the logger to create.
 * @param config Configuration for the logger.
 * @return std::shared_ptr<spdlog::logger> Pointer to the created logger.
 */
template <typename Logger>
std::shared_ptr<spdlog::logger>
create_logger(const logger::logger_config& config)
        requires(std::is_same_v<Logger, logger::sync_logger> ||
                 std::is_same_v<Logger, logger::async_logger>) {

    const auto create_helper = [&config]() {
        switch(config.type()) {
            case logger::console: {
                if constexpr(std::is_same_v<Logger, logger::sync_logger>) {
                    return spdlog::stdout_logger_st(config.ident());
                }
                return spdlog::stdout_logger_mt<spdlog::async_factory>(
                        config.ident());
            }
            case logger::console_color:
                if constexpr(std::is_same_v<Logger, logger::sync_logger>) {
                    return spdlog::stdout_color_st(config.ident());
                }
                return spdlog::stdout_color_mt<spdlog::async_factory>(
                        config.ident());
            case logger::file:
                if constexpr(std::is_same_v<Logger, logger::sync_logger>) {
                    return spdlog::basic_logger_st(
                            config.ident(), config.log_file().value_or(""),
                            true);
                }
                return spdlog::basic_logger_mt<spdlog::async_factory>(
                        config.ident(), config.log_file().value_or(""), true);
            case logger::syslog:
                if constexpr(std::is_same_v<Logger, logger::sync_logger>) {
                    return spdlog::syslog_logger_st("syslog", config.ident(),
                                                    LOG_PID);
                }
                return spdlog::syslog_logger_mt("syslog", config.ident(),
                                                LOG_PID);
            default:
                throw std::invalid_argument("Unknown logger type");
        }
    };

    try {
        auto logger = create_helper();
        assert(logger != nullptr);
        logger->set_pattern(logger::default_pattern);

    if(logger::get_global_logger()) {
        ::logger::destroy_global_logger();
#ifdef LOGGER_ENABLE_DEBUG
        logger->set_level(spdlog::level::debug);
#endif
        spdlog::drop_all();
        return logger;
    } catch(const spdlog::spdlog_ex& ex) {
        throw std::runtime_error("logger initialization failed: " +
                                 std::string(ex.what()));
    }
}

} // namespace

namespace logger {

logger_base::logger_base(logger::logger_config config)
    : m_config(std::move(config)),
      m_internal_logger(::create_logger<async_logger>(m_config)) {}

const logger_config&
logger_base::config() const {
    return m_config;
}

std::shared_ptr<logger_base>&
logger_base::get_default_logger() {
    static std::shared_ptr<logger_base> s_global_logger;
    return s_global_logger;
}

void
logger_base::enable_debug() const {
    m_internal_logger->set_level(spdlog::level::debug);
}

void
logger_base::flush() {
    m_internal_logger->flush();
}

async_logger::async_logger(const logger_config& config) : logger_base(config) {
    try {
        m_internal_logger = ::create_logger<async_logger>(config);
    } catch(const spdlog::spdlog_ex& ex) {
        throw std::runtime_error("logger initialization failed: " +
                                 std::string(ex.what()));
    }
}

sync_logger::sync_logger(const logger_config& config) : logger_base(config) {
    try {
        m_internal_logger = ::create_logger<sync_logger>(config);
    } catch(const spdlog::spdlog_ex& ex) {
        throw std::runtime_error("logger initialization failed: " +
                                 std::string(ex.what()));
    }
}

} // namespace logger
+148 −114
Original line number Diff line number Diff line
@@ -25,12 +25,7 @@
#ifndef SCORD_LOGGER_HPP
#define SCORD_LOGGER_HPP

#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/stdout_sinks.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/syslog_sink.h>
#include <spdlog/logger.h>
#include <fmt/ostream.h>
#include <filesystem>
#include <optional>
@@ -48,8 +43,6 @@ ptr(const T* p) {
} // namespace fmt
#endif // FMT_VERSION

namespace fs = std::filesystem;

namespace logger {

enum logger_type {
@@ -65,7 +58,7 @@ public:
    logger_config() = default;

    explicit logger_config(std::string ident, logger_type type,
                           std::optional<fs::path> log_file = {})
                           std::optional<std::filesystem::path> log_file = {})
        : m_ident(std::move(ident)), m_type(type),
          m_log_file(std::move(log_file)) {}

@@ -79,7 +72,7 @@ public:
        return m_type;
    }

    const std::optional<fs::path>&
    const std::optional<std::filesystem::path>&
    log_file() const {
        return m_log_file;
    }
@@ -87,101 +80,91 @@ public:
private:
    std::string m_ident;
    logger_type m_type = console_color;
    std::optional<fs::path> m_log_file;
    std::optional<std::filesystem::path> m_log_file;
};

class logger {

public:
    logger(const std::string& ident, logger_type type,
           const fs::path& log_file = "") {

        try {

            switch(type) {
                case console:
                    m_internal_logger =
                            spdlog::stdout_logger_mt<spdlog::async_factory>(
                                    ident);
                    break;
                case console_color:
                    m_internal_logger =
                            spdlog::stdout_color_mt<spdlog::async_factory>(
                                    ident);
                    break;
                case file:
                    m_internal_logger =
                            spdlog::basic_logger_mt<spdlog::async_factory>(
                                    ident, log_file.string(), true);
                    break;
                case syslog:
                    m_internal_logger =
                            spdlog::syslog_logger_mt("syslog", ident, LOG_PID);
                    break;
                default:
                    throw std::invalid_argument("Unknown logger type");
            }
/**
 * @brief The default log pattern
 *
 * This is the default log pattern used by the logger.
 * It can be used to create new loggers with the same
 * configuration.
 *
 * The default log pattern is:
 *
 * @code
 * %^[%Y-%m-%d %T.%f] [%n] [%t] [%l]%$ %v
 * @endcode
 *
 * The output of the default log pattern is:
 *
 * @code
 * [2021-01-01 00:00:00.000000] [scord] [12345] [info] Message
 * @endcode
 *
 * Where:
 * - 2021-01-01 00:00:00.000000 is the current date and time
 * - scord is the name of the logger
 * - 12345 is the thread id
 * - info is the log level
 * - Message is the log message
 *
 * The following format specifiers are available:
 *   %Y - Year in 4 digits
 *   %m - month 1-12
 *   %d - day 1-31
 *   %T - ISO 8601 time format (HH:MM:SS)
 *   %f - microsecond part of the current second
 *   %E - epoch (microseconds precision)
 *   %n - logger's name
 *   %t - thread id
 *   %l - log level
 *   %v - message
 */
static constexpr auto default_pattern =
        "%^[%Y-%m-%d %T.%f] [%n] [%t] [%l]%$ %v";

/**
 * @brief The logger_base class
 *
 * This class is a wrapper around spdlog::logger, and provides common
 * functionality to all the logger implementations. It also provides a
 * default logger that can be used by the rest of the code by using the
 * static member function get_default_logger().
 *
 * @note This class should not be used directly. It is intended to serve as a
 *     base class for the different logger implementations.
 */
class logger_base {

            assert(m_internal_logger != nullptr);

            // %Y - Year in 4 digits
            // %m - month 1-12
            // %d - day 1-31
            // %T - ISO 8601 time format (HH:MM:SS)
            // %f - microsecond part of the current second
            // %E - epoch (microseconds precision)
            // %n - logger's name
            // %t - thread id
            // %l - log level
            // %v - message
            // m_internal_logger->set_pattern("[%Y-%m-%d %T.%f] [%E] [%n] [%t]
            // [%l] %v");
            m_internal_logger->set_pattern(
                    "%^[%Y-%m-%d %T.%f] [%n] [%t] [%l]%$ %v");

#ifdef __LOGGER_ENABLE_DEBUG__
            enable_debug();
#endif

            spdlog::drop_all();

            // globally register the logger so that it can be accessed
            // using spdlog::get(logger_name)
            // spdlog::register_logger(m_internal_logger);
        } catch(const spdlog::spdlog_ex& ex) {
            throw std::runtime_error("logger initialization failed: " +
                                     std::string(ex.what()));
        }
    }
protected:
    logger_base() = default;
    explicit logger_base(logger_config config);

    logger(const logger& /*rhs*/) = delete;
    logger&
    operator=(const logger& /*rhs*/) = delete;
    logger(logger&& /*other*/) = default;
    logger&
    operator=(logger&& /*other*/) = default;
public:
    logger_base(const logger_base& /*rhs*/) = delete;
    logger_base&
    operator=(const logger_base& /*rhs*/) = delete;

    ~logger() {
        spdlog::shutdown();
    }
protected:
    logger_base(logger_base&& /*other*/) = default;
    logger_base&
    operator=(logger_base&& /*other*/) = default;
    ~logger_base() = default;

    static std::shared_ptr<logger>&
    get_global_logger() {
        static std::shared_ptr<logger> s_global_logger;
        return s_global_logger;
    }
public:
    const logger_config&
    config() const;

    // the following member functions can be used to interact
    // with a specific logger instance
    inline void
    enable_debug() const {
        m_internal_logger->set_level(spdlog::level::debug);
    }
    static std::shared_ptr<logger_base>&
    get_default_logger();

    inline void
    flush() {
        m_internal_logger->flush();
    }
    void
    enable_debug() const;

    void
    flush();

    template <typename... Args>
    inline void
@@ -207,7 +190,7 @@ public:
        m_internal_logger->error(fmt, std::forward<Args>(args)...);
    }

    static inline std::string
    [[maybe_unused]] static inline std::string
    errno_message(int errno_value) {
        // 1024 should be more than enough for most locales
        constexpr const std::size_t MAX_ERROR_MSG = 1024;
@@ -272,7 +255,7 @@ public:
    }

    template <typename... Args>
    static inline std::string
    [[deprecated]] [[maybe_unused]] static inline std::string
    build_message(Args&&... args) {

        // see:
@@ -289,37 +272,88 @@ public:
        return ss.str();
    }

private:
protected:
    logger_config m_config;
    std::shared_ptr<spdlog::logger> m_internal_logger;
    std::string m_type;
};

/**
 * @brief Synchronous logger implementation
 *
 * This class is a wrapper around spdlog::logger. It provides
 * a synchronous interface to the spdlog logger.
 */
class sync_logger : public logger_base {
public:
    explicit sync_logger(const logger_config& config);
};

/**
 * @brief Asynchronous logger implementation
 *
 * This class is a wrapper around spdlog::async_logger. It
 * provides an asynchronous interface to the spdlog logger.
 */
class async_logger : public logger_base {
public:
    explicit async_logger(const logger_config& config);
};

// the following static functions can be used to interact
// with a globally registered logger instance
// with a globally registered async logger instance

/**
 * @brief Create a default logger instance
 *
 * @tparam Args variadic template parameter pack for the logger constructor
 * arguments
 * @param args arguments for the logger constructor
 */
template <typename... Args>
static inline void
create_global_logger(Args&&... args) {
    logger::get_global_logger() = std::make_shared<logger>(args...);
create_default_logger(Args&&... args) {
    async_logger::get_default_logger() =
            std::make_shared<async_logger>(args...);
}

static inline void
register_global_logger(logger&& lg) {
    logger::get_global_logger() = std::make_shared<logger>(std::move(lg));
/**
 * @brief Register an existing logger instance as the default logger
 *
 * @param config logger configuration
 */
[[maybe_unused]] static inline void
set_default_logger(async_logger&& lg) {
    async_logger::get_default_logger() =
            std::make_shared<async_logger>(std::move(lg));
}

static inline void
destroy_global_logger() {
    logger::get_global_logger().reset();
/**
 * @brief Destroy the default logger instance
 */
[[maybe_unused]] static inline void
destroy_default_logger() {
    async_logger::get_default_logger().reset();
}

static inline void
flush_global_logger() {
    if(logger::get_global_logger()) {
        logger::get_global_logger()->flush();
    }
/**
 * @brief Get the default logger instance
 *
 * @return A shared pointer to the default logger instance
 */
[[maybe_unused]] static inline auto
get_default_logger() {
    return async_logger::get_default_logger();
}

/**
 * @brief Flush the default logger instance
 */
[[maybe_unused]] static inline void
flush_default_logger() {
    if(auto lg = async_logger::get_default_logger(); lg) {
        lg->flush();
    }
}

} // namespace logger

+18 −26
Original line number Diff line number Diff line
@@ -31,32 +31,29 @@

#define LOGGER_INFO(...)                                                       \
    do {                                                                       \
        using logger::logger;                                                  \
        if(logger::get_global_logger()) {                                      \
            logger::get_global_logger()->info(__VA_ARGS__);                    \
        if(logger::get_default_logger()) {                                     \
            logger::get_default_logger()->info(__VA_ARGS__);                   \
        }                                                                      \
    } while(0);


#ifdef __LOGGER_ENABLE_DEBUG__
#ifdef LOGGER_ENABLE_DEBUG

#define LOGGER_DEBUG(...)                                                      \
    do {                                                                       \
        using logger::logger;                                                  \
        if(logger::get_global_logger()) {                                      \
            logger::get_global_logger()->debug(__VA_ARGS__);                   \
        if(logger::get_default_logger()) {                                     \
            logger::get_default_logger()->debug(__VA_ARGS__);                  \
        }                                                                      \
    } while(0);

#define LOGGER_FLUSH()                                                         \
    do {                                                                       \
        using logger::logger;                                                  \
        if(logger::get_global_logger()) {                                      \
            logger::get_global_logger()->flush();                              \
        if(logger::get_default_logger()) {                                     \
            logger::get_default_logger()->flush();                             \
        }                                                                      \
    } while(0);

#else // ! __LOGGER_ENABLE_DEBUG__
#else // ! LOGGER_ENABLE_DEBUG

#define LOGGER_DEBUG(...)                                                      \
    do {                                                                       \
@@ -65,38 +62,33 @@
    do {                                                                       \
    } while(0);

#endif // __LOGGER_ENABLE_DEBUG__
#endif // LOGGER_ENABLE_DEBUG

#define LOGGER_WARN(...)                                                       \
    do {                                                                       \
        using logger::logger;                                                  \
        if(logger::get_global_logger()) {                                      \
            logger::get_global_logger()->warn(__VA_ARGS__);                    \
        if(logger::get_default_logger()) {                                     \
            logger::get_default_logger()->warn(__VA_ARGS__);                   \
        }                                                                      \
    } while(0);

#define LOGGER_ERROR(...)                                                      \
    do {                                                                       \
        using logger::logger;                                                  \
        if(logger::get_global_logger()) {                                      \
            logger::get_global_logger()->error(__VA_ARGS__);                   \
        if(logger::get_default_logger()) {                                     \
            logger::get_default_logger()->error(__VA_ARGS__);                  \
        }                                                                      \
    } while(0);

#define LOGGER_ERRNO(...)                                                      \
    do {                                                                       \
        using logger::logger;                                                  \
        if(logger::get_global_logger()) {                                      \
            logger::get_global_logger()->error_errno(__VA_ARGS__);             \
        if(logger::get_default_logger()) {                                     \
            logger::get_default_logger()->error_errno(__VA_ARGS__);            \
        }                                                                      \
    } while(0);

#define LOGGER_CRITICAL(...)                                                   \
    do {                                                                       \
        using logger::logger;                                                  \
        using logger::logger;                                                  \
        if(logger::get_global_logger()) {                                      \
            logger::get_global_logger()->critical(__VA_ARGS__);                \
        if(logger::get_default_logger()) {                                     \
            logger::get_default_logger()->critical(__VA_ARGS__);               \
        }                                                                      \
    } while(0);

@@ -136,7 +128,7 @@

#define LOGGER_INFO(fmt, ...) LOGGER_LOG(info, fmt, ##__VA_ARGS__);

#ifdef __LOGGER_ENABLE_DEBUG__
#ifdef LOGGER_ENABLE_DEBUG
#define LOGGER_DEBUG(fmt, ...) LOGGER_LOG(debug, fmt, ##__VA_ARGS__);
#endif

+5 −27
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ using namespace std::literals;
namespace network {

server::server(std::string name, std::string address, bool daemonize,
               fs::path rundir)
               std::filesystem::path rundir)
    : m_name(std::move(name)), m_address(std::move(address)),
      m_daemonize(daemonize), m_rundir(std::move(rundir)),
      m_pidfile(daemonize ? std::make_optional(m_rundir / (m_name + ".pid"))
@@ -88,7 +88,7 @@ server::daemonize() {
    // this (and since we want to be able to output messages from all
    // processes), we destroy it now and recreate it post-fork() both in the
    // parent process and in the child.
    logger::destroy_global_logger();
    logger::destroy_default_logger();

    /* Fork off the parent process */
    m_signal_listener.notify_fork(fork_event::fork_prepare);
@@ -213,7 +213,7 @@ server::signal_handler(int signum) {

        case SIGHUP:
            LOGGER_WARN("A signal (SIGHUP) occurred.");
            logger::flush_global_logger();
            logger::flush_default_logger();
            break;

        default:
@@ -223,29 +223,7 @@ server::signal_handler(int signum) {

void
server::init_logger() {

    switch(m_logger_config.type()) {
        case logger::logger_type::console_color:
            logger::create_global_logger(m_logger_config.ident(),
                                         logger::logger_type::console_color);
            break;
        case logger::logger_type::syslog:
            logger::create_global_logger(m_logger_config.ident(),
                                         logger::logger_type::syslog);
            break;
        case logger::logger_type::file:
            if(m_logger_config.log_file().has_value()) {
                logger::create_global_logger(m_logger_config.ident(),
                                             logger::logger_type::file,
                                             *m_logger_config.log_file());
                break;
            }
            [[fallthrough]];
        case logger::logger_type::console:
            logger::create_global_logger(m_logger_config.ident(),
                                         logger::logger_type::console);
            break;
    }
    logger::create_default_logger(m_logger_config);
}

void
@@ -382,7 +360,7 @@ server::teardown() {

    LOGGER_INFO("* Removing pidfile...");
    std::error_code ec;
    fs::remove(*m_pidfile, ec);
    std::filesystem::remove(*m_pidfile, ec);

    if(ec) {
        LOGGER_ERROR("Failed to remove pidfile {}: {}", *m_pidfile,
Loading