mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-10 23:05:53 +00:00
fix: Drop dynamic loggers to fix memory leak (#2686)
This commit is contained in:
@@ -45,7 +45,7 @@ class ConfigValue;
|
|||||||
/**
|
/**
|
||||||
* @brief specific values that are accepted for logger levels in config.
|
* @brief specific values that are accepted for logger levels in config.
|
||||||
*/
|
*/
|
||||||
static constexpr std::array<char const*, 6> kLOG_LEVELS = {
|
static constexpr std::array<std::string_view, 6> kLOG_LEVELS = {
|
||||||
"trace",
|
"trace",
|
||||||
"debug",
|
"debug",
|
||||||
"info",
|
"info",
|
||||||
@@ -57,7 +57,7 @@ static constexpr std::array<char const*, 6> kLOG_LEVELS = {
|
|||||||
/**
|
/**
|
||||||
* @brief specific values that are accepted for logger tag style in config.
|
* @brief specific values that are accepted for logger tag style in config.
|
||||||
*/
|
*/
|
||||||
static constexpr std::array<char const*, 5> kLOG_TAGS = {
|
static constexpr std::array<std::string_view, 5> kLOG_TAGS = {
|
||||||
"int",
|
"int",
|
||||||
"uint",
|
"uint",
|
||||||
"null",
|
"null",
|
||||||
@@ -68,7 +68,7 @@ static constexpr std::array<char const*, 5> kLOG_TAGS = {
|
|||||||
/**
|
/**
|
||||||
* @brief specific values that are accepted for cache loading in config.
|
* @brief specific values that are accepted for cache loading in config.
|
||||||
*/
|
*/
|
||||||
static constexpr std::array<char const*, 3> kLOAD_CACHE_MODE = {
|
static constexpr std::array<std::string_view, 3> kLOAD_CACHE_MODE = {
|
||||||
"sync",
|
"sync",
|
||||||
"async",
|
"async",
|
||||||
"none",
|
"none",
|
||||||
@@ -77,17 +77,17 @@ static constexpr std::array<char const*, 3> kLOAD_CACHE_MODE = {
|
|||||||
/**
|
/**
|
||||||
* @brief specific values that are accepted for database type in config.
|
* @brief specific values that are accepted for database type in config.
|
||||||
*/
|
*/
|
||||||
static constexpr std::array<char const*, 1> kDATABASE_TYPE = {"cassandra"};
|
static constexpr std::array<std::string_view, 1> kDATABASE_TYPE = {"cassandra"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief specific values that are accepted for server's processing_policy in config.
|
* @brief specific values that are accepted for server's processing_policy in config.
|
||||||
*/
|
*/
|
||||||
static constexpr std::array<char const*, 2> kPROCESSING_POLICY = {"parallel", "sequent"};
|
static constexpr std::array<std::string_view, 2> kPROCESSING_POLICY = {"parallel", "sequent"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief specific values that are accepted for database provider in config.
|
* @brief specific values that are accepted for database provider in config.
|
||||||
*/
|
*/
|
||||||
static constexpr std::array<char const*, 2> kPROVIDER = {"cassandra", "aws_keyspace"};
|
static constexpr std::array<std::string_view, 2> kPROVIDER = {"cassandra", "aws_keyspace"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief An interface to enforce constraints on certain values within ClioConfigDefinition.
|
* @brief An interface to enforce constraints on certain values within ClioConfigDefinition.
|
||||||
@@ -123,7 +123,7 @@ protected:
|
|||||||
*/
|
*/
|
||||||
template <std::size_t ArrSize>
|
template <std::size_t ArrSize>
|
||||||
constexpr std::string
|
constexpr std::string
|
||||||
makeErrorMsg(std::string_view key, Value const& value, std::array<char const*, ArrSize> arr) const
|
makeErrorMsg(std::string_view key, Value const& value, std::array<std::string_view, ArrSize> arr) const
|
||||||
{
|
{
|
||||||
// Extract the value from the variant
|
// Extract the value from the variant
|
||||||
auto const valueStr = std::visit([](auto const& v) { return fmt::format("{}", v); }, value);
|
auto const valueStr = std::visit([](auto const& v) { return fmt::format("{}", v); }, value);
|
||||||
@@ -271,7 +271,7 @@ public:
|
|||||||
* @param key The key of the ConfigValue that has this constraint
|
* @param key The key of the ConfigValue that has this constraint
|
||||||
* @param arr The value that has this constraint must be of the values in arr
|
* @param arr The value that has this constraint must be of the values in arr
|
||||||
*/
|
*/
|
||||||
constexpr OneOf(std::string_view key, std::array<char const*, ArrSize> arr) : key_{key}, arr_{arr}
|
constexpr OneOf(std::string_view key, std::array<std::string_view, ArrSize> arr) : key_{key}, arr_{arr}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ private:
|
|||||||
print(std::ostream& stream) const override
|
print(std::ostream& stream) const override
|
||||||
{
|
{
|
||||||
std::string valuesStream;
|
std::string valuesStream;
|
||||||
std::ranges::for_each(arr_, [&valuesStream](std::string const& elem) {
|
std::ranges::for_each(arr_, [&valuesStream](std::string_view elem) {
|
||||||
valuesStream += fmt::format(" `{}`,", elem);
|
valuesStream += fmt::format(" `{}`,", elem);
|
||||||
});
|
});
|
||||||
// replace the last "," with "."
|
// replace the last "," with "."
|
||||||
@@ -327,7 +327,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string_view key_;
|
std::string_view key_;
|
||||||
std::array<char const*, ArrSize> arr_;
|
std::array<std::string_view, ArrSize> arr_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -220,10 +220,10 @@ LogService::createFileSink(FileLoggingParams const& params, std::string const& f
|
|||||||
* @param defaultSeverity The default severity level to use if not overridden.
|
* @param defaultSeverity The default severity level to use if not overridden.
|
||||||
* @return A map of channel names to their minimum severity levels, or an error message if parsing fails.
|
* @return A map of channel names to their minimum severity levels, or an error message if parsing fails.
|
||||||
*/
|
*/
|
||||||
static std::expected<std::unordered_map<std::string, Severity>, std::string>
|
static std::expected<std::unordered_map<std::string_view, Severity>, std::string>
|
||||||
getMinSeverity(config::ClioConfigDefinition const& config, Severity defaultSeverity)
|
getMinSeverity(config::ClioConfigDefinition const& config, Severity defaultSeverity)
|
||||||
{
|
{
|
||||||
std::unordered_map<std::string, Severity> minSeverity;
|
std::unordered_map<std::string_view, Severity> minSeverity;
|
||||||
for (auto const& channel : Logger::kCHANNELS)
|
for (auto const& channel : Logger::kCHANNELS)
|
||||||
minSeverity[channel] = defaultSeverity;
|
minSeverity[channel] = defaultSeverity;
|
||||||
|
|
||||||
@@ -284,13 +284,15 @@ LogServiceState::reset()
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<spdlog::logger>
|
std::shared_ptr<spdlog::logger>
|
||||||
LogServiceState::registerLogger(std::string const& channel, std::optional<Severity> severity)
|
LogServiceState::registerLogger(std::string_view channel, std::optional<Severity> severity)
|
||||||
{
|
{
|
||||||
if (not initialized_) {
|
if (not initialized_) {
|
||||||
throw std::logic_error("LogService is not initialized");
|
throw std::logic_error("LogService is not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<spdlog::logger> existingLogger = spdlog::get(channel);
|
std::string const channelStr{channel};
|
||||||
|
|
||||||
|
std::shared_ptr<spdlog::logger> existingLogger = spdlog::get(channelStr);
|
||||||
if (existingLogger != nullptr) {
|
if (existingLogger != nullptr) {
|
||||||
if (severity.has_value())
|
if (severity.has_value())
|
||||||
existingLogger->set_level(toSpdlogLevel(*severity));
|
existingLogger->set_level(toSpdlogLevel(*severity));
|
||||||
@@ -300,10 +302,10 @@ LogServiceState::registerLogger(std::string const& channel, std::optional<Severi
|
|||||||
std::shared_ptr<spdlog::logger> logger;
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
if (isAsync_) {
|
if (isAsync_) {
|
||||||
logger = std::make_shared<spdlog::async_logger>(
|
logger = std::make_shared<spdlog::async_logger>(
|
||||||
channel, sinks_.begin(), sinks_.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block
|
channelStr, sinks_.begin(), sinks_.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
logger = std::make_shared<spdlog::logger>(channel, sinks_.begin(), sinks_.end());
|
logger = std::make_shared<spdlog::logger>(channelStr, sinks_.begin(), sinks_.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
logger->set_level(toSpdlogLevel(severity.value_or(defaultSeverity_)));
|
logger->set_level(toSpdlogLevel(severity.value_or(defaultSeverity_)));
|
||||||
@@ -427,10 +429,25 @@ LogServiceState::replaceSinks(std::vector<std::shared_ptr<spdlog::sinks::sink>>
|
|||||||
spdlog::apply_all([](std::shared_ptr<spdlog::logger> logger) { logger->sinks() = sinks_; });
|
spdlog::apply_all([](std::shared_ptr<spdlog::logger> logger) { logger->sinks() = sinks_; });
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger::Logger(std::string channel) : logger_(LogServiceState::registerLogger(channel))
|
Logger::Logger(std::string_view const channel) : logger_(LogServiceState::registerLogger(channel))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger::~Logger()
|
||||||
|
{
|
||||||
|
// One reference is held by logger_ and the other by spdlog registry
|
||||||
|
static constexpr size_t kLAST_LOGGER_REF_COUNT = 2;
|
||||||
|
|
||||||
|
if (logger_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool const isDynamic = !std::ranges::contains(kCHANNELS, logger_->name());
|
||||||
|
if (isDynamic && logger_.use_count() == kLAST_LOGGER_REF_COUNT) {
|
||||||
|
spdlog::drop(logger_->name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Logger::Pump::Pump(std::shared_ptr<spdlog::logger> logger, Severity sev, SourceLocationType const& loc)
|
Logger::Pump::Pump(std::shared_ptr<spdlog::logger> logger, Severity sev, SourceLocationType const& loc)
|
||||||
: logger_(std::move(logger))
|
: logger_(std::move(logger))
|
||||||
, severity_(sev)
|
, severity_(sev)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// We forward declare spdlog::logger and spdlog::sinks::sink
|
// We forward declare spdlog::logger and spdlog::sinks::sink
|
||||||
@@ -91,7 +92,7 @@ enum class Severity {
|
|||||||
* otherwise. See @ref LogService::init() for setup of the logging core and
|
* otherwise. See @ref LogService::init() for setup of the logging core and
|
||||||
* severity levels for each channel.
|
* severity levels for each channel.
|
||||||
*/
|
*/
|
||||||
class Logger final {
|
class Logger {
|
||||||
std::shared_ptr<spdlog::logger> logger_;
|
std::shared_ptr<spdlog::logger> logger_;
|
||||||
|
|
||||||
friend class LogService; // to expose the Pump interface
|
friend class LogService; // to expose the Pump interface
|
||||||
@@ -145,7 +146,7 @@ class Logger final {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr std::array<char const*, 8> kCHANNELS = {
|
static constexpr std::array<std::string_view, 8> kCHANNELS = {
|
||||||
"General",
|
"General",
|
||||||
"WebServer",
|
"WebServer",
|
||||||
"Backend",
|
"Backend",
|
||||||
@@ -165,10 +166,10 @@ public:
|
|||||||
*
|
*
|
||||||
* @param channel The channel this logger will report into.
|
* @param channel The channel this logger will report into.
|
||||||
*/
|
*/
|
||||||
Logger(std::string channel);
|
Logger(std::string_view const channel);
|
||||||
|
|
||||||
Logger(Logger const&) = default;
|
Logger(Logger const&) = default;
|
||||||
~Logger() = default;
|
~Logger();
|
||||||
|
|
||||||
Logger(Logger&&) = default;
|
Logger(Logger&&) = default;
|
||||||
Logger&
|
Logger&
|
||||||
@@ -291,7 +292,7 @@ protected:
|
|||||||
* @return Shared pointer to the registered spdlog logger
|
* @return Shared pointer to the registered spdlog logger
|
||||||
*/
|
*/
|
||||||
static std::shared_ptr<spdlog::logger>
|
static std::shared_ptr<spdlog::logger>
|
||||||
registerLogger(std::string const& channel, std::optional<Severity> severity = std::nullopt);
|
registerLogger(std::string_view channel, std::optional<Severity> severity = std::nullopt);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static bool isAsync_; // NOLINT(readability-identifier-naming)
|
static bool isAsync_; // NOLINT(readability-identifier-naming)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ LoggerFixture::init()
|
|||||||
{
|
{
|
||||||
util::LogServiceState::init(false, util::Severity::FTL, {});
|
util::LogServiceState::init(false, util::Severity::FTL, {});
|
||||||
|
|
||||||
std::ranges::for_each(util::Logger::kCHANNELS, [](char const* channel) {
|
std::ranges::for_each(util::Logger::kCHANNELS, [](std::string_view const channel) {
|
||||||
util::LogService::registerLogger(channel);
|
util::LogService::registerLogger(channel);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
using namespace util::config;
|
using namespace util::config;
|
||||||
|
|
||||||
@@ -164,7 +165,7 @@ TEST_F(ConstraintTest, SetValuesOnPortConstraint)
|
|||||||
|
|
||||||
TEST_F(ConstraintTest, OneOfConstraintOneValue)
|
TEST_F(ConstraintTest, OneOfConstraintOneValue)
|
||||||
{
|
{
|
||||||
std::array<char const*, 1> const arr = {"tracer"};
|
std::array<std::string_view, 1> const arr = {"tracer"};
|
||||||
auto const databaseConstraint{OneOf{"database.type", arr}};
|
auto const databaseConstraint{OneOf{"database.type", arr}};
|
||||||
EXPECT_FALSE(databaseConstraint.checkConstraint("tracer").has_value());
|
EXPECT_FALSE(databaseConstraint.checkConstraint("tracer").has_value());
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ TEST_F(ConstraintTest, OneOfConstraintOneValue)
|
|||||||
|
|
||||||
TEST_F(ConstraintTest, OneOfConstraint)
|
TEST_F(ConstraintTest, OneOfConstraint)
|
||||||
{
|
{
|
||||||
std::array<char const*, 3> const arr = {"123", "trace", "haha"};
|
std::array<std::string_view, 3> const arr = {"123", "trace", "haha"};
|
||||||
auto const oneOfCons{OneOf{"log.level", arr}};
|
auto const oneOfCons{OneOf{"log.level", arr}};
|
||||||
|
|
||||||
EXPECT_FALSE(oneOfCons.checkConstraint("trace").has_value());
|
EXPECT_FALSE(oneOfCons.checkConstraint("trace").has_value());
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ TEST_F(LogServiceInitTests, DefaultLogLevel)
|
|||||||
EXPECT_TRUE(LogService::init(config_));
|
EXPECT_TRUE(LogService::init(config_));
|
||||||
|
|
||||||
std::string const logString = "some log";
|
std::string const logString = "some log";
|
||||||
for (auto const& channel : Logger::kCHANNELS) {
|
for (std::string_view const channel : Logger::kCHANNELS) {
|
||||||
Logger const log{channel};
|
Logger const log{channel};
|
||||||
log.trace() << logString;
|
log.trace() << logString;
|
||||||
auto loggerStr = getLoggerString();
|
auto loggerStr = getLoggerString();
|
||||||
|
|||||||
@@ -21,11 +21,23 @@
|
|||||||
#include "util/log/Logger.hpp"
|
#include "util/log/Logger.hpp"
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <spdlog/logger.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <string>
|
#include <string>
|
||||||
using namespace util;
|
using namespace util;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
size_t
|
||||||
|
loggersNum()
|
||||||
|
{
|
||||||
|
size_t counter = 0;
|
||||||
|
spdlog::apply_all([&counter](std::shared_ptr<spdlog::logger>) { ++counter; });
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
// Used as a fixture for tests with enabled logging
|
// Used as a fixture for tests with enabled logging
|
||||||
class LoggerTest : public LoggerFixture {};
|
class LoggerTest : public LoggerFixture {};
|
||||||
|
|
||||||
@@ -71,3 +83,24 @@ TEST_F(LoggerTest, LOGMacro)
|
|||||||
EXPECT_TRUE(computeCalled);
|
EXPECT_TRUE(computeCalled);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
TEST_F(LoggerTest, ManyDynamicLoggers)
|
||||||
|
{
|
||||||
|
static constexpr size_t kNUM_LOGGERS = 10'000;
|
||||||
|
|
||||||
|
auto initialLoggers = loggersNum();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kNUM_LOGGERS; ++i) {
|
||||||
|
std::string const loggerName = "DynamicLogger" + std::to_string(i);
|
||||||
|
|
||||||
|
Logger log{loggerName};
|
||||||
|
log.info() << "Logger number " << i;
|
||||||
|
ASSERT_EQ(getLoggerString(), "inf:" + loggerName + " - Logger number " + std::to_string(i) + "\n");
|
||||||
|
|
||||||
|
Logger copy = log;
|
||||||
|
copy.info() << "Copy of logger number " << i;
|
||||||
|
ASSERT_EQ(getLoggerString(), "inf:" + loggerName + " - Copy of logger number " + std::to_string(i) + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(loggersNum(), initialLoggers);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user