feat(logging): rotating log files (#5485)

This commit is contained in:
Dave Lane
2026-08-07 21:12:25 -04:00
committed by GitHub
parent 49e5697746
commit 7df8c62ea7
7 changed files with 164 additions and 8 deletions

View File

@@ -5,9 +5,14 @@
#include "../tests_common.h"
#include "../tests_log_checker.h"
#include <filesystem>
#include <format>
#include <fstream>
#include <iterator>
#include <random>
#include <src/logging.h>
#include <string>
#include <string_view>
namespace {
std::array log_levels = {
@@ -20,8 +25,103 @@ namespace {
};
constexpr auto log_file = "test_sunshine.log";
/**
* @brief Write test content to a log file.
*
* @param path Path to write.
* @param content Content to write.
*/
void write_log_file(const std::filesystem::path &path, std::string_view content) {
std::ofstream output {path};
output << content;
}
/**
* @brief Read all content from a test log file.
*
* @param path Path to read.
* @return File content.
*/
std::string read_log_file(const std::filesystem::path &path) {
std::ifstream input {path};
return {std::istreambuf_iterator<char> {input}, std::istreambuf_iterator<char> {}};
}
} // namespace
/**
* @brief Test fixture for startup log rotation.
*/
class LogRotationTest: public BaseTest {
protected:
/**
* @brief Create an empty directory for the current test.
*/
void SetUp() override {
BaseTest::SetUp();
std::filesystem::remove_all(test_directory);
std::filesystem::create_directories(test_directory);
}
/**
* @brief Remove files created by the current test.
*/
void TearDown() override {
std::filesystem::remove_all(test_directory);
BaseTest::TearDown();
}
/**
* @brief Build the path for a rotated test log.
*
* @param generation Rotated log generation number.
* @return Path with the generation suffix appended.
*/
std::filesystem::path rotated_log_path(std::size_t generation) const {
auto path = log_path;
path += std::format(".{}", generation);
return path;
}
const std::filesystem::path test_directory {std::filesystem::path {SUNSHINE_TEST_BIN_DIR} / "log_rotation_tests"}; ///< Directory containing log rotation test files.
const std::filesystem::path log_path {test_directory / "custom.log"}; ///< Path to the current test log.
};
TEST_F(LogRotationTest, RotatesCurrentLogAndRetainsFivePreviousLogs) {
write_log_file(log_path, "current");
for (std::size_t generation = 1; generation <= logging::retained_log_file_count; ++generation) {
write_log_file(rotated_log_path(generation), std::to_string(generation));
}
EXPECT_FALSE(logging::rotate_log_file(log_path));
EXPECT_FALSE(std::filesystem::exists(log_path));
EXPECT_EQ(read_log_file(rotated_log_path(1)), "current");
EXPECT_EQ(read_log_file(rotated_log_path(2)), "1");
EXPECT_EQ(read_log_file(rotated_log_path(3)), "2");
EXPECT_EQ(read_log_file(rotated_log_path(4)), "3");
EXPECT_EQ(read_log_file(rotated_log_path(5)), "4");
}
TEST_F(LogRotationTest, SupportsMissingLogGenerations) {
write_log_file(rotated_log_path(2), "second");
EXPECT_FALSE(logging::rotate_log_file(log_path));
EXPECT_FALSE(std::filesystem::exists(log_path));
EXPECT_FALSE(std::filesystem::exists(rotated_log_path(1)));
EXPECT_FALSE(std::filesystem::exists(rotated_log_path(2)));
EXPECT_EQ(read_log_file(rotated_log_path(3)), "second");
}
TEST_F(LogRotationTest, ReportsFilesystemErrors) {
std::filesystem::create_directories(rotated_log_path(logging::retained_log_file_count) / "child");
write_log_file(log_path, "current");
EXPECT_TRUE(logging::rotate_log_file(log_path));
EXPECT_EQ(read_log_file(log_path), "current");
}
struct LogLevelsTest: BaseTest, testing::WithParamInterface<decltype(log_levels)::value_type> {};
INSTANTIATE_TEST_SUITE_P(