Skip to content

Commit 25296f8

Browse files
authored
fix: Better errors on logger init failure (#1857)
Fixes #1326.
1 parent 4b17880 commit 25296f8

File tree

5 files changed

+66
-16
lines changed

5 files changed

+66
-16
lines changed

src/main/Main.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,21 @@ try {
5252
if (not app::parseConfig(run.configPath))
5353
return EXIT_FAILURE;
5454

55-
util::LogService::init(gClioConfig);
55+
if (auto const initSuccess = util::LogService::init(gClioConfig); not initSuccess) {
56+
std::cerr << initSuccess.error() << std::endl;
57+
return EXIT_FAILURE;
58+
}
5659
app::ClioApplication clio{gClioConfig};
5760
return clio.run(run.useNgWebServer);
5861
},
5962
[](app::CliArgs::Action::Migrate const& migrate) {
6063
if (not app::parseConfig(migrate.configPath))
6164
return EXIT_FAILURE;
6265

63-
util::LogService::init(gClioConfig);
66+
if (auto const initSuccess = util::LogService::init(gClioConfig); not initSuccess) {
67+
std::cerr << initSuccess.error() << std::endl;
68+
return EXIT_FAILURE;
69+
}
6470
app::MigratorApplication migrator{gClioConfig, migrate.subCmd};
6571
return migrator.run();
6672
}

src/util/log/Logger.cpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828

2929
#include <boost/algorithm/string/predicate.hpp>
3030
#include <boost/date_time/posix_time/posix_time_duration.hpp>
31-
#include <boost/filesystem/operations.hpp>
32-
#include <boost/filesystem/path.hpp>
3331
#include <boost/log/attributes/attribute_value_set.hpp>
3432
#include <boost/log/core/core.hpp>
3533
#include <boost/log/expressions/filter.hpp>
@@ -48,18 +46,20 @@
4846
#include <boost/log/utility/setup/console.hpp>
4947
#include <boost/log/utility/setup/file.hpp>
5048
#include <boost/log/utility/setup/formatter_parser.hpp>
49+
#include <fmt/core.h>
5150

5251
#include <algorithm>
5352
#include <array>
5453
#include <cstddef>
5554
#include <cstdint>
55+
#include <filesystem>
5656
#include <ios>
5757
#include <iostream>
5858
#include <optional>
5959
#include <ostream>
60-
#include <stdexcept>
6160
#include <string>
6261
#include <string_view>
62+
#include <system_error>
6363
#include <unordered_map>
6464
#include <utility>
6565

@@ -111,7 +111,7 @@ getSeverityLevel(std::string_view logLevel)
111111
std::unreachable();
112112
}
113113

114-
void
114+
std::expected<void, std::string>
115115
LogService::init(config::ClioConfigDefinition const& config)
116116
{
117117
namespace keywords = boost::log::keywords;
@@ -132,9 +132,15 @@ LogService::init(config::ClioConfigDefinition const& config)
132132

133133
auto const logDir = config.maybeValue<std::string>("log_directory");
134134
if (logDir) {
135-
boost::filesystem::path dirPath{logDir.value()};
136-
if (!boost::filesystem::exists(dirPath))
137-
boost::filesystem::create_directories(dirPath);
135+
std::filesystem::path dirPath{logDir.value()};
136+
if (not std::filesystem::exists(dirPath)) {
137+
if (std::error_code error; not std::filesystem::create_directories(dirPath, error)) {
138+
return std::unexpected{
139+
fmt::format("Couldn't create logs directory '{}': {}", dirPath.string(), error.message())
140+
};
141+
}
142+
}
143+
138144
auto const rotationPeriod = config.get<uint32_t>("log_rotation_hour_interval");
139145

140146
// 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)
169175
for (auto it = overrides.begin<util::config::ObjectView>(); it != overrides.end<util::config::ObjectView>(); ++it) {
170176
auto const& channelConfig = *it;
171177
auto const name = channelConfig.get<std::string>("channel");
172-
if (std::count(std::begin(Logger::kCHANNELS), std::end(Logger::kCHANNELS), name) == 0)
173-
throw std::runtime_error("Can't override settings for log channel " + name + ": invalid channel");
178+
if (std::ranges::count(Logger::kCHANNELS, name) == 0) { // TODO: use std::ranges::contains when available
179+
return std::unexpected{fmt::format("Can't override settings for log channel {}: invalid channel", name)};
180+
}
174181

175182
minSeverity[name] = getSeverityLevel(channelConfig.get<std::string>("log_level"));
176183
}
@@ -189,6 +196,7 @@ LogService::init(config::ClioConfigDefinition const& config)
189196
filter = boost::log::filter{std::move(logFilter)};
190197
boost::log::core::get()->set_filter(filter);
191198
LOG(LogService::info()) << "Default log level = " << defaultSeverity;
199+
return {};
192200
}
193201

194202
Logger::Pump

src/util/log/Logger.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
#include <array>
4848
#include <cstddef>
49+
#include <expected>
4950
#include <optional>
5051
#include <ostream>
5152
#include <string>
@@ -278,8 +279,9 @@ class LogService {
278279
* @brief Global log core initialization from a @ref Config
279280
*
280281
* @param config The configuration to use
282+
* @return Void on success, error message on failure
281283
*/
282-
static void
284+
[[nodiscard]] static std::expected<void, std::string>
283285
init(config::ClioConfigDefinition const& config);
284286

285287
/**

src/util/newconfig/ConfigDefinition.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,10 @@ static ClioConfigDefinition gClioConfig = ClioConfigDefinition{
345345
{"cache.page_fetch_size", ConfigValue{ConfigType::Integer}.defaultValue(512).withConstraint(gValidateUint16)},
346346
{"cache.load", ConfigValue{ConfigType::String}.defaultValue("async").withConstraint(gValidateLoadMode)},
347347

348-
{"log_channels.[].channel", Array{ConfigValue{ConfigType::String}.optional().withConstraint(gValidateChannelName)}
348+
{"log_channels.[].channel", Array{ConfigValue{ConfigType::String}.withConstraint(gValidateChannelName)}
349349
},
350350
{"log_channels.[].log_level",
351-
Array{ConfigValue{ConfigType::String}.optional().withConstraint(gValidateLogLevelName)}},
351+
Array{ConfigValue{ConfigType::String}.withConstraint(gValidateLogLevelName)}},
352352

353353
{"log_level", ConfigValue{ConfigType::String}.defaultValue("info").withConstraint(gValidateLogLevelName)},
354354

tests/unit/LoggerTests.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
#include "util/newconfig/ConfigValue.hpp"
2727
#include "util/newconfig/Types.hpp"
2828

29+
#include <boost/json/array.hpp>
2930
#include <boost/json/object.hpp>
3031
#include <boost/json/parse.hpp>
3132
#include <fmt/core.h>
33+
#include <gmock/gmock.h>
3234
#include <gtest/gtest.h>
3335

3436
#include <cstddef>
@@ -113,7 +115,7 @@ TEST_F(LoggerInitTest, DefaultLogLevel)
113115
ASSERT_FALSE(parsingErrors.has_value());
114116
std::string const logString = "some log";
115117

116-
LogService::init(config_);
118+
EXPECT_TRUE(LogService::init(config_));
117119
for (auto const& channel : Logger::kCHANNELS) {
118120
Logger const log{channel};
119121
log.trace() << logString;
@@ -151,7 +153,7 @@ TEST_F(LoggerInitTest, ChannelLogLevel)
151153
ASSERT_FALSE(parsingErrors.has_value());
152154
std::string const logString = "some log";
153155

154-
LogService::init(config_);
156+
EXPECT_TRUE(LogService::init(config_));
155157
for (auto const& channel : Logger::kCHANNELS) {
156158
Logger const log{channel};
157159
log.trace() << logString;
@@ -175,6 +177,38 @@ TEST_F(LoggerInitTest, ChannelLogLevel)
175177
}
176178
}
177179

180+
TEST_F(LoggerInitTest, InitReturnsErrorIfCouldNotCreateLogDirectory)
181+
{
182+
// "/proc" directory is read only on any unix OS
183+
auto const parsingErrors = config_.parse(ConfigFileJson{boost::json::object{{"log_directory", "/proc/logs"}}});
184+
ASSERT_FALSE(parsingErrors.has_value());
185+
186+
auto const result = LogService::init(config_);
187+
EXPECT_FALSE(result);
188+
EXPECT_THAT(result.error(), testing::HasSubstr("Couldn't create logs directory"));
189+
}
190+
191+
TEST_F(LoggerInitTest, InitReturnsErrorIfProvidedInvalidChannel)
192+
{
193+
auto const jsonStr = R"json(
194+
{
195+
"log_channels": [
196+
{
197+
"channel": "SomeChannel",
198+
"log_level": "warn"
199+
}
200+
]
201+
})json";
202+
203+
auto const json = boost::json::parse(jsonStr).as_object();
204+
auto const parsingErrors = config_.parse(ConfigFileJson{json});
205+
ASSERT_FALSE(parsingErrors.has_value());
206+
207+
auto const result = LogService::init(config_);
208+
EXPECT_FALSE(result);
209+
EXPECT_EQ(result.error(), "Can't override settings for log channel SomeChannel: invalid channel");
210+
}
211+
178212
TEST_F(LoggerInitTest, LogSizeAndHourRotationCannotBeZero)
179213
{
180214
std::vector<std::string_view> const keys{

0 commit comments

Comments
 (0)