~aleteoryx/muditaos

d492c3646e478c8d84ece22a1d16dc8cab7895a8 — Jakub Pyszczak 4 years ago 038099b
[EGD-7548] Crash dump rotate

Added dumps rotation. Maximum count of the files set to 5.
The oldest dumps are removed if there're more than 5 of them.
Comparison is taken by the date.
Unified log and crash dumps rotators.
M board/rt1051/CMakeLists.txt => board/rt1051/CMakeLists.txt +2 -0
@@ 30,6 30,8 @@ target_include_directories(board
)

target_link_libraries(board
    PRIVATE
        utils-rotator
    PUBLIC
        fsl
        module-vfs

M board/rt1051/crashdump/crashdumpwriter_vfs.cpp => board/rt1051/crashdump/crashdumpwriter_vfs.cpp +10 -7
@@ 8,21 8,24 @@
#include "purefs/vfs_subsystem.hpp"
#include <purefs/filesystem_paths.hpp>

#include <filesystem>

namespace crashdump
{
    constexpr inline auto crashDumpFileName = "crashdump.hex";

    void CrashDumpWriterVFS::openDump()
    {
        std::array<char, 64> crashDumpFileName{0};
        formatCrashDumpFileName(crashDumpFileName);
        vfs                          = purefs::subsystem::vfs_core();
        const auto crashDumpFilePath = purefs::dir::getCrashDumpsPath() / crashDumpFileName;

        const auto crashDumpFilePath = purefs::dir::getCrashDumpsPath() / crashDumpFileName.data();

        vfs    = purefs::subsystem::vfs_core();
        if (!rotator.rotateFile(crashDumpFilePath)) {
            LOG_FATAL("Failed to rotate crash dumps.");
        }
        dumpFd = vfs->open(crashDumpFilePath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);

        if (dumpFd < 0) {
            LOG_FATAL("Failed to open crash dump file [%s]. Won't be able to save crash info.",
                      crashDumpFilePath.c_str());
            LOG_FATAL("Failed to open crash dump file. Won't be able to save crash info.");
        }
    }


M board/rt1051/crashdump/crashdumpwriter_vfs.hpp => board/rt1051/crashdump/crashdumpwriter_vfs.hpp +5 -9
@@ 4,6 4,7 @@
#pragma once

#include "crashdumpwriter.hpp"
#include <rotator/Rotator.hpp>

#include <array>
#include <ctime>


@@ 16,11 17,12 @@ namespace purefs::fs

namespace crashdump
{
    constexpr inline auto CrashDumpFileNameFormat = "crashdump-%FT%TZ.hex";

    constexpr inline auto maxRotationFilesCount = 5;
    class CrashDumpWriterVFS : public CrashDumpWriter
    {
      public:
        CrashDumpWriterVFS() : rotator{".hex"}
        {}
        void openDump() override;
        void saveDump() override;



@@ 29,13 31,7 @@ namespace crashdump
        void writeWords(const std::uint32_t *buff, std::size_t size) override;

      private:
        template <std::size_t N> void formatCrashDumpFileName(std::array<char, N> &name)
        {
            std::time_t now;
            std::time(&now);
            std::strftime(name.data(), name.size(), CrashDumpFileNameFormat, std::localtime(&now));
        }

        utils::Rotator<maxRotationFilesCount> rotator;
        int dumpFd{-1};

        std::shared_ptr<purefs::fs::filesystem> vfs;

M module-utils/CMakeLists.txt => module-utils/CMakeLists.txt +1 -0
@@ 11,6 11,7 @@ add_subdirectory(locale)
add_subdirectory(log)
add_subdirectory(math)
add_subdirectory(phonenumber)
add_subdirectory(rotator)
add_subdirectory(rrule)
add_subdirectory(time)
add_subdirectory(unicode)

M module-utils/log/CMakeLists.txt => module-utils/log/CMakeLists.txt +1 -1
@@ 17,10 17,10 @@ target_link_libraries(log
    PRIVATE
        utility
        purefs-paths

    PUBLIC
        module-os
        log-api
        utils-rotator    
)

if (${ENABLE_TESTS})

M module-utils/log/Logger.cpp => module-utils/log/Logger.cpp +3 -42
@@ 12,21 12,6 @@

namespace Log
{
    namespace
    {
        std::string getRotatedLogFileExtension(int count)
        {
            return ".log." + utils::to_string(count);
        }

        std::filesystem::path getRotatedFilePath(const std::filesystem::path &source, int rotationCount)
        {
            auto path = source;
            path.replace_extension(getRotatedLogFileExtension(rotationCount));
            return path;
        }
    } // namespace

    std::map<std::string, logger_level> Logger::filtered = {{"ApplicationManager", logger_level::LOGINFO},
                                                            {"CellularMux", logger_level::LOGINFO},
                                                            {"ServiceCellular", logger_level::LOGINFO},


@@ 47,7 32,7 @@ namespace Log
        return stream;
    }

    Logger::Logger() : circularBuffer(circularBufferSize)
    Logger::Logger() : circularBuffer{circularBufferSize}, rotator{".log"}
    {}

    void Logger::enableColors(bool enable)


@@ 81,11 66,10 @@ namespace Log
        return logs;
    }

    void Logger::init(Application app, size_t fileSize, int filesCount)
    void Logger::init(Application app, size_t fileSize)
    {
        application      = std::move(app);
        maxFileSize      = fileSize;
        maxRotationIndex = filesCount - 1;
#if LOG_USE_COLOR == 1
        enableColors(true);
#else


@@ 158,7 142,7 @@ namespace Log
            LOG_DEBUG("Max log file size exceeded. Rotating log files...");
            {
                LockGuard lock(logFileMutex);
                rotateLogFile(logPath);
                rotator.rotateFile(logPath);
            }
            firstDump = true;
        }


@@ 187,29 171,6 @@ namespace Log
        return status;
    }

    void Logger::rotateLogFile(const std::filesystem::path &logPath)
    {
        for (int i = currentRotationIndex; i > 0; --i) {
            std::filesystem::path src = getRotatedFilePath(logPath, i);
            if (i == maxRotationIndex) {
                std::filesystem::remove(src);
                continue;
            }
            std::filesystem::path dest = getRotatedFilePath(logPath, i + 1);
            std::filesystem::rename(src, dest);
        }
        auto rotatedLogPath = getRotatedFilePath(logPath, 1);
        std::filesystem::rename(logPath, rotatedLogPath);
        onLogRotationFinished();
    }

    void Logger::onLogRotationFinished() noexcept
    {
        if (currentRotationIndex < maxRotationIndex) {
            ++currentRotationIndex;
        }
    }

    void Logger::addFileHeader(std::ofstream &file) const
    {
        file << application;

M module-utils/log/Logger.hpp => module-utils/log/Logger.hpp +3 -5
@@ 7,6 7,7 @@
#include <log/log.hpp>
#include "LoggerBuffer.hpp"
#include "log_colors.hpp"
#include <rotator/Rotator.hpp>
#include <map>
#include <mutex.hpp>
#include <string>


@@ 39,7 40,7 @@ namespace Log
            return logger;
        }
        auto getLogs() -> std::string;
        void init(Application app, size_t fileSize = MAX_LOG_FILE_SIZE, int filesCount = MAX_LOG_FILES_COUNT);
        void init(Application app, size_t fileSize = MAX_LOG_FILE_SIZE);
        auto log(Device device, const char *fmt, va_list args) -> int;
        auto log(logger_level level, const char *file, int line, const char *function, const char *fmt, va_list args)
            -> int;


@@ 73,8 74,6 @@ namespace Log
        }

        void addFileHeader(std::ofstream &file) const;
        void rotateLogFile(const std::filesystem::path &logPath);
        void onLogRotationFinished() noexcept;

        cpp_freertos::MutexStandard mutex;
        cpp_freertos::MutexStandard logFileMutex;


@@ 83,11 82,10 @@ namespace Log
        char loggerBuffer[LOGGER_BUFFER_SIZE] = {0};
        size_t loggerBufferCurrentPos         = 0;
        size_t maxFileSize                    = MAX_LOG_FILE_SIZE;
        int currentRotationIndex              = 0;
        int maxRotationIndex                  = 0;

        Application application;
        LoggerBuffer circularBuffer;
        utils::Rotator<MAX_LOG_FILES_COUNT> rotator;
        static constexpr size_t circularBufferSize = 1000;

        static const char *levelNames[];

M module-utils/log/tests/CMakeLists.txt => module-utils/log/tests/CMakeLists.txt +1 -0
@@ 20,6 20,7 @@ add_catch2_executable(
    LIBS
        module-utils
        module-vfs
        log    
    USE_FS
)


M module-utils/log/tests/test_logDumps.cpp => module-utils/log/tests/test_logDumps.cpp +2 -4
@@ 38,7 38,6 @@ TEST_CASE("Test if logs are dumped to a file without rotation")
{
    auto app                            = Log::Application{"TestApp", "rev", "tag", "branch"};
    constexpr auto MaxFileSize          = 1024 * 1024; // 1 MB
    constexpr auto MaxFilesCount        = 3;
    constexpr auto TestLog              = "12345678";
    const std::filesystem::path logsDir = "./ut_logs";
    const auto testLogFile              = logsDir / "TestApp.log";


@@ 50,7 49,7 @@ TEST_CASE("Test if logs are dumped to a file without rotation")
    std::filesystem::create_directory(logsDir);

    // Initialize the logger with test parameters.
    Log::Logger::get().init(std::move(app), MaxFileSize, MaxFilesCount);
    Log::Logger::get().init(std::move(app), MaxFileSize);
    REQUIRE(countFiles(logsDir) == 0);

    // Dump logs.


@@ 72,7 71,6 @@ TEST_CASE("Test if log files rotate")
{
    auto app                            = Log::Application{"TestApp", "rev", "tag", "branch"};
    constexpr auto MaxFileSize          = 10; // 10 bytes
    constexpr auto MaxFilesCount        = 3;
    constexpr auto TestLog              = "12345678";
    const std::filesystem::path logsDir = "./ut_logs";
    const auto testLogFile              = logsDir / "TestApp.log";


@@ 84,7 82,7 @@ TEST_CASE("Test if log files rotate")
    std::filesystem::create_directory(logsDir);

    // Initialize the logger with test parameters.
    Log::Logger::get().init(std::move(app), MaxFileSize, MaxFilesCount);
    Log::Logger::get().init(std::move(app), MaxFileSize);
    REQUIRE(countFiles(logsDir) == 0);

    // Dump logs.

A module-utils/rotator/CMakeLists.txt => module-utils/rotator/CMakeLists.txt +15 -0
@@ 0,0 1,15 @@
add_library(utils-rotator INTERFACE)

target_sources(utils-rotator
    PUBLIC
        include/rotator/Rotator.hpp
)

target_include_directories(utils-rotator
    INTERFACE
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)

if (${ENABLE_TESTS})
    add_subdirectory(tests)
endif()

A module-utils/rotator/include/rotator/Rotator.hpp => module-utils/rotator/include/rotator/Rotator.hpp +82 -0
@@ 0,0 1,82 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <filesystem>
#include <string>

namespace utils
{
    template <std::size_t maxRotationFilesCount> class Rotator
    {
      protected:
        const std::string extension;

        std::size_t getRotatedFilesCount(const std::filesystem::path &path, std::error_code &ec) const
        {
            std::size_t count = 0;
            for (std::size_t i = 1; i < maxRotationFilesCount; i++) {
                if (!std::filesystem::exists(getRotatedFilePath(path, i), ec)) {
                    break;
                }
                if (ec) {
                    break;
                }
                count++;
            }
            return count;
        }

        std::string getRotatedFileExtension(int count) const
        {
            return extension + "." + std::to_string(count);
        }

        std::filesystem::path getRotatedFilePath(const std::filesystem::path &source, int rotationCount) const
        {
            auto path = source;
            path.replace_extension(getRotatedFileExtension(rotationCount));
            return path;
        }

      public:
        explicit Rotator(std::string extension) : extension{extension}
        {}

        virtual ~Rotator() = default;

        bool rotateFile(const std::filesystem::path &path) const
        {
            std::error_code ec;
            if (const auto firstDump = !std::filesystem::exists(path, ec); ec) {
                return false;
            }
            else if (firstDump) {
                return true;
            }
            const auto rotatedFiles = getRotatedFilesCount(path, ec);
            if (ec) {
                return false;
            }
            for (std::size_t i = rotatedFiles; i > 0; i--) {
                const auto src = getRotatedFilePath(path, i);
                if (i == maxRotationFilesCount - 1) {
                    std::filesystem::remove(src, ec);
                    if (ec) {
                        return false;
                    }
                    continue;
                }
                const auto dest = getRotatedFilePath(path, i + 1);
                std::filesystem::rename(src, dest, ec);
                if (ec) {
                    return false;
                }
            }
            auto rotatedLogPath = getRotatedFilePath(path, 1);
            std::filesystem::rename(path, rotatedLogPath, ec);
            return (ec) ? false : true;
        }
    };
} // namespace utils

A module-utils/rotator/tests/CMakeLists.txt => module-utils/rotator/tests/CMakeLists.txt +9 -0
@@ 0,0 1,9 @@
# Rotator tests
add_catch2_executable(
    NAME
        rotator-test
    SRCS
        test_Rotator.cpp
    LIBS
        utils-rotator
)

A module-utils/rotator/tests/test_Rotator.cpp => module-utils/rotator/tests/test_Rotator.cpp +94 -0
@@ 0,0 1,94 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#define CATCH_CONFIG_MAIN

#include <catch2/catch.hpp>
#include <rotator/Rotator.hpp>

#include <iostream>
#include <filesystem>

namespace
{
    constexpr std::size_t maxRotationFilesCount = 5;

    class HexRotator : public utils::Rotator<maxRotationFilesCount>
    {
      public:
        HexRotator() : utils::Rotator<maxRotationFilesCount>{".hex"}
        {}

        std::filesystem::path rotatedFilePath(const std::filesystem::path &source, int rotationCount)
        {
            return this->getRotatedFilePath(source, rotationCount);
        }
    };
} // namespace

TEST_CASE("Rotation of hex dump files")
{
    HexRotator rotator;
    constexpr auto fileName = "crashdump.hex";
    SECTION("Handle first dump")
    {
        REQUIRE(rotator.rotateFile(fileName));
    }
    SECTION("Handle first rotate")
    {
        constexpr auto firstRotationName = "crashdump.hex.1";
        std::ofstream file(fileName);
        REQUIRE(rotator.rotateFile(fileName));
        REQUIRE(std::filesystem::exists(firstRotationName));
        REQUIRE_FALSE(std::filesystem::exists(fileName));
        REQUIRE(std::filesystem::remove(firstRotationName));
    }
    SECTION("Handle max rotates")
    {
        for (std::size_t i = 1; i <= maxRotationFilesCount; i++) {
            const auto rotatedFileName = rotator.rotatedFilePath(fileName, i);
            std::ofstream file(fileName);
            REQUIRE(rotator.rotateFile(fileName));
            for (std::size_t j = i - 1; j > 0; j--) {
                REQUIRE(std::filesystem::exists(rotator.rotatedFilePath(fileName, j)));
            }
            if (i == maxRotationFilesCount) {
                REQUIRE_FALSE(std::filesystem::exists(rotator.rotatedFilePath(fileName, i)));
            }
            REQUIRE_FALSE(std::filesystem::exists(fileName));
        }
        for (std::size_t i = 1; i < maxRotationFilesCount; i++) {
            const auto rotatedFileName = rotator.rotatedFilePath(fileName, i);
            REQUIRE(std::filesystem::remove(rotatedFileName));
        }
    }

    SECTION("Handle more than max files")
    {
        for (std::size_t i = 1; i <= maxRotationFilesCount; i++) {
            const auto rotatedFileName = rotator.rotatedFilePath(fileName, i);
            std::ofstream file(fileName);
            REQUIRE(rotator.rotateFile(fileName));
            for (std::size_t j = i - 1; j > 0; j--) {
                REQUIRE(std::filesystem::exists(rotator.rotatedFilePath(fileName, j)));
            }
            if (i == maxRotationFilesCount) {
                REQUIRE_FALSE(std::filesystem::exists(rotator.rotatedFilePath(fileName, i)));
            }
            REQUIRE_FALSE(std::filesystem::exists(fileName));
        }
        std::ofstream file(fileName);
        REQUIRE(rotator.rotateFile(fileName));
        REQUIRE_FALSE(std::filesystem::exists(rotator.rotatedFilePath(fileName, maxRotationFilesCount + 1)));
        for (std::size_t i = 1; i < maxRotationFilesCount; i++) {
            const auto rotatedFileName = rotator.rotatedFilePath(fileName, i);
            REQUIRE(std::filesystem::exists(rotatedFileName));
        }
        REQUIRE_FALSE(std::filesystem::exists(rotator.rotatedFilePath(fileName, maxRotationFilesCount)));
        REQUIRE_FALSE(std::filesystem::exists(fileName));
        for (std::size_t i = 1; i < maxRotationFilesCount; i++) {
            const auto rotatedFileName = rotator.rotatedFilePath(fileName, i);
            REQUIRE(std::filesystem::remove(rotatedFileName));
        }
    }
}