diff --git a/examples/06_platform_logging/CMakeLists.txt b/examples/06_platform_logging/CMakeLists.txt new file mode 100644 index 0000000..f10e95f --- /dev/null +++ b/examples/06_platform_logging/CMakeLists.txt @@ -0,0 +1,27 @@ +#============================================================================== +# 06_platform_logging - Cross-platform logging initialisation example +# +# This example runs as a standalone binary on desktop platforms only +# (macOS, Linux, Windows). On iOS, visionOS, Android, and Web it is +# excluded because those targets are integrated into app bundles, not +# standalone executables. The bridging snippets in main.cpp show how +# to wire up logging from the platform layer on those targets. +#============================================================================== + +if(NOT VNE_PLATFORM_IOS AND NOT VNE_PLATFORM_VISIONOS + AND NOT VNE_PLATFORM_ANDROID AND NOT VNE_PLATFORM_WEB) + + add_executable(06_PlatformLogging main.cpp) + + target_link_libraries(06_PlatformLogging + PRIVATE + vne::logging + ) + + target_include_directories(06_PlatformLogging + PRIVATE + $ + $ + ) + +endif() diff --git a/examples/06_platform_logging/main.cpp b/examples/06_platform_logging/main.cpp new file mode 100644 index 0000000..9422f0c --- /dev/null +++ b/examples/06_platform_logging/main.cpp @@ -0,0 +1,121 @@ +/* --------------------------------------------------------------------- + * Copyright (c) 2025 Ajeet Singh Yadav. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License") + * + * Author: Ajeet Singh Yadav + * Created: May 2026 + * + * Autodoc: yes + * + * ---------------------------------------------------------------------- + */ + +/* ---------------------------------------------------------------------- + * Example: Cross-platform logging setup + * + * Demonstrates how to initialise the logger on each supported platform. + * Mobile and sandboxed platforms (iOS, visionOS, Android, Web) cannot + * resolve a writable log directory on their own — the app must obtain the + * correct path from the platform and pass it via setAppLogDirectory() + * before calling configureLogger(). + * + * Platform bridging snippets + * -------------------------- + * + * iOS / visionOS (export a C wrapper from C++, call it from Swift before init): + * + * // C++ side (in your library/app, exported for Swift to call) + * extern "C" void vne_set_log_dir(const char* path) { + * if (path != nullptr) { + * vne::log::Logging::setAppLogDirectory(path); + * } + * } + * + * // Swift side (declare the C function and pass the resolved path) + * import Foundation + * func vne_set_log_dir(_ path: UnsafePointer) + * + * // --- in AppDelegate / @main --- + * let dir = FileManager.default + * .urls(for: .documentDirectory, in: .userDomainMask)[0] + * .appendingPathComponent("logs").path + * dir.withCString { vne_set_log_dir($0) } + * + * Android (JNI, call from your Activity / Application.onCreate): + * + * extern "C" JNIEXPORT void JNICALL + * Java_com_example_app_MainActivity_initLogging(JNIEnv* env, jobject ctx) { + * jclass cls = env->GetObjectClass(ctx); + * jmethodID mid = env->GetMethodID(cls, "getFilesDir", "()Ljava/io/File;"); + * jobject file = env->CallObjectMethod(ctx, mid); + * jmethodID pathMid = env->GetMethodID(env->GetObjectClass(file), + * "getAbsolutePath", + * "()Ljava/lang/String;"); + * jstring jpath = (jstring)env->CallObjectMethod(file, pathMid); + * const char* path = env->GetStringUTFChars(jpath, nullptr); + * vne::log::Logging::setAppLogDirectory(std::string(path) + "/logs"); + * env->ReleaseStringUTFChars(jpath, path); + * } + * + * Web (Emscripten): + * File logging is not supported. The logger automatically falls back to + * console-only output — no extra setup needed. + * + * ---------------------------------------------------------------------- + */ + +#include + +#include + +namespace { +CREATE_VNE_LOGGER_CATEGORY("platform.example") +} + +// --------------------------------------------------------------------------- +// Platform-specific log directory resolution +// --------------------------------------------------------------------------- +// On desktop platforms the library resolves the directory automatically. +// On mobile/web the app must call setAppLogDirectory() before this runs. +// This helper just shows what happens per platform at runtime. +static vne::log::LogSinkType chooseSink() { +#if defined(VNE_PLATFORM_WEB) + // No filesystem — console only. + return vne::log::LogSinkType::eConsole; +#elif defined(VNE_PLATFORM_VISIONOS) || defined(VNE_PLATFORM_ANDROID) + // File logging is only available if the app provided a directory. + std::string dir = vne::log::Logging::getPlatformSpecificLogDirectory(); + return dir.empty() ? vne::log::LogSinkType::eConsole : vne::log::LogSinkType::eBoth; +#else + return vne::log::LogSinkType::eBoth; +#endif +} + +int main() { + // On iOS / visionOS / Android the calling app must have already invoked + // vne::log::Logging::setAppLogDirectory() before main() + // or before this point. See the bridging snippets in the file header. + + std::string log_dir = vne::log::Logging::getPlatformSpecificLogDirectory(); + + vne::log::LoggerConfig config = vne::log::Logging::defaultLoggerConfig(); + config.sink = chooseSink(); + if (!log_dir.empty()) { + config.file_path = (std::filesystem::path(log_dir) / "platform_example.log").string(); + } + config.log_level = vne::log::LogLevel::eTrace; + config.file_append = false; // overwrite on each launch; set true to keep history across runs + + vne::log::Logging::configureLogger(config); + + VNE_LOG_INFO << "Logging initialised"; + VNE_LOG_INFO << "Log directory: " << (log_dir.empty() ? "(console only)" : log_dir); + + VNE_LOG_TRACE << "Trace message"; + VNE_LOG_DEBUG << "Debug message"; + VNE_LOG_WARN << "Warning message"; + VNE_LOG_ERROR << "Error message"; + + vne::log::Logging::shutdown(); + return 0; +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ea7f662..ff0a3c8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -16,3 +16,6 @@ add_subdirectory(04_benchmark) # 05 - Multithreaded: Thread-safe logging from concurrent threads add_subdirectory(05_multithreaded) + +# 06 - Platform Logging: Cross-platform init with setAppLogDirectory (desktop only) +add_subdirectory(06_platform_logging) diff --git a/include/vertexnova/logging/logging.h b/include/vertexnova/logging/logging.h index 1893863..9dd9247 100644 --- a/include/vertexnova/logging/logging.h +++ b/include/vertexnova/logging/logging.h @@ -55,6 +55,7 @@ struct VNE_LOGGING_API LoggerConfig { //!< warn, error, fatal). LogLevel flush_level = LogLevel::eError; //!< The log level at which the logger will flush its output. bool async = false; //!< Flag indicating whether the logger operates asynchronously. + bool file_append = true; //!< If false, the log file is truncated on open (overwrite mode). }; inline constexpr const char* kDefaultLoggerName = "vertexnova"; //!< Default logger name. @@ -138,8 +139,10 @@ class VNE_LOGGING_API Logging { * * @param logger_name The name of the logger to which the file sink will be added. * @param file The name of the file where logs will be written. + * @param append If true (default), opens the log file in append mode; if false, + * truncates/overwrites the file on open. */ - static void addFileSink(const std::string& logger_name, const std::string& file); + static void addFileSink(const std::string& logger_name, const std::string& file, bool append = true); /** * @brief Sets the console pattern for the logger. @@ -184,28 +187,52 @@ class VNE_LOGGING_API Logging { static void setFlushLevel(const std::string& logger_name, LogLevel level); /** - * @brief Gets the appropriate log directory based on build context. - * - * Determines the best location for log files based on: - * 1. Build directory (if available) - * 2. Current working directory (if it looks like a build dir) - * 3. Fallback to "logfiles" in project root - * - * @return The path to the log directory. + * @brief Gets the platform-specific log directory (delegates to getPlatformSpecificLogDirectory). + * @return The path to the log directory, or "logs" as a relative fallback. */ static std::string getLogDirectory(); + /** + * @brief Sets the log directory provided by the application. + * + * Mobile platforms (iOS, visionOS, Android) have sandboxed filesystems + * that require platform APIs to resolve the correct writable path at + * runtime. Call this before configureLogger() with the path obtained + * from the platform: + * + * iOS/visionOS (Swift): + * @code + * let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + * .appendingPathComponent("logs").path + * VneLogging.setAppLogDirectory(dir) + * @endcode + * + * Android (JNI): + * @code + * jstring jpath = (jstring)env->CallObjectMethod(context, getFilesDirMethod); + * std::string path = std::string(env->GetStringUTFChars(jpath, nullptr)) + "/logs"; + * vne::log::Logging::setAppLogDirectory(path); + * @endcode + * + * @param path Absolute writable path for log files. Empty string resets to auto-detection. + * + * @note Thread-safety: call during startup before logger configuration. + * Concurrent calls with logging/configuration APIs are not guaranteed to be safe. + */ + static void setAppLogDirectory(const std::string& path); + /** * @brief Gets platform-specific log directory with enhanced mobile support. * - * Provides platform-aware log directory paths following best practices: + * Returns the app-provided directory (set via setAppLogDirectory) if one + * has been set. Otherwise falls back to platform conventions: * - Windows: AppData\Local\VertexNova\logs * - macOS: ~/Library/Logs/VertexNova * - Linux: ~/.local/share/VertexNova/logs (XDG compliant) - * - Android: /data/data/com.vertexnova.app/files/logs (app-private) - * - iOS: ~/Documents/VertexNova/logs (app-private, backed up) + * - iOS: ~/Documents/VertexNova/logs (simulator only; use setAppLogDirectory on device) + * - visionOS / Android / Web: returns "" (use setAppLogDirectory) * - * @return The platform-specific path to the log directory. + * @return The platform-specific path to the log directory, or "" if unavailable. */ static std::string getPlatformSpecificLogDirectory(); @@ -255,6 +282,7 @@ class VNE_LOGGING_API Logging { private: static std::shared_ptr s_log_manager; //!< The LogManager instance for managing logging operations. + static std::string s_app_log_dir; //!< App-provided log directory (set via setAppLogDirectory). }; } // namespace vne::log diff --git a/src/vertexnova/logging/core/async_logger.cpp b/src/vertexnova/logging/core/async_logger.cpp index bda9cfb..739d5f5 100644 --- a/src/vertexnova/logging/core/async_logger.cpp +++ b/src/vertexnova/logging/core/async_logger.cpp @@ -56,7 +56,7 @@ void AsyncLogger::log(const std::string& category_name, if (level >= current_log_level_) { dispatcher_->dispatch(log_sinks_, category_name, level, time_stamp_type, message, file, function, line); if (level >= flush_level_) { - dispatcher_->flush(log_sinks_); + dispatcher_->flush(log_sinks_, level); } } } diff --git a/src/vertexnova/logging/core/console_log_sink.cpp b/src/vertexnova/logging/core/console_log_sink.cpp index 9d546ec..ada40e7 100644 --- a/src/vertexnova/logging/core/console_log_sink.cpp +++ b/src/vertexnova/logging/core/console_log_sink.cpp @@ -79,6 +79,17 @@ void ConsoleLogSink::flush() { std::cerr.flush(); } +void ConsoleLogSink::flush(LogLevel level) { + if (level >= LogLevel::eError) { + // Error-triggered flush must drain stdout too — prior info/warn lines may still + // be buffered on cout when the error is written to cerr (e.g. before crash). + std::cout.flush(); + std::cerr.flush(); + } else { + std::cout.flush(); + } +} + std::string ConsoleLogSink::getPattern() const { return pattern_; } diff --git a/src/vertexnova/logging/core/console_log_sink.h b/src/vertexnova/logging/core/console_log_sink.h index 85a78f6..175601b 100644 --- a/src/vertexnova/logging/core/console_log_sink.h +++ b/src/vertexnova/logging/core/console_log_sink.h @@ -71,12 +71,20 @@ class VNE_LOGGING_API ConsoleLogSink : public ILogSink { uint32_t line) override; /** - * @brief Flushes the console output. - * - * Ensures that all log messages have been output to the console. + * @brief Flushes all console streams (stdout and stderr). */ void flush() override; + /** + * @brief Flushes console output for the given level. + * + * Flushes std::cout for levels below eError. For eError and eFatal, flushes + * both std::cout and std::cerr so earlier stdout logs are not left buffered. + * + * @param level The level of the message that triggered the flush. + */ + void flush(LogLevel level) override; + /** * @brief Gets the current log pattern. * diff --git a/src/vertexnova/logging/core/file_log_sink.h b/src/vertexnova/logging/core/file_log_sink.h index 5b4dc06..dcd7e6f 100644 --- a/src/vertexnova/logging/core/file_log_sink.h +++ b/src/vertexnova/logging/core/file_log_sink.h @@ -70,9 +70,7 @@ class VNE_LOGGING_API FileLogSink : public ILogSink { uint32_t line) override; /** - * @brief Flushes the file output. - * - * Ensures that all log messages have been written to the file. + * @brief Flushes the log file. */ void flush() override; diff --git a/src/vertexnova/logging/core/log_dispatcher.cpp b/src/vertexnova/logging/core/log_dispatcher.cpp index eed6f89..57e841a 100644 --- a/src/vertexnova/logging/core/log_dispatcher.cpp +++ b/src/vertexnova/logging/core/log_dispatcher.cpp @@ -53,5 +53,12 @@ void LogDispatcher::flush(const std::vector>& log_sink } } +void LogDispatcher::flush(const std::vector>& log_sinks, LogLevel level) { + log_queue_worker_.flush(); + for (auto& sink : log_sinks) { + sink->flush(level); + } +} + } // namespace log } // namespace vne diff --git a/src/vertexnova/logging/core/log_dispatcher.h b/src/vertexnova/logging/core/log_dispatcher.h index db1c820..6d8f772 100644 --- a/src/vertexnova/logging/core/log_dispatcher.h +++ b/src/vertexnova/logging/core/log_dispatcher.h @@ -76,15 +76,21 @@ class VNE_LOGGING_API LogDispatcher { uint32_t line); /** - * @brief Flushes all pending log messages in the log_sinks. + * @brief Drains the queue and fully flushes every sink. * * @param log_sinks The collection of log sinks to flush. - * - * This method ensures that all log messages currently in the queue are - * processed and written out by the log sinks. */ void flush(const std::vector>& log_sinks); + /** + * @brief Drains the queue and flushes the stream matching the given level. + * + * @param log_sinks The collection of log sinks to flush. + * @param level The level that triggered the flush — forwarded to each sink + * so only the relevant output stream is flushed. + */ + void flush(const std::vector>& log_sinks, LogLevel level); + private: // Deleted copy constructor and assignment operator LogDispatcher(const LogDispatcher&) = delete; diff --git a/src/vertexnova/logging/core/log_sink.h b/src/vertexnova/logging/core/log_sink.h index 03f76f0..59e2c0e 100644 --- a/src/vertexnova/logging/core/log_sink.h +++ b/src/vertexnova/logging/core/log_sink.h @@ -60,14 +60,27 @@ class VNE_LOGGING_API ILogSink { uint32_t line) = 0; /** - * @brief Flushes the log output. + * @brief Flushes all buffered output from this sink. * - * Pure virtual function that must be implemented by derived classes. - * This function is used to ensure that all log messages have been - * processed and output. + * Used for explicit full flush (e.g. ILogger::flush()). Legacy custom sinks + * implement only this method; the default flush(LogLevel) forwards here. */ virtual void flush() = 0; + /** + * @brief Flushes output for the log level that triggered auto-flush. + * + * Override when the sink routes by level (e.g. ConsoleLogSink). The default + * calls flush() so sinks that only implement flush() keep working. + * + * ConsoleLogSink flushes std::cout for sub-error levels; for eError and above + * it flushes both std::cout and std::cerr. File sinks typically override + * flush() only (level is ignored). + * + * @param level The level of the message that triggered the flush. + */ + virtual void flush(LogLevel /*level*/) { flush(); } + /** * @brief Gets the log pattern. * diff --git a/src/vertexnova/logging/core/sync_logger.cpp b/src/vertexnova/logging/core/sync_logger.cpp index 9c0e08f..e222be3 100644 --- a/src/vertexnova/logging/core/sync_logger.cpp +++ b/src/vertexnova/logging/core/sync_logger.cpp @@ -59,7 +59,7 @@ void SyncLogger::log(const std::string& category_name, } if (level >= flush_level_) { for (auto& sink : log_sinks_) { - sink->flush(); + sink->flush(level); } } } diff --git a/src/vertexnova/logging/core/text_color.cpp b/src/vertexnova/logging/core/text_color.cpp index 7b6743c..14178c8 100644 --- a/src/vertexnova/logging/core/text_color.cpp +++ b/src/vertexnova/logging/core/text_color.cpp @@ -44,19 +44,24 @@ std::atomic g_color_detected{-1}; /** * @brief Detects if the terminal supports ANSI colors. * - * Colors are enabled by default on most platforms. Disabled on: - * - Web (Emscripten): Browser console doesn't support ANSI codes - * - iOS: Xcode console doesn't support ANSI codes + * Colors are disabled at compile time on platforms with no ANSI support: + * - Web (Emscripten): browser console doesn't support ANSI codes + * - iOS / visionOS: Xcode console doesn't support ANSI codes + * - Android: logcat doesn't support ANSI codes * - * Note: Some terminals (like Xcode debugger on macOS) may show raw escape - * codes instead of colors. Use setColorEnabled(false) to disable colors - * programmatically, or set the NO_COLOR environment variable. + * On other platforms: + * - Respects the NO_COLOR env var (https://no-color.org/) + * + * Colors can be overridden at runtime with setColorEnabled(). + * Note: isatty() is intentionally NOT used — IDE output panels (Qt Creator, + * CLion, etc.) redirect stdout without being a real TTY, yet they render ANSI + * color codes correctly. */ bool detectColorSupport() { -#if defined(VNE_PLATFORM_WEB) || defined(VNE_PLATFORM_IOS) +#if defined(VNE_PLATFORM_WEB) || defined(VNE_PLATFORM_IOS) || defined(VNE_PLATFORM_VISIONOS) \ + || defined(VNE_PLATFORM_ANDROID) return false; #else - // Check NO_COLOR environment variable (https://no-color.org/) if (std::getenv("NO_COLOR") != nullptr) { return false; } diff --git a/src/vertexnova/logging/log_manager.cpp b/src/vertexnova/logging/log_manager.cpp index b09527d..3b8aa02 100644 --- a/src/vertexnova/logging/log_manager.cpp +++ b/src/vertexnova/logging/log_manager.cpp @@ -60,10 +60,10 @@ void LogManager::addConsoleSink(const std::string& logger_name) { } } -void LogManager::addFileSink(const std::string& logger_name, const std::string& log_file_path) { +void LogManager::addFileSink(const std::string& logger_name, const std::string& log_file_path, bool append) { auto logger = getLogger(logger_name); if (logger) { - auto file_sink = std::make_unique(log_file_path); + auto file_sink = std::make_unique(log_file_path, append); logger->addLogSink(std::move(file_sink)); } } diff --git a/src/vertexnova/logging/log_manager.h b/src/vertexnova/logging/log_manager.h index 261576d..bde34da 100644 --- a/src/vertexnova/logging/log_manager.h +++ b/src/vertexnova/logging/log_manager.h @@ -79,8 +79,9 @@ class VNE_LOGGING_API LogManager final { * * @param logger_name The name of the logger to which the file sink should be added. * @param log_file_path The path where the log file should be created. + * @param append Whether to append to the existing log file (true) or overwrite it (false). Default is true. */ - void addFileSink(const std::string& logger_name, const std::string& log_file_path); + void addFileSink(const std::string& logger_name, const std::string& log_file_path, bool append = true); /** * @brief Sets the pattern for the console sink of a logger. diff --git a/src/vertexnova/logging/logging.cpp b/src/vertexnova/logging/logging.cpp index ac13374..13961b0 100644 --- a/src/vertexnova/logging/logging.cpp +++ b/src/vertexnova/logging/logging.cpp @@ -62,32 +62,6 @@ constexpr const char* getPathSeparator() { #endif } -/** - * @brief Checks if a path looks like a build directory. - * @param path The path to check. - * @return True if the path appears to be a build directory. - */ -bool isBuildDirectory(const std::string& path) { - return path.find("build") != std::string::npos || path.find("out") != std::string::npos - || path.find("bin") != std::string::npos || path.find("cmake-build") != std::string::npos; -} - -/** - * @brief Gets the current working directory. - * @return Current working directory path. - */ -std::string getCurrentDirectory() { -#ifdef VNE_PLATFORM_WEB - return "."; -#else - try { - return std::filesystem::current_path().string(); - } catch (...) { - return "."; - } -#endif -} - /** * @brief Creates directories recursively (portable). * @param path The directory path to create. @@ -118,6 +92,7 @@ bool createDirectories(const std::string& path) { //============================================================================== std::shared_ptr Logging::s_log_manager = nullptr; +std::string Logging::s_app_log_dir; //============================================================================== // Core logging functions @@ -151,11 +126,11 @@ void Logging::addConsoleSink(const std::string& logger_name) { s_log_manager->addConsoleSink(logger_name); } -void Logging::addFileSink(const std::string& logger_name, const std::string& file) { +void Logging::addFileSink(const std::string& logger_name, const std::string& file, bool append) { if (!s_log_manager) { s_log_manager = std::make_shared(); } - s_log_manager->addFileSink(logger_name, file); + s_log_manager->addFileSink(logger_name, file, append); } void Logging::setConsolePattern(const std::string& logger_name, const std::string& pattern) { @@ -229,7 +204,7 @@ void Logging::configureLogger(const LoggerConfig& cfg) { #ifndef VNE_PLATFORM_WEB if (cfg.sink == LogSinkType::eFile || cfg.sink == LogSinkType::eBoth) { if (!cfg.file_path.empty()) { - addFileSink(cfg.name, cfg.file_path); + addFileSink(cfg.name, cfg.file_path, cfg.file_append); if (!cfg.file_pattern.empty()) { setFilePattern(cfg.name, cfg.file_pattern); } @@ -245,17 +220,27 @@ void Logging::configureLogger(const LoggerConfig& cfg) { // Path utility functions //============================================================================== +void Logging::setAppLogDirectory(const std::string& path) { + s_app_log_dir = path; + if (!path.empty()) { + ensureLogDirectoryExists(path); + } +} + std::string Logging::getLogDirectory() { - // Delegate to platform-specific implementation return getPlatformSpecificLogDirectory(); } std::string Logging::getPlatformSpecificLogDirectory() { + // App-provided path takes priority (required on iOS, visionOS, Android). + if (!s_app_log_dir.empty()) { + return s_app_log_dir; + } + const std::string sep = getPathSeparator(); const std::string home = getHomeDirectory(); #if defined(VNE_PLATFORM_WIN) || defined(_WIN32) - // Windows: Use LocalAppData if (const char* appdata = std::getenv("LOCALAPPDATA")) { return std::string(appdata) + sep + "VertexNova" + sep + "logs"; } @@ -264,23 +249,22 @@ std::string Logging::getPlatformSpecificLogDirectory() { } #elif defined(VNE_PLATFORM_MACOS) - // macOS: Use ~/Library/Logs if (!home.empty()) { return home + "/Library/Logs/VertexNova"; } #elif defined(VNE_PLATFORM_IOS) - // iOS: Use ~/Documents (app sandbox) + // Simulator fallback only. On a real device call setAppLogDirectory() with + // the path from FileManager.default.urls(for: .documentDirectory, ...). if (!home.empty()) { return home + "/Documents/VertexNova/logs"; } -#elif defined(VNE_PLATFORM_ANDROID) - // Android: App internal storage - return "/data/data/com.vertexnova.app/files/logs"; +#elif defined(VNE_PLATFORM_VISIONOS) || defined(VNE_PLATFORM_ANDROID) || defined(VNE_PLATFORM_WEB) + // Sandbox/no-filesystem platforms: file logging requires setAppLogDirectory(). + return ""; #elif defined(VNE_PLATFORM_LINUX) - // Linux: XDG Base Directory Specification if (const char* xdg = std::getenv("XDG_DATA_HOME")) { return std::string(xdg) + "/VertexNova/logs"; } @@ -288,19 +272,8 @@ std::string Logging::getPlatformSpecificLogDirectory() { return home + "/.local/share/VertexNova/logs"; } -#elif defined(VNE_PLATFORM_WEB) - // Web: No file logging - return ""; - #endif - // Fallback: Check if we're in a build directory - std::string cwd = getCurrentDirectory(); - if (isBuildDirectory(cwd)) { - return cwd + sep + "logs"; - } - - // Final fallback return "logs"; } diff --git a/tests/core/console_log_sink_test.cpp b/tests/core/console_log_sink_test.cpp index c777837..eb6184e 100644 --- a/tests/core/console_log_sink_test.cpp +++ b/tests/core/console_log_sink_test.cpp @@ -124,6 +124,7 @@ TEST_F(ConsoleLogSinkTest, SetPatternChangesLogFormat) { TEST_F(ConsoleLogSinkTest, FlushDoesNotThrow) { log::ConsoleLogSink console_log_sink; EXPECT_NO_THROW(console_log_sink.flush()); + EXPECT_NO_THROW(console_log_sink.flush(log::LogLevel::eInfo)); } TEST_F(ConsoleLogSinkTest, SetPatternUpdatesPattern) { diff --git a/tests/core/log_dispatcher_test.cpp b/tests/core/log_dispatcher_test.cpp index b88d799..0bad5f3 100644 --- a/tests/core/log_dispatcher_test.cpp +++ b/tests/core/log_dispatcher_test.cpp @@ -69,7 +69,7 @@ TEST_F(LogDispatcherTest, TestDispatchAndFlush) { 123); // Flush log sinks - EXPECT_CALL(*dynamic_cast(log_sinks_[0].get()), flush()); - EXPECT_CALL(*dynamic_cast(log_sinks_[1].get()), flush()); - dispatcher.flush(log_sinks_); + EXPECT_CALL(*dynamic_cast(log_sinks_[0].get()), flush(log::LogLevel::eInfo)); + EXPECT_CALL(*dynamic_cast(log_sinks_[1].get()), flush(log::LogLevel::eInfo)); + dispatcher.flush(log_sinks_, log::LogLevel::eInfo); } diff --git a/tests/core/mocks/log_sink_mock.h b/tests/core/mocks/log_sink_mock.h index d4b1bbe..2331167 100644 --- a/tests/core/mocks/log_sink_mock.h +++ b/tests/core/mocks/log_sink_mock.h @@ -52,10 +52,15 @@ class LogSinkMock : public ILogSink { (override)); /** - * @brief Mock method to flush the log sink. + * @brief Mock method to fully flush the log sink. */ MOCK_METHOD(void, flush, (), (override)); + /** + * @brief Mock method to flush for a log level (auto-flush path). + */ + MOCK_METHOD(void, flush, (LogLevel level), (override)); + /** * @brief Gets the log pattern (mock implementation). * diff --git a/tests/logging_path_test.cpp b/tests/logging_path_test.cpp index 5631561..db241ce 100644 --- a/tests/logging_path_test.cpp +++ b/tests/logging_path_test.cpp @@ -21,13 +21,13 @@ namespace vne::log { class LoggingPathTest : public ::testing::Test { protected: void SetUp() override { - // Clean up any existing test directories + Logging::setAppLogDirectory(""); cleanupTestDirectories(); } void TearDown() override { - // Shutdown logging and clean up test files Logging::shutdown(); + Logging::setAppLogDirectory(""); cleanupTestDirectories(); } @@ -71,6 +71,30 @@ TEST_F(LoggingPathTest, GetLogDirectory_DelegatesToPlatformSpecific) { EXPECT_EQ(log_dir1, log_dir2); } +TEST_F(LoggingPathTest, SetAppLogDirectory_ReturnsAppProvidedPath) { + const std::string app_dir = "test_logs/app_override"; + ASSERT_TRUE(Logging::ensureLogDirectoryExists(app_dir)); + + Logging::setAppLogDirectory(app_dir); + + EXPECT_EQ(Logging::getPlatformSpecificLogDirectory(), app_dir); + EXPECT_EQ(Logging::getLogDirectory(), app_dir); + EXPECT_TRUE(std::filesystem::exists(app_dir)); +} + +TEST_F(LoggingPathTest, SetAppLogDirectory_EmptyStringResetsToAutoDetection) { + const std::string baseline = Logging::getPlatformSpecificLogDirectory(); + const std::string app_dir = "test_logs/app_reset"; + + Logging::setAppLogDirectory(app_dir); + ASSERT_EQ(Logging::getPlatformSpecificLogDirectory(), app_dir); + + Logging::setAppLogDirectory(""); + + EXPECT_EQ(Logging::getPlatformSpecificLogDirectory(), baseline); + EXPECT_EQ(Logging::getLogDirectory(), baseline); +} + //============================================================================== // ensureLogDirectoryExists Tests //==============================================================================