mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
feat: Use spdlog logger (#2372)
This commit is contained in:
@@ -108,6 +108,8 @@ public:
|
||||
|
||||
ioc.stop();
|
||||
LOG(util::LogService::info()) << "io_context stopped";
|
||||
|
||||
util::LogService::shutdown();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "app/VerifyConfig.hpp"
|
||||
#include "migration/MigrationApplication.hpp"
|
||||
#include "rpc/common/impl/HandlerProvider.hpp"
|
||||
#include "util/ScopeGuard.hpp"
|
||||
#include "util/TerminationHandler.hpp"
|
||||
#include "util/config/ConfigDefinition.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
@@ -37,6 +38,7 @@ int
|
||||
main(int argc, char const* argv[])
|
||||
try {
|
||||
util::setTerminationHandler();
|
||||
util::ScopeGuard loggerShutdownGuard{[]() { util::LogService::shutdown(); }};
|
||||
|
||||
auto const action = app::CliArgs::parse(argc, argv);
|
||||
return action.apply(
|
||||
|
||||
@@ -50,8 +50,6 @@ target_link_libraries(
|
||||
clio_util
|
||||
PUBLIC Boost::headers
|
||||
Boost::iostreams
|
||||
Boost::log
|
||||
Boost::log_setup
|
||||
fmt::fmt
|
||||
openssl::openssl
|
||||
xrpl::libxrpl
|
||||
@@ -59,6 +57,7 @@ target_link_libraries(
|
||||
clio_options
|
||||
clio_rpc_center
|
||||
clio_build_version
|
||||
PRIVATE spdlog::spdlog
|
||||
)
|
||||
|
||||
# FIXME: needed on gcc-12, clang-16 and AppleClang for now (known boost 1.82 issue for some compilers)
|
||||
|
||||
57
src/util/ScopeGuard.hpp
Normal file
57
src/util/ScopeGuard.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2025, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace util {
|
||||
|
||||
/**
|
||||
* @brief Run a function when the scope is exited
|
||||
*/
|
||||
template <typename Func>
|
||||
class ScopeGuard {
|
||||
public:
|
||||
ScopeGuard(ScopeGuard const&) = delete;
|
||||
ScopeGuard(ScopeGuard&&) = delete;
|
||||
ScopeGuard&
|
||||
operator=(ScopeGuard const&) = delete;
|
||||
ScopeGuard&
|
||||
operator=(ScopeGuard&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Create ScopeGuard object.
|
||||
*
|
||||
* @param func The function to run when the scope is exited.
|
||||
*/
|
||||
ScopeGuard(Func func) : func_(std::move(func))
|
||||
{
|
||||
}
|
||||
|
||||
~ScopeGuard()
|
||||
{
|
||||
func_();
|
||||
}
|
||||
|
||||
private:
|
||||
Func func_;
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
@@ -44,6 +44,9 @@ terminationHandler()
|
||||
#else
|
||||
LOG(LogService::fatal()) << "Exit on terminate. Stacktrace disabled.";
|
||||
#endif // CLIO_WITHOUT_STACKTRACE
|
||||
|
||||
// We're calling std::abort later, so spdlog won't be shutdown automatically
|
||||
util::LogService::shutdown();
|
||||
std::abort();
|
||||
}
|
||||
|
||||
|
||||
@@ -358,10 +358,9 @@ getClioConfig()
|
||||
|
||||
{"log_level", ConfigValue{ConfigType::String}.defaultValue("info").withConstraint(gValidateLogLevelName)},
|
||||
|
||||
{"log_format",
|
||||
ConfigValue{ConfigType::String}.defaultValue(
|
||||
R"(%TimeStamp% (%SourceLocation%) [%ThreadID%] %Channel%:%Severity% %Message%)"
|
||||
)},
|
||||
{"spdlog_format", ConfigValue{ConfigType::String}.defaultValue(R"(%Y-%m-%d %H:%M:%S.%f %^%3!l:%n%$ - %v)")},
|
||||
|
||||
{"spdlog_async", ConfigValue{ConfigType::Boolean}.defaultValue(true)},
|
||||
|
||||
{"log_to_console", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
|
||||
|
||||
@@ -369,11 +368,7 @@ getClioConfig()
|
||||
|
||||
{"log_rotation_size", ConfigValue{ConfigType::Integer}.defaultValue(2048).withConstraint(gValidateUint32)},
|
||||
|
||||
{"log_directory_max_size",
|
||||
ConfigValue{ConfigType::Integer}.defaultValue(50 * 1024).withConstraint(gValidateUint32)},
|
||||
|
||||
{"log_rotation_hour_interval",
|
||||
ConfigValue{ConfigType::Integer}.defaultValue(12).withConstraint(gValidateUint32)},
|
||||
{"log_directory_max_files", ConfigValue{ConfigType::Integer}.defaultValue(25).withConstraint(gValidateUint32)},
|
||||
|
||||
{"log_tag_style", ConfigValue{ConfigType::String}.defaultValue("none").withConstraint(gValidateLogTag)},
|
||||
|
||||
|
||||
@@ -263,18 +263,16 @@ private:
|
||||
KV{.key = "log_level",
|
||||
.value = "The general logging level of Clio. This level is applied to all log channels that do not have an "
|
||||
"explicitly defined logging level."},
|
||||
KV{.key = "log_format",
|
||||
.value = "The format string for log messages. The format is described here: "
|
||||
"<https://www.boost.org/doc/libs/1_83_0/libs/log/doc/html/log/tutorial/formatters.html>."},
|
||||
KV{.key = "spdlog_format",
|
||||
.value = "The format string for log messages using spdlog format patterns. Documentation can be found at: "
|
||||
"<https://github.com/gabime/spdlog/wiki/Custom-formatting>."},
|
||||
KV{.key = "spdlog_async", .value = "Whether spdlog is asynchronous or not."},
|
||||
KV{.key = "log_to_console", .value = "Enables or disables logging to the console."},
|
||||
KV{.key = "log_directory", .value = "The directory path for the log files."},
|
||||
KV{.key = "log_rotation_size",
|
||||
.value = "The log rotation size in megabytes. When the log file reaches this particular size, a new log "
|
||||
"file starts."},
|
||||
KV{.key = "log_directory_max_size", .value = "The maximum size of the log directory in megabytes."},
|
||||
KV{.key = "log_rotation_hour_interval",
|
||||
.value = "Represents the interval (in hours) for log rotation. If the current log file reaches this value "
|
||||
"in logging, a new log file starts."},
|
||||
KV{.key = "log_directory_max_files", .value = "The maximum number of log files in the directory."},
|
||||
KV{.key = "log_tag_style",
|
||||
.value =
|
||||
"Log tags are unique identifiers for log messages. `uint`/`int` starts logging from 0 and increments, "
|
||||
|
||||
@@ -25,83 +25,62 @@
|
||||
#include "util/config/ArrayView.hpp"
|
||||
#include "util/config/ConfigDefinition.hpp"
|
||||
#include "util/config/ObjectView.hpp"
|
||||
#include "util/prometheus/Counter.hpp"
|
||||
#include "util/prometheus/Label.hpp"
|
||||
#include "util/prometheus/Prometheus.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time_duration.hpp>
|
||||
#include <boost/log/attributes/attribute_value_set.hpp>
|
||||
#include <boost/log/core/core.hpp>
|
||||
#include <boost/log/expressions/filter.hpp>
|
||||
#include <boost/log/keywords/auto_flush.hpp>
|
||||
#include <boost/log/keywords/file_name.hpp>
|
||||
#include <boost/log/keywords/filter.hpp>
|
||||
#include <boost/log/keywords/format.hpp>
|
||||
#include <boost/log/keywords/max_size.hpp>
|
||||
#include <boost/log/keywords/open_mode.hpp>
|
||||
#include <boost/log/keywords/rotation_size.hpp>
|
||||
#include <boost/log/keywords/target.hpp>
|
||||
#include <boost/log/keywords/target_file_name.hpp>
|
||||
#include <boost/log/keywords/time_based_rotation.hpp>
|
||||
#include <boost/log/sinks/text_file_backend.hpp>
|
||||
#include <boost/log/utility/exception_handler.hpp>
|
||||
#include <boost/log/utility/setup/common_attributes.hpp>
|
||||
#include <boost/log/utility/setup/console.hpp>
|
||||
#include <boost/log/utility/setup/file.hpp>
|
||||
#include <boost/log/utility/setup/formatter_parser.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <spdlog/async.h>
|
||||
#include <spdlog/async_logger.h>
|
||||
#include <spdlog/common.h>
|
||||
#include <spdlog/logger.h>
|
||||
#include <spdlog/sinks/basic_file_sink.h>
|
||||
#include <spdlog/sinks/rotating_file_sink.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <ios>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace util {
|
||||
|
||||
LogService::Data LogService::data{};
|
||||
|
||||
namespace {
|
||||
|
||||
class LoggerExceptionHandler {
|
||||
std::reference_wrapper<util::prometheus::CounterInt> exceptionCounter_ =
|
||||
PrometheusService::counterInt("logger_exceptions_total_number", util::prometheus::Labels{});
|
||||
|
||||
public:
|
||||
using result_type = void;
|
||||
|
||||
LoggerExceptionHandler()
|
||||
{
|
||||
ASSERT(PrometheusService::isInitialised(), "Prometheus should be initialised before Logger");
|
||||
}
|
||||
|
||||
void
|
||||
operator()(std::exception const& e) const
|
||||
{
|
||||
std::cerr << fmt::format("Exception in logger: {}\n", e.what());
|
||||
++exceptionCounter_.get();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Logger LogService::generalLog = Logger{"General"};
|
||||
boost::log::filter LogService::filter{};
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream& stream, Severity sev)
|
||||
spdlog::level::level_enum
|
||||
toSpdlogLevel(Severity sev)
|
||||
{
|
||||
static constexpr std::array<char const*, 6> kLABELS = {
|
||||
switch (sev) {
|
||||
case Severity::TRC:
|
||||
return spdlog::level::trace;
|
||||
case Severity::DBG:
|
||||
return spdlog::level::debug;
|
||||
case Severity::NFO:
|
||||
return spdlog::level::info;
|
||||
case Severity::WRN:
|
||||
return spdlog::level::warn;
|
||||
case Severity::ERR:
|
||||
return spdlog::level::err;
|
||||
case Severity::FTL:
|
||||
return spdlog::level::critical;
|
||||
}
|
||||
return spdlog::level::info;
|
||||
}
|
||||
|
||||
std::string_view
|
||||
toString(Severity sev)
|
||||
{
|
||||
static constexpr std::array<std::string_view, 6> kLABELS = {
|
||||
"TRC",
|
||||
"DBG",
|
||||
"NFO",
|
||||
@@ -110,9 +89,11 @@ operator<<(std::ostream& stream, Severity sev)
|
||||
"FTL",
|
||||
};
|
||||
|
||||
return stream << kLABELS.at(static_cast<int>(sev));
|
||||
return kLABELS.at(static_cast<int>(sev));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief converts the loglevel to string to a corresponding Severity enum value.
|
||||
*
|
||||
@@ -144,60 +125,47 @@ getSeverityLevel(std::string_view logLevel)
|
||||
* @brief Initializes console logging.
|
||||
*
|
||||
* @param logToConsole A boolean indicating whether to log to console.
|
||||
* @param format The log format string.
|
||||
* @return Vector of sinks for console logging.
|
||||
*/
|
||||
static void
|
||||
initConsoleLogging(bool logToConsole, std::string const& format)
|
||||
static std::vector<spdlog::sink_ptr>
|
||||
createConsoleSinks(bool logToConsole)
|
||||
{
|
||||
namespace keywords = boost::log::keywords;
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
|
||||
if (logToConsole) {
|
||||
boost::log::add_console_log(
|
||||
std::cout, keywords::format = format, keywords::filter = LogSeverity < Severity::FTL
|
||||
);
|
||||
auto consoleSink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||
consoleSink->set_level(spdlog::level::trace);
|
||||
sinks.push_back(std::move(consoleSink));
|
||||
}
|
||||
|
||||
// Always print fatal logs to cerr
|
||||
boost::log::add_console_log(std::cerr, keywords::format = format, keywords::filter = LogSeverity >= Severity::FTL);
|
||||
// Always add stderr sink for fatal logs
|
||||
auto stderrSink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
|
||||
stderrSink->set_level(spdlog::level::critical);
|
||||
sinks.push_back(std::move(stderrSink));
|
||||
|
||||
return sinks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes file logging.
|
||||
*
|
||||
* @param config The configuration object containing log settings.
|
||||
* @param format The log format string.
|
||||
* @param dirPath The directory path where log files will be stored.
|
||||
* @return File sink for logging.
|
||||
*/
|
||||
void
|
||||
LogService::initFileLogging(FileLoggingParams const& params, std::string const& format)
|
||||
spdlog::sink_ptr
|
||||
LogService::createFileSink(FileLoggingParams const& params)
|
||||
{
|
||||
namespace keywords = boost::log::keywords;
|
||||
namespace sinks = boost::log::sinks;
|
||||
|
||||
// the below are taken from user in MB, but boost::log::add_file_log needs it to be in bytes
|
||||
std::filesystem::path const dirPath(params.logDir);
|
||||
// the below are taken from user in MB, but spdlog needs it to be in bytes
|
||||
auto const rotationSize = mbToBytes(params.rotationSizeMB);
|
||||
auto const dirSize = mbToBytes(params.dirMaxSizeMB);
|
||||
|
||||
auto const dirPath = std::filesystem::path{params.logDir};
|
||||
auto fileSink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||
(dirPath / "clio.log").string(), rotationSize, params.dirMaxFiles
|
||||
);
|
||||
fileSink->set_level(spdlog::level::trace);
|
||||
|
||||
auto fileSink = boost::log::add_file_log(
|
||||
keywords::file_name = dirPath / "clio.log",
|
||||
keywords::target_file_name = dirPath / "clio_%Y-%m-%d_%H-%M-%S.log",
|
||||
keywords::auto_flush = true,
|
||||
keywords::format = format,
|
||||
keywords::open_mode = std::ios_base::app,
|
||||
keywords::rotation_size = rotationSize,
|
||||
keywords::time_based_rotation =
|
||||
sinks::file::rotation_at_time_interval(boost::posix_time::hours(params.rotationHours))
|
||||
);
|
||||
fileSink->locked_backend()->set_file_collector(
|
||||
sinks::file::make_collector(keywords::target = dirPath, keywords::max_size = dirSize)
|
||||
);
|
||||
fileSink->locked_backend()->scan_for_files();
|
||||
|
||||
boost::log::core::get()->set_exception_handler(
|
||||
boost::log::make_exception_handler<std::exception>(LoggerExceptionHandler())
|
||||
);
|
||||
return fileSink;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,41 +197,45 @@ getMinSeverity(config::ClioConfigDefinition const& config, Severity defaultSever
|
||||
return minSeverity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a log filter based on the minimum severity levels for each channel.
|
||||
*
|
||||
* @param defaultSeverity The default severity level to use if not overridden.
|
||||
* @param minSeverity A map of channel names to their minimum severity levels.
|
||||
* @return A boost::log::filter that filters log records based on the severity level.
|
||||
*/
|
||||
static boost::log::filter
|
||||
createLogFilter(Severity defaultSeverity, std::unordered_map<std::string, Severity> const& minSeverity)
|
||||
std::shared_ptr<spdlog::logger>
|
||||
LogService::registerLogger(std::string const& channel, Severity severity)
|
||||
{
|
||||
auto logFilter = [minSeverity = minSeverity,
|
||||
defaultSeverity](boost::log::attribute_value_set const& attributes) -> bool {
|
||||
auto const channel = attributes[LogChannel];
|
||||
auto const severity = attributes[LogSeverity];
|
||||
if (!channel || !severity)
|
||||
return false;
|
||||
if (auto const it = minSeverity.find(channel.get()); it != minSeverity.end())
|
||||
return severity.get() >= it->second;
|
||||
return severity.get() >= defaultSeverity;
|
||||
};
|
||||
std::shared_ptr<spdlog::logger> logger;
|
||||
if (data.isAsync) {
|
||||
logger = std::make_shared<spdlog::async_logger>(
|
||||
channel,
|
||||
data.allSinks.begin(),
|
||||
data.allSinks.end(),
|
||||
spdlog::thread_pool(),
|
||||
spdlog::async_overflow_policy::block
|
||||
);
|
||||
} else {
|
||||
logger = std::make_shared<spdlog::logger>(channel, data.allSinks.begin(), data.allSinks.end());
|
||||
}
|
||||
|
||||
return boost::log::filter{std::move(logFilter)};
|
||||
logger->set_level(toSpdlogLevel(severity));
|
||||
logger->flush_on(spdlog::level::err);
|
||||
|
||||
spdlog::register_logger(logger);
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
std::expected<void, std::string>
|
||||
LogService::init(config::ClioConfigDefinition const& config)
|
||||
{
|
||||
boost::log::add_common_attributes();
|
||||
boost::log::register_simple_formatter_factory<Severity, char>("Severity");
|
||||
std::string const format = config.get<std::string>("log_format");
|
||||
// Drop existing loggers
|
||||
spdlog::drop_all();
|
||||
|
||||
initConsoleLogging(config.get<bool>("log_to_console"), format);
|
||||
data.isAsync = config.get<bool>("spdlog_async");
|
||||
|
||||
auto const logDir = config.maybeValue<std::string>("log_directory");
|
||||
if (logDir) {
|
||||
if (data.isAsync) {
|
||||
spdlog::init_thread_pool(8192, 1);
|
||||
}
|
||||
|
||||
data.allSinks = createConsoleSinks(config.get<bool>("log_to_console"));
|
||||
|
||||
if (auto const logDir = config.maybeValue<std::string>("log_directory"); logDir.has_value()) {
|
||||
std::filesystem::path const dirPath{logDir.value()};
|
||||
if (not std::filesystem::exists(dirPath)) {
|
||||
if (std::error_code error; not std::filesystem::create_directories(dirPath, error)) {
|
||||
@@ -272,13 +244,13 @@ LogService::init(config::ClioConfigDefinition const& config)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
FileLoggingParams const params{
|
||||
.logDir = logDir.value(),
|
||||
.rotationSizeMB = config.get<uint32_t>("log_rotation_size"),
|
||||
.dirMaxSizeMB = config.get<uint32_t>("log_directory_max_size"),
|
||||
.rotationHours = config.get<uint32_t>("log_rotation_hour_interval")
|
||||
.dirMaxFiles = config.get<uint32_t>("log_directory_max_files"),
|
||||
};
|
||||
initFileLogging(params, format);
|
||||
data.allSinks.push_back(createFileSink(params));
|
||||
}
|
||||
|
||||
// get default severity, can be overridden per channel using the `log_channels` array
|
||||
@@ -289,17 +261,92 @@ LogService::init(config::ClioConfigDefinition const& config)
|
||||
}
|
||||
auto const minSeverity = std::move(maybeMinSeverity).value();
|
||||
|
||||
auto logFilter = createLogFilter(defaultSeverity, minSeverity);
|
||||
boost::log::core::get()->set_filter(logFilter);
|
||||
// Create loggers for each channel
|
||||
for (auto const& channel : Logger::kCHANNELS) {
|
||||
auto const it = minSeverity.find(channel);
|
||||
auto const severity = (it != minSeverity.end()) ? it->second : defaultSeverity;
|
||||
registerLogger(channel, severity);
|
||||
}
|
||||
|
||||
LOG(LogService::info()) << "Default log level = " << defaultSeverity;
|
||||
spdlog::set_default_logger(spdlog::get("General"));
|
||||
|
||||
std::string const format = config.get<std::string>("spdlog_format");
|
||||
spdlog::set_pattern(format);
|
||||
|
||||
LOG(LogService::info()) << "Default log level = " << toString(defaultSeverity);
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
LogService::shutdown()
|
||||
{
|
||||
LOG(LogService::info()) << "Shutting down logger";
|
||||
spdlog::shutdown();
|
||||
}
|
||||
|
||||
Logger::Pump
|
||||
LogService::trace(SourceLocationType const& loc)
|
||||
{
|
||||
return Logger(spdlog::default_logger()).trace(loc);
|
||||
}
|
||||
|
||||
Logger::Pump
|
||||
LogService::debug(SourceLocationType const& loc)
|
||||
{
|
||||
return Logger(spdlog::default_logger()).debug(loc);
|
||||
}
|
||||
|
||||
Logger::Pump
|
||||
LogService::info(SourceLocationType const& loc)
|
||||
{
|
||||
return Logger(spdlog::default_logger()).info(loc);
|
||||
}
|
||||
|
||||
Logger::Pump
|
||||
LogService::warn(SourceLocationType const& loc)
|
||||
{
|
||||
return Logger(spdlog::default_logger()).warn(loc);
|
||||
}
|
||||
|
||||
Logger::Pump
|
||||
LogService::error(SourceLocationType const& loc)
|
||||
{
|
||||
return Logger(spdlog::default_logger()).error(loc);
|
||||
}
|
||||
|
||||
Logger::Pump
|
||||
LogService::fatal(SourceLocationType const& loc)
|
||||
{
|
||||
return Logger(spdlog::default_logger()).fatal(loc);
|
||||
}
|
||||
|
||||
bool
|
||||
LogService::enabled()
|
||||
{
|
||||
return boost::log::core::get()->get_logging_enabled();
|
||||
return spdlog::get_level() != spdlog::level::off;
|
||||
}
|
||||
|
||||
Logger::Logger(std::string channel) : logger_(spdlog::get(channel))
|
||||
{
|
||||
if (!logger_) {
|
||||
logger_ = LogService::registerLogger(channel);
|
||||
}
|
||||
}
|
||||
|
||||
Logger::Pump::Pump(std::shared_ptr<spdlog::logger> logger, Severity sev, SourceLocationType const& loc)
|
||||
: logger_(std::move(logger))
|
||||
, severity_(sev)
|
||||
, sourceLocation_(loc)
|
||||
, enabled_(logger_ != nullptr && logger_->should_log(toSpdlogLevel(sev)))
|
||||
{
|
||||
}
|
||||
|
||||
Logger::Pump::~Pump()
|
||||
{
|
||||
if (enabled_) {
|
||||
spdlog::source_loc sourceLocation{prettyPath(sourceLocation_).cbegin(), sourceLocation_.line(), nullptr};
|
||||
logger_->log(sourceLocation, toSpdlogLevel(severity_), std::move(stream_).str());
|
||||
}
|
||||
}
|
||||
|
||||
Logger::Pump
|
||||
@@ -333,17 +380,21 @@ Logger::fatal(SourceLocationType const& loc) const
|
||||
return {logger_, Severity::FTL, loc};
|
||||
}
|
||||
|
||||
std::string
|
||||
Logger::Logger(std::shared_ptr<spdlog::logger> logger) : logger_(std::move(logger))
|
||||
{
|
||||
}
|
||||
|
||||
std::string_view
|
||||
Logger::Pump::prettyPath(SourceLocationType const& loc, size_t maxDepth)
|
||||
{
|
||||
auto const filePath = std::string{loc.file_name()};
|
||||
std::string_view filePath{loc.file_name()};
|
||||
auto idx = filePath.size();
|
||||
while (maxDepth-- > 0) {
|
||||
idx = filePath.rfind('/', idx - 1);
|
||||
if (idx == std::string::npos || idx == 0)
|
||||
break;
|
||||
}
|
||||
return filePath.substr(idx == std::string::npos ? 0 : idx + 1) + ':' + std::to_string(loc.line());
|
||||
return filePath.substr(idx == std::string::npos ? 0 : idx + 1);
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
||||
@@ -21,35 +21,27 @@
|
||||
|
||||
#include "util/SourceLocation.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/conversion.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/log/core/core.hpp>
|
||||
#include <boost/log/core/record.hpp>
|
||||
#include <boost/log/expressions/filter.hpp>
|
||||
#include <boost/log/expressions/keyword.hpp>
|
||||
#include <boost/log/expressions/predicates/channel_severity_filter.hpp>
|
||||
#include <boost/log/keywords/channel.hpp>
|
||||
#include <boost/log/keywords/severity.hpp>
|
||||
#include <boost/log/sinks/unlocked_frontend.hpp>
|
||||
#include <boost/log/sources/record_ostream.hpp>
|
||||
#include <boost/log/sources/severity_channel_logger.hpp>
|
||||
#include <boost/log/sources/severity_feature.hpp>
|
||||
#include <boost/log/sources/severity_logger.hpp>
|
||||
#include <boost/log/utility/manipulators/add_value.hpp>
|
||||
#include <boost/log/utility/setup/common_attributes.hpp>
|
||||
#include <boost/log/utility/setup/console.hpp>
|
||||
#include <boost/log/utility/setup/file.hpp>
|
||||
#include <boost/log/utility/setup/formatter_parser.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
// We forward declare spdlog::logger and spdlog::sinks::sink
|
||||
// to avoid including the spdlog headers in this header file.
|
||||
namespace spdlog {
|
||||
|
||||
class logger;
|
||||
|
||||
namespace sinks {
|
||||
class sink;
|
||||
} // namespace sinks
|
||||
|
||||
} // namespace spdlog
|
||||
|
||||
struct BenchmarkLoggingInitializer;
|
||||
|
||||
@@ -85,23 +77,6 @@ enum class Severity {
|
||||
FTL,
|
||||
};
|
||||
|
||||
/** @cond */
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
BOOST_LOG_ATTRIBUTE_KEYWORD(LogSeverity, "Severity", Severity);
|
||||
BOOST_LOG_ATTRIBUTE_KEYWORD(LogChannel, "Channel", std::string);
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
/** @endcond */
|
||||
|
||||
/**
|
||||
* @brief Custom labels for @ref Severity in log output.
|
||||
*
|
||||
* @param stream std::ostream The output stream
|
||||
* @param sev Severity The severity to output to the ostream
|
||||
* @return The same ostream we were given
|
||||
*/
|
||||
std::ostream&
|
||||
operator<<(std::ostream& stream, Severity sev);
|
||||
|
||||
/**
|
||||
* @brief A simple thread-safe logger for the channel specified
|
||||
* in the constructor.
|
||||
@@ -111,31 +86,25 @@ operator<<(std::ostream& stream, Severity sev);
|
||||
* severity levels for each channel.
|
||||
*/
|
||||
class Logger final {
|
||||
using LoggerType = boost::log::sources::severity_channel_logger_mt<Severity, std::string>;
|
||||
mutable LoggerType logger_;
|
||||
std::shared_ptr<spdlog::logger> logger_;
|
||||
|
||||
friend class LogService; // to expose the Pump interface
|
||||
friend struct ::BenchmarkLoggingInitializer;
|
||||
|
||||
/**
|
||||
* @brief Helper that pumps data into a log record via `operator<<`.
|
||||
*/
|
||||
class Pump final {
|
||||
using PumpOptType = std::optional<boost::log::aux::record_pump<LoggerType>>;
|
||||
|
||||
boost::log::record rec_;
|
||||
PumpOptType pump_ = std::nullopt;
|
||||
std::shared_ptr<spdlog::logger> logger_;
|
||||
Severity const severity_;
|
||||
SourceLocationType const sourceLocation_;
|
||||
std::ostringstream stream_;
|
||||
bool const enabled_;
|
||||
|
||||
public:
|
||||
~Pump() = default;
|
||||
~Pump();
|
||||
|
||||
Pump(LoggerType& logger, Severity sev, SourceLocationType const& loc)
|
||||
: rec_{logger.open_record(boost::log::keywords::severity = sev)}
|
||||
{
|
||||
if (rec_) {
|
||||
pump_.emplace(boost::log::aux::make_record_pump(logger, rec_));
|
||||
pump_->stream() << boost::log::add_value("SourceLocation", prettyPath(loc));
|
||||
}
|
||||
}
|
||||
Pump(std::shared_ptr<spdlog::logger> logger, Severity sev, SourceLocationType const& loc);
|
||||
|
||||
Pump(Pump&&) = delete;
|
||||
Pump(Pump const&) = delete;
|
||||
@@ -145,7 +114,7 @@ class Logger final {
|
||||
operator=(Pump&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Perfectly forwards any incoming data into the underlying boost::log pump if the pump is available.
|
||||
* @brief Perfectly forwards any incoming data into the underlying stream if data should be logged.
|
||||
*
|
||||
* @tparam T Type of data to pump
|
||||
* @param data The data to pump
|
||||
@@ -155,8 +124,8 @@ class Logger final {
|
||||
[[maybe_unused]] Pump&
|
||||
operator<<(T&& data)
|
||||
{
|
||||
if (pump_)
|
||||
pump_->stream() << std::forward<T>(data);
|
||||
if (enabled_)
|
||||
stream_ << std::forward<T>(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -165,11 +134,11 @@ class Logger final {
|
||||
*/
|
||||
operator bool() const
|
||||
{
|
||||
return pump_.has_value();
|
||||
return enabled_;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] static std::string
|
||||
[[nodiscard]] static std::string_view
|
||||
prettyPath(SourceLocationType const& loc, size_t maxDepth = 3);
|
||||
};
|
||||
|
||||
@@ -194,9 +163,7 @@ public:
|
||||
*
|
||||
* @param channel The channel this logger will report into.
|
||||
*/
|
||||
Logger(std::string channel) : logger_{boost::log::keywords::channel = channel}
|
||||
{
|
||||
}
|
||||
Logger(std::string channel);
|
||||
|
||||
Logger(Logger const&) = default;
|
||||
~Logger() = default;
|
||||
@@ -261,6 +228,9 @@ public:
|
||||
*/
|
||||
[[nodiscard]] Pump
|
||||
fatal(SourceLocationType const& loc = CURRENT_SRC_LOCATION) const;
|
||||
|
||||
private:
|
||||
Logger(std::shared_ptr<spdlog::logger> logger);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -270,8 +240,19 @@ public:
|
||||
* entrypoint for logging into the `General` channel as well as raising alerts.
|
||||
*/
|
||||
class LogService {
|
||||
static Logger generalLog; /*< Global logger for General channel */
|
||||
static boost::log::filter filter;
|
||||
struct Data {
|
||||
bool isAsync;
|
||||
Severity severity;
|
||||
std::vector<std::shared_ptr<spdlog::sinks::sink>> allSinks;
|
||||
};
|
||||
|
||||
friend class Logger;
|
||||
|
||||
private:
|
||||
static Data data;
|
||||
|
||||
static std::shared_ptr<spdlog::logger>
|
||||
registerLogger(std::string const& channel, Severity severity = data.severity);
|
||||
|
||||
public:
|
||||
LogService() = delete;
|
||||
@@ -285,6 +266,12 @@ public:
|
||||
[[nodiscard]] static std::expected<void, std::string>
|
||||
init(config::ClioConfigDefinition const& config);
|
||||
|
||||
/**
|
||||
* @brief Shutdown spdlog to guarantee output is not lost
|
||||
*/
|
||||
static void
|
||||
shutdown();
|
||||
|
||||
/**
|
||||
* @brief Globally accessible General logger at Severity::TRC severity
|
||||
*
|
||||
@@ -292,10 +279,7 @@ public:
|
||||
* @return The pump to use for logging
|
||||
*/
|
||||
[[nodiscard]] static Logger::Pump
|
||||
trace(SourceLocationType const& loc = CURRENT_SRC_LOCATION)
|
||||
{
|
||||
return generalLog.trace(loc);
|
||||
}
|
||||
trace(SourceLocationType const& loc = CURRENT_SRC_LOCATION);
|
||||
|
||||
/**
|
||||
* @brief Globally accessible General logger at Severity::DBG severity
|
||||
@@ -304,10 +288,7 @@ public:
|
||||
* @return The pump to use for logging
|
||||
*/
|
||||
[[nodiscard]] static Logger::Pump
|
||||
debug(SourceLocationType const& loc = CURRENT_SRC_LOCATION)
|
||||
{
|
||||
return generalLog.debug(loc);
|
||||
}
|
||||
debug(SourceLocationType const& loc = CURRENT_SRC_LOCATION);
|
||||
|
||||
/**
|
||||
* @brief Globally accessible General logger at Severity::NFO severity
|
||||
@@ -316,10 +297,7 @@ public:
|
||||
* @return The pump to use for logging
|
||||
*/
|
||||
[[nodiscard]] static Logger::Pump
|
||||
info(SourceLocationType const& loc = CURRENT_SRC_LOCATION)
|
||||
{
|
||||
return generalLog.info(loc);
|
||||
}
|
||||
info(SourceLocationType const& loc = CURRENT_SRC_LOCATION);
|
||||
|
||||
/**
|
||||
* @brief Globally accessible General logger at Severity::WRN severity
|
||||
@@ -328,10 +306,7 @@ public:
|
||||
* @return The pump to use for logging
|
||||
*/
|
||||
[[nodiscard]] static Logger::Pump
|
||||
warn(SourceLocationType const& loc = CURRENT_SRC_LOCATION)
|
||||
{
|
||||
return generalLog.warn(loc);
|
||||
}
|
||||
warn(SourceLocationType const& loc = CURRENT_SRC_LOCATION);
|
||||
|
||||
/**
|
||||
* @brief Globally accessible General logger at Severity::ERR severity
|
||||
@@ -340,10 +315,7 @@ public:
|
||||
* @return The pump to use for logging
|
||||
*/
|
||||
[[nodiscard]] static Logger::Pump
|
||||
error(SourceLocationType const& loc = CURRENT_SRC_LOCATION)
|
||||
{
|
||||
return generalLog.error(loc);
|
||||
}
|
||||
error(SourceLocationType const& loc = CURRENT_SRC_LOCATION);
|
||||
|
||||
/**
|
||||
* @brief Globally accessible General logger at Severity::FTL severity
|
||||
@@ -352,10 +324,7 @@ public:
|
||||
* @return The pump to use for logging
|
||||
*/
|
||||
[[nodiscard]] static Logger::Pump
|
||||
fatal(SourceLocationType const& loc = CURRENT_SRC_LOCATION)
|
||||
{
|
||||
return generalLog.fatal(loc);
|
||||
}
|
||||
fatal(SourceLocationType const& loc = CURRENT_SRC_LOCATION);
|
||||
|
||||
/**
|
||||
* @brief Whether the LogService is enabled or not
|
||||
@@ -370,14 +339,14 @@ private:
|
||||
std::string logDir;
|
||||
|
||||
uint32_t rotationSizeMB;
|
||||
uint32_t dirMaxSizeMB;
|
||||
uint32_t rotationHours;
|
||||
uint32_t dirMaxFiles;
|
||||
};
|
||||
|
||||
friend struct ::BenchmarkLoggingInitializer;
|
||||
|
||||
static void
|
||||
initFileLogging(FileLoggingParams const& params, std::string const& format);
|
||||
[[nodiscard]]
|
||||
static std::shared_ptr<spdlog::sinks::sink>
|
||||
createFileSink(FileLoggingParams const& params);
|
||||
};
|
||||
|
||||
}; // namespace util
|
||||
|
||||
Reference in New Issue
Block a user