From 87e761347e13942cb5c00b36bbd7b326c05a0170 Mon Sep 17 00:00:00 2001 From: Oliver Benz Date: Sat, 27 Dec 2025 14:22:18 +0100 Subject: [PATCH 1/2] Clang-Format: Update format. --- .clang-format | 6 +-- examples/standard/main.cpp | 2 +- src/LogConfig.cpp | 2 +- src/LogEntry.cpp | 2 +- src/LogLevel.cpp | 2 +- src/LogOutputConsole.cpp | 2 +- src/LogOutputFile.cpp | 56 ++++++++++++------------- src/LogOutputMock.cpp | 2 +- src/Logger.cpp | 2 +- src/Profiler.cpp | 14 +++---- src/include/Logger/ILogOutput.hpp | 8 ++-- src/include/Logger/LogConfig.hpp | 8 ++-- src/include/Logger/LogEntry.hpp | 8 ++-- src/include/Logger/LogLevel.hpp | 2 +- src/include/Logger/LogOutputConsole.hpp | 2 +- src/include/Logger/LogOutputFile.hpp | 8 ++-- src/include/Logger/LogOutputMock.hpp | 2 +- src/include/Logger/Logger.hpp | 6 +-- src/include/Logger/Profiler.hpp | 2 +- tests/LogConfig.gtest.cpp | 10 ++--- tests/LogEntry.gtest.cpp | 4 +- tests/LogLevel.gtest.cpp | 4 +- tests/LogOutput.gtest.cpp | 31 +++++++------- tests/Profiler.gtest.cpp | 4 +- 24 files changed, 93 insertions(+), 96 deletions(-) diff --git a/.clang-format b/.clang-format index ef276a7..037b4a6 100644 --- a/.clang-format +++ b/.clang-format @@ -3,8 +3,8 @@ BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignEscapedNewlines: Left -AlignTrailingComments: false -AlignConsecutiveAssignments: None +AlignTrailingComments: true +AlignConsecutiveAssignments: true AlignOperands: Align AllowAllArgumentsOnNextLine: false AllowAllConstructorInitializersOnNextLine: false @@ -46,7 +46,7 @@ SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: false SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 2 +SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false diff --git a/examples/standard/main.cpp b/examples/standard/main.cpp index 92b3e06..3d1cf4c 100644 --- a/examples/standard/main.cpp +++ b/examples/standard/main.cpp @@ -10,7 +10,7 @@ int main() { std::cout << "Version: " << LOGVERSION_MAJOR << "." << LOGVERSION_MINOR << "." << LOGVERSION_PATCH << std::endl; static Logging::LogConfig config; - auto mock = std::make_shared(); + auto mock = std::make_shared(); auto logFile = std::make_shared("Filename.txt"); config.AddLogOutput(std::make_shared()); diff --git a/src/LogConfig.cpp b/src/LogConfig.cpp index b77aeea..76c85d0 100644 --- a/src/LogConfig.cpp +++ b/src/LogConfig.cpp @@ -26,4 +26,4 @@ void LogConfig::SetMinLogLevel(const LogLevel logLevel) { m_minLogLevel = logLevel; } -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/LogEntry.cpp b/src/LogEntry.cpp index 72e2da3..ec3ade3 100644 --- a/src/LogEntry.cpp +++ b/src/LogEntry.cpp @@ -8,4 +8,4 @@ std::string LogEntry::OutputText() const { return std::format("{} {} {}", m_time, LevelToText(m_level), m_text); } -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/LogLevel.cpp b/src/LogLevel.cpp index bac8a45..5d3044a 100644 --- a/src/LogLevel.cpp +++ b/src/LogLevel.cpp @@ -20,4 +20,4 @@ std::string LevelToText(LogLevel level) { return ""; } -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/LogOutputConsole.cpp b/src/LogOutputConsole.cpp index bd324ee..bbcf90d 100644 --- a/src/LogOutputConsole.cpp +++ b/src/LogOutputConsole.cpp @@ -14,4 +14,4 @@ void LogOutputConsole::Write(const LogEntry& entry) { std::clog << entry.OutputText() << "\n"; } -} // namespace Logging +} // namespace Logging diff --git a/src/LogOutputFile.cpp b/src/LogOutputFile.cpp index a4e637b..fe59d33 100644 --- a/src/LogOutputFile.cpp +++ b/src/LogOutputFile.cpp @@ -12,15 +12,15 @@ LogOutputFile::LogOutputFile(const std::string& filePath, std::size_t maxFileSiz void LogOutputFile::RotateFile() { const std::filesystem::path originalPath(m_filePath); - const auto parent = originalPath.parent_path(); - const auto stem = originalPath.stem().string(); + const auto parent = originalPath.parent_path(); + const auto stem = originalPath.stem().string(); const auto extension = originalPath.extension().string(); const auto baseName = extension.empty() ? originalPath.filename().string() : stem; std::error_code ec; - constexpr unsigned kMaxRotations = 10000; - unsigned count = 1u; + constexpr unsigned kMaxRotations = 10000; + unsigned count = 1u; std::filesystem::path newFilePath; do { if (count > kMaxRotations) { @@ -28,26 +28,22 @@ void LogOutputFile::RotateFile() { } auto rotatedName = baseName + "(" + std::to_string(count) + ")" + extension; - newFilePath = parent.empty() ? std::filesystem::path(rotatedName) : parent / rotatedName; + newFilePath = parent.empty() ? std::filesystem::path(rotatedName) : parent / rotatedName; ++count; } while (std::filesystem::exists(newFilePath, ec)); ec.clear(); // // Any errors during probing are ignored by design std::filesystem::rename(originalPath, newFilePath, ec); - if (ec == std::errc::cross_device_link) { - // Best-effort fallback - std::filesystem::copy_file( - originalPath, - newFilePath, - std::filesystem::copy_options::overwrite_existing, - ec); - - if (!ec) { - std::filesystem::remove(originalPath, ec); - } - } - - // Any failure beyond this point is intentionally ignored. + if (ec == std::errc::cross_device_link) { + // Best-effort fallback + std::filesystem::copy_file(originalPath, newFilePath, std::filesystem::copy_options::overwrite_existing, ec); + + if (!ec) { + std::filesystem::remove(originalPath, ec); + } + } + + // Any failure beyond this point is intentionally ignored. } void LogOutputFile::Write(const std::vector& logEntries) { @@ -64,11 +60,11 @@ void LogOutputFile::Write(const std::vector& logEntries) { outfile.close(); // Check max file size reached - std::error_code ec; - const auto fileSize = std::filesystem::file_size(m_filePath, ec); - if (!ec && fileSize >= m_maxFileSize) { - RotateFile(); - } + std::error_code ec; + const auto fileSize = std::filesystem::file_size(m_filePath, ec); + if (!ec && fileSize >= m_maxFileSize) { + RotateFile(); + } } void LogOutputFile::Write(const LogEntry& entry) { @@ -83,15 +79,15 @@ void LogOutputFile::Write(const LogEntry& entry) { outfile.close(); // Check max filesize reached - std::error_code ec; - const auto fileSize = std::filesystem::file_size(m_filePath, ec); - if (!ec && fileSize >= m_maxFileSize) { - RotateFile(); - } + std::error_code ec; + const auto fileSize = std::filesystem::file_size(m_filePath, ec); + if (!ec && fileSize >= m_maxFileSize) { + RotateFile(); + } } std::string LogOutputFile::FilePath() const { return m_filePath; } -} // namespace Logging +} // namespace Logging diff --git a/src/LogOutputMock.cpp b/src/LogOutputMock.cpp index 11e7ebf..60a2a44 100644 --- a/src/LogOutputMock.cpp +++ b/src/LogOutputMock.cpp @@ -10,4 +10,4 @@ void LogOutputMock::Write(const LogEntry& entry) { m_logEntries.emplace_back(entry); } -} // namespace Logging +} // namespace Logging diff --git a/src/Logger.cpp b/src/Logger.cpp index 569fa28..c7da707 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -35,4 +35,4 @@ void Logger::Log(const LogLevel level, const std::string& text) { m_entries.emplace_back(entry); } -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/Profiler.cpp b/src/Profiler.cpp index b841967..0cb4807 100644 --- a/src/Profiler.cpp +++ b/src/Profiler.cpp @@ -7,25 +7,25 @@ namespace Logging { Profiler::Profiler(Logger logger, std::string identifier) : m_logger(std::move(logger)), m_identifier(std::move(identifier)) { m_startTime = std::chrono::steady_clock::now(); - m_lastTime = m_startTime; + m_lastTime = m_startTime; m_logger.Log(Logging::LogLevel::Info, std::format("--> {} START", m_identifier)); } Profiler::~Profiler() { - const auto now = std::chrono::steady_clock::now(); - const auto timeMs = std::chrono::duration_cast(now - m_startTime).count(); // t in ms + const auto now = std::chrono::steady_clock::now(); + const auto timeMs = std::chrono::duration_cast(now - m_startTime).count(); // t in ms m_logger.Log(Logging::LogLevel::Info, std::format("<-- {} END: {}ms", m_identifier, timeMs)); } void Profiler::LogStep(const std::string& stepName) { - const auto now = std::chrono::steady_clock::now(); - const auto stepMs = std::chrono::duration_cast(now - m_lastTime).count(); // t in ms - const auto totalMs = std::chrono::duration_cast(now - m_startTime).count(); // t in ms + const auto now = std::chrono::steady_clock::now(); + const auto stepMs = std::chrono::duration_cast(now - m_lastTime).count(); // t in ms + const auto totalMs = std::chrono::duration_cast(now - m_startTime).count(); // t in ms m_logger.Log(Logging::LogLevel::Info, std::format("--- {} STEP {}: +{}ms ({}ms total)", m_identifier, stepName, stepMs, totalMs)); m_lastTime = now; } -} // namespace Logging +} // namespace Logging diff --git a/src/include/Logger/ILogOutput.hpp b/src/include/Logger/ILogOutput.hpp index 267b553..a0e68d8 100644 --- a/src/include/Logger/ILogOutput.hpp +++ b/src/include/Logger/ILogOutput.hpp @@ -9,11 +9,11 @@ namespace Logging { //! Defines what functionality a log output has to define. class ILogOutput { protected: - ILogOutput() = default; - ILogOutput(ILogOutput&& other) = default; + ILogOutput() = default; + ILogOutput(ILogOutput&& other) = default; ILogOutput(const ILogOutput& other) = default; - ILogOutput& operator=(ILogOutput&& other) = default; + ILogOutput& operator=(ILogOutput&& other) = default; ILogOutput& operator=(ILogOutput const& other) = default; public: @@ -26,4 +26,4 @@ class ILogOutput { virtual void Write(const LogEntry& entry) = 0; }; -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/include/Logger/LogConfig.hpp b/src/include/Logger/LogConfig.hpp index 2dfc465..f88b64e 100644 --- a/src/include/Logger/LogConfig.hpp +++ b/src/include/Logger/LogConfig.hpp @@ -30,9 +30,9 @@ class LogConfig { LogLevel MinLogLevel() const; private: - bool m_logEnabled = true; //!< Enable/Disable logging. - LogLevel m_minLogLevel = LogLevel::Any; //!< Only log messages with severity above this. - std::vector> m_logOutputs; //!< Where to write the log data to. + bool m_logEnabled = true; //!< Enable/Disable logging. + LogLevel m_minLogLevel = LogLevel::Any; //!< Only log messages with severity above this. + std::vector> m_logOutputs; //!< Where to write the log data to. }; -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/include/Logger/LogEntry.hpp b/src/include/Logger/LogEntry.hpp index 7914986..62065ae 100644 --- a/src/include/Logger/LogEntry.hpp +++ b/src/include/Logger/LogEntry.hpp @@ -10,9 +10,9 @@ struct LogEntry { //! Returns the formatted log entry data. std::string OutputText() const; - LogLevel m_level; //!< Log level of this one entry. - std::string m_text; //!< All log entries. - std::string m_time; //!< Timestamp of the log entry. + LogLevel m_level; //!< Log level of this one entry. + std::string m_text; //!< All log entries. + std::string m_time; //!< Timestamp of the log entry. }; -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/include/Logger/LogLevel.hpp b/src/include/Logger/LogLevel.hpp index db07010..cdf5e36 100644 --- a/src/include/Logger/LogLevel.hpp +++ b/src/include/Logger/LogLevel.hpp @@ -8,4 +8,4 @@ enum class LogLevel { Any = 000, Info = 100, Debug = 200, Warning = 300, Error = std::string LevelToText(LogLevel level); -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/include/Logger/LogOutputConsole.hpp b/src/include/Logger/LogOutputConsole.hpp index 0a713be..aeda4db 100644 --- a/src/include/Logger/LogOutputConsole.hpp +++ b/src/include/Logger/LogOutputConsole.hpp @@ -13,4 +13,4 @@ class LogOutputConsole : public ILogOutput { void Write(const LogEntry& entry) override; }; -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/include/Logger/LogOutputFile.hpp b/src/include/Logger/LogOutputFile.hpp index 8affb07..b63214d 100644 --- a/src/include/Logger/LogOutputFile.hpp +++ b/src/include/Logger/LogOutputFile.hpp @@ -32,9 +32,9 @@ class LogOutputFile : public ILogOutput { void RotateFile(); private: - std::string m_filePath; //!< Path to the log file. - std::uintmax_t m_maxFileSize; //!< Maximum size of a logfile before starting a new file. - std::mutex m_writeLock; //!< Lock so file is only opened on one thread. + std::string m_filePath; //!< Path to the log file. + std::uintmax_t m_maxFileSize; //!< Maximum size of a logfile before starting a new file. + std::mutex m_writeLock; //!< Lock so file is only opened on one thread. }; -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/include/Logger/LogOutputMock.hpp b/src/include/Logger/LogOutputMock.hpp index f6deef1..c1129dc 100644 --- a/src/include/Logger/LogOutputMock.hpp +++ b/src/include/Logger/LogOutputMock.hpp @@ -17,4 +17,4 @@ class LogOutputMock : public ILogOutput { std::vector m_logEntries; }; -} // namespace Logging \ No newline at end of file +} // namespace Logging \ No newline at end of file diff --git a/src/include/Logger/Logger.hpp b/src/include/Logger/Logger.hpp index 5e32714..cf91b13 100644 --- a/src/include/Logger/Logger.hpp +++ b/src/include/Logger/Logger.hpp @@ -22,8 +22,8 @@ class Logger { void Log(LogLevel level, const std::string& text); private: - LogConfig& m_config; //!< Configuration by which this logger object should adhere. - std::vector m_entries; //!< List of log entries received. + LogConfig& m_config; //!< Configuration by which this logger object should adhere. + std::vector m_entries; //!< List of log entries received. }; -} // namespace Logging +} // namespace Logging diff --git a/src/include/Logger/Profiler.hpp b/src/include/Logger/Profiler.hpp index 13fb95f..9d4ed1e 100644 --- a/src/include/Logger/Profiler.hpp +++ b/src/include/Logger/Profiler.hpp @@ -26,4 +26,4 @@ class Profiler { std::chrono::time_point m_lastTime; }; -} // namespace Logging +} // namespace Logging diff --git a/tests/LogConfig.gtest.cpp b/tests/LogConfig.gtest.cpp index 32b72c5..d6f7dab 100644 --- a/tests/LogConfig.gtest.cpp +++ b/tests/LogConfig.gtest.cpp @@ -39,7 +39,7 @@ TEST(LogConfig, DisableLogging) { logger.Log(LogLevel::Debug, "Testing Entry 2"); logger.Flush(); - EXPECT_EQ(mock->m_logEntries.size(), 2u); // Still 2 entries + EXPECT_EQ(mock->m_logEntries.size(), 2u); // Still 2 entries } TEST(LogConfig, MinLogLevel) { @@ -84,8 +84,8 @@ TEST(LogConfig, MinLogLevel) { TEST(LogConfig, LogOutputs) { static Logging::LogConfig config; - auto mock = std::make_shared(); - auto logFile = std::make_shared("LogSectionA.txt"); + auto mock = std::make_shared(); + auto logFile = std::make_shared("LogSectionA.txt"); auto logFile1 = std::make_shared("LogSectionB.txt"); auto logFile2 = std::make_shared("LogSectionC.txt"); @@ -98,5 +98,5 @@ TEST(LogConfig, LogOutputs) { EXPECT_EQ(config.LogOutputs().size(), 5u); } -} // namespace GTest -} // namespace Logging +} // namespace GTest +} // namespace Logging diff --git a/tests/LogEntry.gtest.cpp b/tests/LogEntry.gtest.cpp index 7604141..a0369b7 100644 --- a/tests/LogEntry.gtest.cpp +++ b/tests/LogEntry.gtest.cpp @@ -22,5 +22,5 @@ TEST(LogEntry, OutputTextPattern) { EXPECT_TRUE(std::regex_match(output, pattern)); } -} // namespace GTest -} // namespace Logging +} // namespace GTest +} // namespace Logging diff --git a/tests/LogLevel.gtest.cpp b/tests/LogLevel.gtest.cpp index 613962b..a572c4d 100644 --- a/tests/LogLevel.gtest.cpp +++ b/tests/LogLevel.gtest.cpp @@ -14,5 +14,5 @@ TEST(LogLevel, LevelToText) { EXPECT_STREQ(LevelToText(LogLevel::Critical).c_str(), "[Critical]"); } -} // namespace GTest -} // namespace Logging +} // namespace GTest +} // namespace Logging diff --git a/tests/LogOutput.gtest.cpp b/tests/LogOutput.gtest.cpp index 65d3357..a6dc854 100644 --- a/tests/LogOutput.gtest.cpp +++ b/tests/LogOutput.gtest.cpp @@ -113,6 +113,7 @@ TEST(LogOutput, FileBasic) { TEST(LogOutput, FileMaxSize) { static constexpr std::uintmax_t maxSize = 50; + auto logFile = std::make_shared("Logfile.txt", maxSize); LogConfig config; @@ -123,22 +124,22 @@ TEST(LogOutput, FileMaxSize) { // Write to output file { Logger(config).Log(LogLevel::Info, testString); } - EXPECT_FALSE(std::filesystem::exists("Logfile.txt")); // New file only created after next write + EXPECT_FALSE(std::filesystem::exists("Logfile.txt")); // New file only created after next write EXPECT_TRUE(std::filesystem::exists("Logfile(1).txt")); // Wrapping happens after a write - EXPECT_FALSE(std::filesystem::exists("Logfile(2).txt")); // Does not exist yet + EXPECT_FALSE(std::filesystem::exists("Logfile(2).txt")); // Does not exist yet // Less than maxSize Bytes { Logger(config).Log(LogLevel::Info, "a"); } - EXPECT_TRUE(std::filesystem::exists("Logfile.txt")); // New write -> Create file again + EXPECT_TRUE(std::filesystem::exists("Logfile.txt")); // New write -> Create file again EXPECT_TRUE(std::filesystem::exists("Logfile(1).txt")); // Still exists - EXPECT_FALSE(std::filesystem::exists("Logfile(2).txt")); // Does not exist yet + EXPECT_FALSE(std::filesystem::exists("Logfile(2).txt")); // Does not exist yet // Fill the current log file { Logger(config).Log(LogLevel::Debug, testString); } - EXPECT_FALSE(std::filesystem::exists("Logfile.txt")); // New file only created after new write + EXPECT_FALSE(std::filesystem::exists("Logfile.txt")); // New file only created after new write EXPECT_TRUE(std::filesystem::exists("Logfile(1).txt")); // Still exists EXPECT_TRUE(std::filesystem::exists("Logfile(2).txt")); // Newly created from write - EXPECT_FALSE(std::filesystem::exists("Logfile(3).txt")); // Does not exist yet + EXPECT_FALSE(std::filesystem::exists("Logfile(3).txt")); // Does not exist yet // Fill two files worth (whole will be in one file -> One write operation) { @@ -146,20 +147,20 @@ TEST(LogOutput, FileMaxSize) { logger.Log(LogLevel::Debug, testString); logger.Log(LogLevel::Debug, testString); } - EXPECT_FALSE(std::filesystem::exists("Logfile.txt")); // New file only created after new write - EXPECT_TRUE(std::filesystem::exists("Logfile(1).txt")); // Still exists - EXPECT_TRUE(std::filesystem::exists("Logfile(2).txt")); // Still exists + EXPECT_FALSE(std::filesystem::exists("Logfile.txt")); // New file only created after new write + EXPECT_TRUE(std::filesystem::exists("Logfile(1).txt")); // Still exists + EXPECT_TRUE(std::filesystem::exists("Logfile(2).txt")); // Still exists EXPECT_TRUE( - std::filesystem::exists("Logfile(3).txt")); // Newly created from write -> One log write always to one file - EXPECT_FALSE(std::filesystem::exists("Logfile(4).txt")); // Does not exist + std::filesystem::exists("Logfile(3).txt")); // Newly created from write -> One log write always to one file + EXPECT_FALSE(std::filesystem::exists("Logfile(4).txt")); // Does not exist // Cleanup - EXPECT_NE(std::remove("Logfile.txt"), 0); // Does not exist + EXPECT_NE(std::remove("Logfile.txt"), 0); // Does not exist EXPECT_EQ(std::remove("Logfile(1).txt"), 0); EXPECT_EQ(std::remove("Logfile(2).txt"), 0); EXPECT_EQ(std::remove("Logfile(3).txt"), 0); - EXPECT_NE(std::remove("Logfile(4).txt"), 0); // Does not exist + EXPECT_NE(std::remove("Logfile(4).txt"), 0); // Does not exist } -} // namespace GTest -} // namespace Logging +} // namespace GTest +} // namespace Logging diff --git a/tests/Profiler.gtest.cpp b/tests/Profiler.gtest.cpp index 94e96c1..4538ad6 100644 --- a/tests/Profiler.gtest.cpp +++ b/tests/Profiler.gtest.cpp @@ -39,5 +39,5 @@ TEST(Profiler, LogsStartStepsAndEnd) { EXPECT_NE(mock->m_logEntries[3].m_text.find("ProfileCase"), std::string::npos); } -} // namespace GTest -} // namespace Logging +} // namespace GTest +} // namespace Logging From 221401d91b81e15feb55312c8d0f348848f20d2c Mon Sep 17 00:00:00 2001 From: Oliver Benz Date: Sat, 27 Dec 2025 14:33:14 +0100 Subject: [PATCH 2/2] Logger: Function to get default log file directory paths. --- src/LogOutputFile.cpp | 39 +++++++++++++++++++++++++++- src/include/Logger/LogOutputFile.hpp | 11 ++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/LogOutputFile.cpp b/src/LogOutputFile.cpp index fe59d33..726b4db 100644 --- a/src/LogOutputFile.cpp +++ b/src/LogOutputFile.cpp @@ -1,9 +1,15 @@ #include "Logger/LogOutputFile.hpp" -#include +#include +#include #include #include +#ifdef _WIN32 +#include +#include +#endif + namespace Logging { LogOutputFile::LogOutputFile(const std::string& filePath, std::size_t maxFileSize) @@ -90,4 +96,35 @@ std::string LogOutputFile::FilePath() const { return m_filePath; } +std::filesystem::path GetDefaultLogDir(const std::string& appName) { +#if defined(_WIN32) + // 1) Windows: %LOCALAPPDATA%//logs + PWSTR path = nullptr; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path))) { + std::filesystem::path p(path); + CoTaskMemFree(path); + return p / appName / "logs"; + } +#elif defined(__APPLE__) + // 2) macOS: ~/Library/Logs/ + const char* home = std::getenv("HOME"); + if (home && std::strlen(home) > 0) { + return std::filesystem::path(home) / "Library/Logs" / appName; + } +#else + // 3) Linux/Unix: XDG_STATE_HOME//logs or ~/.local/state//logs + const char* state = std::getenv("XDG_STATE_HOME"); + if (state && std::strlen(state) > 0) { + return std::filesystem::path(state) / appName / "logs"; + } + const char* home = std::getenv("HOME"); + if (home && std::strlen(home) > 0) { + return std::filesystem::path(home) / ".local/state" / appName / "logs"; + } +#endif + + // 4) Fallback + return std::filesystem::current_path() / "logs"; +} + } // namespace Logging diff --git a/src/include/Logger/LogOutputFile.hpp b/src/include/Logger/LogOutputFile.hpp index b63214d..fc843fe 100644 --- a/src/include/Logger/LogOutputFile.hpp +++ b/src/include/Logger/LogOutputFile.hpp @@ -3,12 +3,23 @@ #include "Logger/ILogOutput.hpp" #include "Logger/LogEntry.hpp" +#include #include #include #include namespace Logging { +//! Get the default logfile output directory. Including a subdirectory for the application. +/*! \returns + * Windows: "%FOLDERID_LocalAppData%//logs" + * MacOS: "%HOME%/Library/Logs/" + * Linux/Unix: "%XDG_STATE_HOME%//logs" else "%HOME%/.local/state//logs" + * Fallback: "/logs" + */ +std::filesystem::path GetDefaultLogDir(const std::string& appName); + + class LogOutputFile : public ILogOutput { public: //! The current log file is always the one mentioned in 'filePath'.