fix: Better errors on logger init failure (#1857)

Fixes #1326.
This commit is contained in:
Sergey Kuznetsov
2025-02-18 15:43:13 +00:00
committed by GitHub
parent 4b178805de
commit 25296f8ffa
5 changed files with 66 additions and 16 deletions

View File

@@ -52,7 +52,10 @@ try {
if (not app::parseConfig(run.configPath)) if (not app::parseConfig(run.configPath))
return EXIT_FAILURE; return EXIT_FAILURE;
util::LogService::init(gClioConfig); if (auto const initSuccess = util::LogService::init(gClioConfig); not initSuccess) {
std::cerr << initSuccess.error() << std::endl;
return EXIT_FAILURE;
}
app::ClioApplication clio{gClioConfig}; app::ClioApplication clio{gClioConfig};
return clio.run(run.useNgWebServer); return clio.run(run.useNgWebServer);
}, },
@@ -60,7 +63,10 @@ try {
if (not app::parseConfig(migrate.configPath)) if (not app::parseConfig(migrate.configPath))
return EXIT_FAILURE; return EXIT_FAILURE;
util::LogService::init(gClioConfig); if (auto const initSuccess = util::LogService::init(gClioConfig); not initSuccess) {
std::cerr << initSuccess.error() << std::endl;
return EXIT_FAILURE;
}
app::MigratorApplication migrator{gClioConfig, migrate.subCmd}; app::MigratorApplication migrator{gClioConfig, migrate.subCmd};
return migrator.run(); return migrator.run();
} }

View File

@@ -28,8 +28,6 @@
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp> #include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/log/attributes/attribute_value_set.hpp> #include <boost/log/attributes/attribute_value_set.hpp>
#include <boost/log/core/core.hpp> #include <boost/log/core/core.hpp>
#include <boost/log/expressions/filter.hpp> #include <boost/log/expressions/filter.hpp>
@@ -48,18 +46,20 @@
#include <boost/log/utility/setup/console.hpp> #include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/file.hpp> #include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/formatter_parser.hpp> #include <boost/log/utility/setup/formatter_parser.hpp>
#include <fmt/core.h>
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <filesystem>
#include <ios> #include <ios>
#include <iostream> #include <iostream>
#include <optional> #include <optional>
#include <ostream> #include <ostream>
#include <stdexcept>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <system_error>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
@@ -111,7 +111,7 @@ getSeverityLevel(std::string_view logLevel)
std::unreachable(); std::unreachable();
} }
void std::expected<void, std::string>
LogService::init(config::ClioConfigDefinition const& config) LogService::init(config::ClioConfigDefinition const& config)
{ {
namespace keywords = boost::log::keywords; namespace keywords = boost::log::keywords;
@@ -132,9 +132,15 @@ LogService::init(config::ClioConfigDefinition const& config)
auto const logDir = config.maybeValue<std::string>("log_directory"); auto const logDir = config.maybeValue<std::string>("log_directory");
if (logDir) { if (logDir) {
boost::filesystem::path dirPath{logDir.value()}; std::filesystem::path dirPath{logDir.value()};
if (!boost::filesystem::exists(dirPath)) if (not std::filesystem::exists(dirPath)) {
boost::filesystem::create_directories(dirPath); if (std::error_code error; not std::filesystem::create_directories(dirPath, error)) {
return std::unexpected{
fmt::format("Couldn't create logs directory '{}': {}", dirPath.string(), error.message())
};
}
}
auto const rotationPeriod = config.get<uint32_t>("log_rotation_hour_interval"); auto const rotationPeriod = config.get<uint32_t>("log_rotation_hour_interval");
// the below are taken from user in MB, but boost::log::add_file_log needs it to be in bytes // the below are taken from user in MB, but boost::log::add_file_log needs it to be in bytes
@@ -169,8 +175,9 @@ LogService::init(config::ClioConfigDefinition const& config)
for (auto it = overrides.begin<util::config::ObjectView>(); it != overrides.end<util::config::ObjectView>(); ++it) { for (auto it = overrides.begin<util::config::ObjectView>(); it != overrides.end<util::config::ObjectView>(); ++it) {
auto const& channelConfig = *it; auto const& channelConfig = *it;
auto const name = channelConfig.get<std::string>("channel"); auto const name = channelConfig.get<std::string>("channel");
if (std::count(std::begin(Logger::kCHANNELS), std::end(Logger::kCHANNELS), name) == 0) if (std::ranges::count(Logger::kCHANNELS, name) == 0) { // TODO: use std::ranges::contains when available
throw std::runtime_error("Can't override settings for log channel " + name + ": invalid channel"); return std::unexpected{fmt::format("Can't override settings for log channel {}: invalid channel", name)};
}
minSeverity[name] = getSeverityLevel(channelConfig.get<std::string>("log_level")); minSeverity[name] = getSeverityLevel(channelConfig.get<std::string>("log_level"));
} }
@@ -189,6 +196,7 @@ LogService::init(config::ClioConfigDefinition const& config)
filter = boost::log::filter{std::move(logFilter)}; filter = boost::log::filter{std::move(logFilter)};
boost::log::core::get()->set_filter(filter); boost::log::core::get()->set_filter(filter);
LOG(LogService::info()) << "Default log level = " << defaultSeverity; LOG(LogService::info()) << "Default log level = " << defaultSeverity;
return {};
} }
Logger::Pump Logger::Pump

View File

@@ -46,6 +46,7 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <expected>
#include <optional> #include <optional>
#include <ostream> #include <ostream>
#include <string> #include <string>
@@ -278,8 +279,9 @@ public:
* @brief Global log core initialization from a @ref Config * @brief Global log core initialization from a @ref Config
* *
* @param config The configuration to use * @param config The configuration to use
* @return Void on success, error message on failure
*/ */
static void [[nodiscard]] static std::expected<void, std::string>
init(config::ClioConfigDefinition const& config); init(config::ClioConfigDefinition const& config);
/** /**

View File

@@ -345,10 +345,10 @@ static ClioConfigDefinition gClioConfig = ClioConfigDefinition{
{"cache.page_fetch_size", ConfigValue{ConfigType::Integer}.defaultValue(512).withConstraint(gValidateUint16)}, {"cache.page_fetch_size", ConfigValue{ConfigType::Integer}.defaultValue(512).withConstraint(gValidateUint16)},
{"cache.load", ConfigValue{ConfigType::String}.defaultValue("async").withConstraint(gValidateLoadMode)}, {"cache.load", ConfigValue{ConfigType::String}.defaultValue("async").withConstraint(gValidateLoadMode)},
{"log_channels.[].channel", Array{ConfigValue{ConfigType::String}.optional().withConstraint(gValidateChannelName)} {"log_channels.[].channel", Array{ConfigValue{ConfigType::String}.withConstraint(gValidateChannelName)}
}, },
{"log_channels.[].log_level", {"log_channels.[].log_level",
Array{ConfigValue{ConfigType::String}.optional().withConstraint(gValidateLogLevelName)}}, Array{ConfigValue{ConfigType::String}.withConstraint(gValidateLogLevelName)}},
{"log_level", ConfigValue{ConfigType::String}.defaultValue("info").withConstraint(gValidateLogLevelName)}, {"log_level", ConfigValue{ConfigType::String}.defaultValue("info").withConstraint(gValidateLogLevelName)},

View File

@@ -26,9 +26,11 @@
#include "util/newconfig/ConfigValue.hpp" #include "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/Types.hpp" #include "util/newconfig/Types.hpp"
#include <boost/json/array.hpp>
#include <boost/json/object.hpp> #include <boost/json/object.hpp>
#include <boost/json/parse.hpp> #include <boost/json/parse.hpp>
#include <fmt/core.h> #include <fmt/core.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <cstddef> #include <cstddef>
@@ -113,7 +115,7 @@ TEST_F(LoggerInitTest, DefaultLogLevel)
ASSERT_FALSE(parsingErrors.has_value()); ASSERT_FALSE(parsingErrors.has_value());
std::string const logString = "some log"; std::string const logString = "some log";
LogService::init(config_); EXPECT_TRUE(LogService::init(config_));
for (auto const& channel : Logger::kCHANNELS) { for (auto const& channel : Logger::kCHANNELS) {
Logger const log{channel}; Logger const log{channel};
log.trace() << logString; log.trace() << logString;
@@ -151,7 +153,7 @@ TEST_F(LoggerInitTest, ChannelLogLevel)
ASSERT_FALSE(parsingErrors.has_value()); ASSERT_FALSE(parsingErrors.has_value());
std::string const logString = "some log"; std::string const logString = "some log";
LogService::init(config_); EXPECT_TRUE(LogService::init(config_));
for (auto const& channel : Logger::kCHANNELS) { for (auto const& channel : Logger::kCHANNELS) {
Logger const log{channel}; Logger const log{channel};
log.trace() << logString; log.trace() << logString;
@@ -175,6 +177,38 @@ TEST_F(LoggerInitTest, ChannelLogLevel)
} }
} }
TEST_F(LoggerInitTest, InitReturnsErrorIfCouldNotCreateLogDirectory)
{
// "/proc" directory is read only on any unix OS
auto const parsingErrors = config_.parse(ConfigFileJson{boost::json::object{{"log_directory", "/proc/logs"}}});
ASSERT_FALSE(parsingErrors.has_value());
auto const result = LogService::init(config_);
EXPECT_FALSE(result);
EXPECT_THAT(result.error(), testing::HasSubstr("Couldn't create logs directory"));
}
TEST_F(LoggerInitTest, InitReturnsErrorIfProvidedInvalidChannel)
{
auto const jsonStr = R"json(
{
"log_channels": [
{
"channel": "SomeChannel",
"log_level": "warn"
}
]
})json";
auto const json = boost::json::parse(jsonStr).as_object();
auto const parsingErrors = config_.parse(ConfigFileJson{json});
ASSERT_FALSE(parsingErrors.has_value());
auto const result = LogService::init(config_);
EXPECT_FALSE(result);
EXPECT_EQ(result.error(), "Can't override settings for log channel SomeChannel: invalid channel");
}
TEST_F(LoggerInitTest, LogSizeAndHourRotationCannotBeZero) TEST_F(LoggerInitTest, LogSizeAndHourRotationCannotBeZero)
{ {
std::vector<std::string_view> const keys{ std::vector<std::string_view> const keys{