From 63e17d02ad3ee000d1537487ef3fc8cd6ca7b52c Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 20:56:28 -0400 Subject: [PATCH 01/16] feat: add cross-platform logging example and enhance logger configuration --- examples/06_platform_logging/CMakeLists.txt | 27 +++++ examples/06_platform_logging/main.cpp | 107 ++++++++++++++++++++ examples/CMakeLists.txt | 3 + include/vertexnova/logging/logging.h | 48 ++++++--- src/vertexnova/logging/core/text_color.cpp | 29 ++++-- src/vertexnova/logging/logging.cpp | 65 ++++-------- tests/logging_path_test.cpp | 2 +- 7 files changed, 213 insertions(+), 68 deletions(-) create mode 100644 examples/06_platform_logging/CMakeLists.txt create mode 100644 examples/06_platform_logging/main.cpp 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..f276d4e --- /dev/null +++ b/examples/06_platform_logging/main.cpp @@ -0,0 +1,107 @@ +/* --------------------------------------------------------------------- + * 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 + * + * 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 (Swift, call before your C++ init): + * + * import Foundation + * // expose a C symbol that the Swift layer can call + * @_silgen_name("vne_set_log_dir") + * func vneSetLogDir(_ path: UnsafePointer) { + * vne::log::Logging::setAppLogDirectory(String(cString: path)) + * } + * // --- in AppDelegate / @main --- + * let dir = FileManager.default + * .urls(for: .documentDirectory, in: .userDomainMask)[0] + * .appendingPathComponent("logs").path + * dir.withCString { vneSetLogDir($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 + +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 = log_dir + "/platform_example.log"; + } + config.log_level = vne::log::LogLevel::eTrace; + + 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..60652ca 100644 --- a/include/vertexnova/logging/logging.h +++ b/include/vertexnova/logging/logging.h @@ -53,7 +53,7 @@ struct VNE_LOGGING_API LoggerConfig { std::string file_path; //!< The path to the file where logs will be written, if applicable. LogLevel log_level = LogLevel::eInfo; //!< The log level for filtering messages (e.g., trace, debug, info, //!< warn, error, fatal). - LogLevel flush_level = LogLevel::eError; //!< The log level at which the logger will flush its output. + LogLevel flush_level = LogLevel::eTrace; //!< The log level at which the logger will flush its output. bool async = false; //!< Flag indicating whether the logger operates asynchronously. }; @@ -184,28 +184,49 @@ 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. + */ + 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 +276,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/text_color.cpp b/src/vertexnova/logging/core/text_color.cpp index 7b6743c..dc8eb27 100644 --- a/src/vertexnova/logging/core/text_color.cpp +++ b/src/vertexnova/logging/core/text_color.cpp @@ -15,6 +15,10 @@ #include #include +#if !defined(VNE_PLATFORM_WIN) && !defined(_WIN32) +#include +#endif + namespace { /** @@ -44,22 +48,31 @@ 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 + * + * On other platforms, runtime detection is also performed: + * - Respects NO_COLOR env var (https://no-color.org/) + * - Checks isatty(stdout) to disable colors in Xcode debugger, piped + * output, and CI environments that redirect stdout. * - * 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. + * Colors can be overridden at runtime with setColorEnabled(). */ 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; } +#if !defined(VNE_PLATFORM_WIN) && !defined(_WIN32) + if (!isatty(fileno(stdout))) { + return false; + } +#endif return true; #endif } diff --git a/src/vertexnova/logging/logging.cpp b/src/vertexnova/logging/logging.cpp index ac13374..8455afb 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 @@ -196,7 +171,7 @@ LoggerConfig Logging::defaultLoggerConfig() { config.console_pattern = "%x [%l] %v"; config.file_pattern = "%x [%n] [%l] [%!] %v"; config.log_level = LogLevel::eInfo; - config.flush_level = LogLevel::eError; + config.flush_level = LogLevel::eTrace; config.async = false; #ifdef VNE_PLATFORM_WEB @@ -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/logging_path_test.cpp b/tests/logging_path_test.cpp index 5631561..66aa22a 100644 --- a/tests/logging_path_test.cpp +++ b/tests/logging_path_test.cpp @@ -224,7 +224,7 @@ TEST_F(LoggingPathTest, DefaultLoggerConfig_HasValidDefaults) { EXPECT_FALSE(config.console_pattern.empty()); EXPECT_FALSE(config.file_pattern.empty()); EXPECT_EQ(config.log_level, LogLevel::eInfo); - EXPECT_EQ(config.flush_level, LogLevel::eError); + EXPECT_EQ(config.flush_level, LogLevel::eTrace); EXPECT_FALSE(config.async); #ifdef VNE_PLATFORM_WEB From ca685f9baa31ef8fb00a3bb3246eb48f9c3fbc71 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 21:01:42 -0400 Subject: [PATCH 02/16] feat: enhance file sink configuration to support append mode --- examples/06_platform_logging/main.cpp | 7 +++++++ include/vertexnova/logging/logging.h | 3 ++- src/vertexnova/logging/log_manager.cpp | 4 ++-- src/vertexnova/logging/log_manager.h | 3 ++- src/vertexnova/logging/logging.cpp | 6 +++--- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/examples/06_platform_logging/main.cpp b/examples/06_platform_logging/main.cpp index f276d4e..8b5c72e 100644 --- a/examples/06_platform_logging/main.cpp +++ b/examples/06_platform_logging/main.cpp @@ -5,6 +5,12 @@ * Author: Ajeet Singh Yadav * Created: May 2026 * + * Autodoc: yes + * + * ---------------------------------------------------------------------- + */ + + /* ---------------------------------------------------------------------- * Example: Cross-platform logging setup * * Demonstrates how to initialise the logger on each supported platform. @@ -91,6 +97,7 @@ int main() { config.file_path = log_dir + "/platform_example.log"; } 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); diff --git a/include/vertexnova/logging/logging.h b/include/vertexnova/logging/logging.h index 60652ca..7ac2458 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::eTrace; //!< 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. @@ -139,7 +140,7 @@ 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. */ - 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. 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 8455afb..59cfd82 100644 --- a/src/vertexnova/logging/logging.cpp +++ b/src/vertexnova/logging/logging.cpp @@ -126,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) { @@ -204,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); } From bd89f5b4cd9550da793999d210f2d6c75105ea20 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 21:12:58 -0400 Subject: [PATCH 03/16] feat: update flush methods to accept log level for better control over output streams --- examples/06_platform_logging/main.cpp | 4 ++-- src/vertexnova/logging/core/async_logger.cpp | 5 +++-- src/vertexnova/logging/core/console_log_sink.cpp | 9 ++++++--- src/vertexnova/logging/core/console_log_sink.h | 8 +++++--- src/vertexnova/logging/core/file_log_sink.cpp | 2 +- src/vertexnova/logging/core/file_log_sink.h | 9 ++++++--- src/vertexnova/logging/core/log_dispatcher.cpp | 4 ++-- src/vertexnova/logging/core/log_dispatcher.h | 10 +++++----- src/vertexnova/logging/core/log_sink.h | 12 +++++++----- src/vertexnova/logging/core/sync_logger.cpp | 5 +++-- tests/core/console_log_sink_test.cpp | 2 +- tests/core/file_log_sink_test.cpp | 6 +++--- tests/core/log_dispatcher_test.cpp | 6 +++--- tests/core/mocks/log_sink_mock.h | 2 +- 14 files changed, 48 insertions(+), 36 deletions(-) diff --git a/examples/06_platform_logging/main.cpp b/examples/06_platform_logging/main.cpp index 8b5c72e..e14ef9b 100644 --- a/examples/06_platform_logging/main.cpp +++ b/examples/06_platform_logging/main.cpp @@ -6,11 +6,11 @@ * Created: May 2026 * * Autodoc: yes - * + * * ---------------------------------------------------------------------- */ - /* ---------------------------------------------------------------------- +/* ---------------------------------------------------------------------- * Example: Cross-platform logging setup * * Demonstrates how to initialise the logger on each supported platform. diff --git a/src/vertexnova/logging/core/async_logger.cpp b/src/vertexnova/logging/core/async_logger.cpp index bda9cfb..85febca 100644 --- a/src/vertexnova/logging/core/async_logger.cpp +++ b/src/vertexnova/logging/core/async_logger.cpp @@ -56,13 +56,14 @@ 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); } } } void AsyncLogger::flush() { - dispatcher_->flush(log_sinks_); + dispatcher_->flush(log_sinks_, LogLevel::eTrace); // flush cout + dispatcher_->flush(log_sinks_, LogLevel::eError); // flush cerr } std::string AsyncLogger::getName() const { diff --git a/src/vertexnova/logging/core/console_log_sink.cpp b/src/vertexnova/logging/core/console_log_sink.cpp index 9d546ec..f872fff 100644 --- a/src/vertexnova/logging/core/console_log_sink.cpp +++ b/src/vertexnova/logging/core/console_log_sink.cpp @@ -74,9 +74,12 @@ void ConsoleLogSink::log(const std::string& name, } } -void ConsoleLogSink::flush() { - std::cout.flush(); - std::cerr.flush(); +void ConsoleLogSink::flush(LogLevel level) { + if (level >= LogLevel::eError) { + std::cerr.flush(); + } else { + std::cout.flush(); + } } std::string ConsoleLogSink::getPattern() const { diff --git a/src/vertexnova/logging/core/console_log_sink.h b/src/vertexnova/logging/core/console_log_sink.h index 85a78f6..9bffef6 100644 --- a/src/vertexnova/logging/core/console_log_sink.h +++ b/src/vertexnova/logging/core/console_log_sink.h @@ -71,11 +71,13 @@ class VNE_LOGGING_API ConsoleLogSink : public ILogSink { uint32_t line) override; /** - * @brief Flushes the console output. + * @brief Flushes the console stream that the given level writes to. * - * Ensures that all log messages have been output to the console. + * Flushes std::cerr for eError / eFatal, std::cout for all other levels. + * + * @param level The level of the message that triggered the flush. */ - void flush() override; + void flush(LogLevel level) override; /** * @brief Gets the current log pattern. diff --git a/src/vertexnova/logging/core/file_log_sink.cpp b/src/vertexnova/logging/core/file_log_sink.cpp index 39f8c54..a18ab66 100644 --- a/src/vertexnova/logging/core/file_log_sink.cpp +++ b/src/vertexnova/logging/core/file_log_sink.cpp @@ -64,7 +64,7 @@ void FileLogSink::log(const std::string& name, file_stream_ << formatted_log << '\n'; } -void FileLogSink::flush() { +void FileLogSink::flush(LogLevel /*level*/) { if (file_stream_.is_open()) { file_stream_.flush(); } diff --git a/src/vertexnova/logging/core/file_log_sink.h b/src/vertexnova/logging/core/file_log_sink.h index 5b4dc06..7d24ddb 100644 --- a/src/vertexnova/logging/core/file_log_sink.h +++ b/src/vertexnova/logging/core/file_log_sink.h @@ -70,11 +70,14 @@ class VNE_LOGGING_API FileLogSink : public ILogSink { uint32_t line) override; /** - * @brief Flushes the file output. + * @brief Flushes the log file. * - * Ensures that all log messages have been written to the file. + * The level parameter is accepted for interface compatibility but is not + * used — there is only one output stream. + * + * @param level Unused. */ - void flush() override; + void flush(LogLevel level) override; /** * @brief Gets the current log pattern. diff --git a/src/vertexnova/logging/core/log_dispatcher.cpp b/src/vertexnova/logging/core/log_dispatcher.cpp index eed6f89..fa56745 100644 --- a/src/vertexnova/logging/core/log_dispatcher.cpp +++ b/src/vertexnova/logging/core/log_dispatcher.cpp @@ -46,10 +46,10 @@ void LogDispatcher::dispatch(const std::vector>& log_s }); } -void LogDispatcher::flush(const std::vector>& log_sinks) { +void LogDispatcher::flush(const std::vector>& log_sinks, LogLevel level) { log_queue_worker_.flush(); for (auto& sink : log_sinks) { - sink->flush(); + sink->flush(level); } } diff --git a/src/vertexnova/logging/core/log_dispatcher.h b/src/vertexnova/logging/core/log_dispatcher.h index db1c820..255801c 100644 --- a/src/vertexnova/logging/core/log_dispatcher.h +++ b/src/vertexnova/logging/core/log_dispatcher.h @@ -76,14 +76,14 @@ class VNE_LOGGING_API LogDispatcher { uint32_t line); /** - * @brief Flushes all pending log messages in the log_sinks. + * @brief Drains the queue and flushes the stream matching the given level. * * @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. + * @param level The level that triggered the flush — forwarded to each sink + * so only the relevant output stream is flushed. + * Defaults to eTrace (flushes std::cout). */ - void flush(const std::vector>& log_sinks); + void flush(const std::vector>& log_sinks, LogLevel level = LogLevel::eTrace); private: // Deleted copy constructor and assignment operator diff --git a/src/vertexnova/logging/core/log_sink.h b/src/vertexnova/logging/core/log_sink.h index 03f76f0..93d5fe3 100644 --- a/src/vertexnova/logging/core/log_sink.h +++ b/src/vertexnova/logging/core/log_sink.h @@ -60,13 +60,15 @@ class VNE_LOGGING_API ILogSink { uint32_t line) = 0; /** - * @brief Flushes the log output. + * @brief Flushes the output stream that corresponds to the given log level. * - * 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. + * Implementations should flush only the stream the level was written to + * (e.g. ConsoleLogSink flushes std::cerr for errors, std::cout otherwise). + * File sinks ignore the level and always flush their file stream. + * + * @param level The level of the message that triggered the flush. */ - virtual void flush() = 0; + virtual void flush(LogLevel level) = 0; /** * @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..1a1c430 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); } } } @@ -68,7 +68,8 @@ void SyncLogger::log(const std::string& category_name, void SyncLogger::flush() { std::lock_guard lock(mutex_); for (auto& sink : log_sinks_) { - sink->flush(); + sink->flush(LogLevel::eTrace); // flush cout (cout is the buffered stream) + sink->flush(LogLevel::eError); // flush cerr (unbuffered in practice, but be explicit) } } diff --git a/tests/core/console_log_sink_test.cpp b/tests/core/console_log_sink_test.cpp index c777837..bfdd609 100644 --- a/tests/core/console_log_sink_test.cpp +++ b/tests/core/console_log_sink_test.cpp @@ -123,7 +123,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/file_log_sink_test.cpp b/tests/core/file_log_sink_test.cpp index 8968969..3976f13 100644 --- a/tests/core/file_log_sink_test.cpp +++ b/tests/core/file_log_sink_test.cpp @@ -61,7 +61,7 @@ TEST_F(FileLogSinkTest, LogWritesToFile) { "TestFile", "TestFunction", 42); - file_sync.flush(); + file_sync.flush(log::LogLevel::eTrace); } std::ifstream infile(unique_file); std::string line; @@ -154,7 +154,7 @@ TEST_F(FileLogSinkTest, FlushWritesToFile) { EXPECT_FALSE(found_before) << "Message should not be present before flush."; // Now flush and check for the message, retry a few times for robustness - file_sink.flush(); + file_sink.flush(log::LogLevel::eTrace); bool found_after = false; for (int attempt = 0; attempt < 3 && !found_after; ++attempt) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); @@ -184,7 +184,7 @@ TEST_F(FileLogSinkTest, SetPatternChangesLogFormat) { "TestFile", "TestFunction", 42); - file_sink.flush(); + file_sink.flush(log::LogLevel::eTrace); } std::ifstream infile(unique_file); std::string line; 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..7552283 100644 --- a/tests/core/mocks/log_sink_mock.h +++ b/tests/core/mocks/log_sink_mock.h @@ -54,7 +54,7 @@ class LogSinkMock : public ILogSink { /** * @brief Mock method to flush the log sink. */ - MOCK_METHOD(void, flush, (), (override)); + MOCK_METHOD(void, flush, (LogLevel level), (override)); /** * @brief Gets the log pattern (mock implementation). From 13cb1b10d13c9526552623c1eed09b81341ec16d Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 21:56:29 -0400 Subject: [PATCH 04/16] feat: enhance logging file path configuration using std::filesystem for cross-platform compatibility --- examples/06_platform_logging/main.cpp | 4 +++- include/vertexnova/logging/logging.h | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/06_platform_logging/main.cpp b/examples/06_platform_logging/main.cpp index e14ef9b..621d569 100644 --- a/examples/06_platform_logging/main.cpp +++ b/examples/06_platform_logging/main.cpp @@ -59,6 +59,8 @@ * ---------------------------------------------------------------------- */ +#include + #include namespace { @@ -94,7 +96,7 @@ int main() { vne::log::LoggerConfig config = vne::log::Logging::defaultLoggerConfig(); config.sink = chooseSink(); if (!log_dir.empty()) { - config.file_path = log_dir + "/platform_example.log"; + 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 diff --git a/include/vertexnova/logging/logging.h b/include/vertexnova/logging/logging.h index 7ac2458..e8c6879 100644 --- a/include/vertexnova/logging/logging.h +++ b/include/vertexnova/logging/logging.h @@ -139,6 +139,8 @@ 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, bool append = true); @@ -213,6 +215,9 @@ class VNE_LOGGING_API Logging { * @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); From ad6d9bb58faa5d6035fb1a38e7c49b2c723dfeba Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 21:58:11 -0400 Subject: [PATCH 05/16] refactor: update flush method signature to remove default log level parameter for improved clarity --- src/vertexnova/logging/core/log_dispatcher.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vertexnova/logging/core/log_dispatcher.h b/src/vertexnova/logging/core/log_dispatcher.h index 255801c..0f5fff8 100644 --- a/src/vertexnova/logging/core/log_dispatcher.h +++ b/src/vertexnova/logging/core/log_dispatcher.h @@ -81,9 +81,8 @@ class VNE_LOGGING_API LogDispatcher { * @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. - * Defaults to eTrace (flushes std::cout). */ - void flush(const std::vector>& log_sinks, LogLevel level = LogLevel::eTrace); + void flush(const std::vector>& log_sinks, LogLevel level); private: // Deleted copy constructor and assignment operator From 9e608daea8f2eebee0bd2492c72e885580bc0cc2 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 21:59:34 -0400 Subject: [PATCH 06/16] chore: include header in text_color.cpp for improved functionality --- src/vertexnova/logging/core/text_color.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vertexnova/logging/core/text_color.cpp b/src/vertexnova/logging/core/text_color.cpp index dc8eb27..3f4e6d6 100644 --- a/src/vertexnova/logging/core/text_color.cpp +++ b/src/vertexnova/logging/core/text_color.cpp @@ -12,6 +12,7 @@ #include "text_color.h" #include +#include #include #include From 8d3f4be41b98256234cc91ee92b11e16372b66b8 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 22:00:52 -0400 Subject: [PATCH 07/16] refactor: use scoped calls for isatty and fileno in detectColorSupport function for improved clarity --- src/vertexnova/logging/core/text_color.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vertexnova/logging/core/text_color.cpp b/src/vertexnova/logging/core/text_color.cpp index 3f4e6d6..94d054b 100644 --- a/src/vertexnova/logging/core/text_color.cpp +++ b/src/vertexnova/logging/core/text_color.cpp @@ -12,11 +12,11 @@ #include "text_color.h" #include -#include #include #include #if !defined(VNE_PLATFORM_WIN) && !defined(_WIN32) +#include #include #endif @@ -70,7 +70,7 @@ bool detectColorSupport() { return false; } #if !defined(VNE_PLATFORM_WIN) && !defined(_WIN32) - if (!isatty(fileno(stdout))) { + if (!::isatty(::fileno(::stdout))) { return false; } #endif From ff70fa8acec74b7f0060917612be002cd5363c33 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 22:01:30 -0400 Subject: [PATCH 08/16] docs: update iOS/visionOS logging example to clarify C wrapper usage for Swift integration --- examples/06_platform_logging/main.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/examples/06_platform_logging/main.cpp b/examples/06_platform_logging/main.cpp index 621d569..9422f0c 100644 --- a/examples/06_platform_logging/main.cpp +++ b/examples/06_platform_logging/main.cpp @@ -22,19 +22,24 @@ * Platform bridging snippets * -------------------------- * - * iOS / visionOS (Swift, call before your C++ init): + * iOS / visionOS (export a C wrapper from C++, call it from Swift before init): * - * import Foundation - * // expose a C symbol that the Swift layer can call - * @_silgen_name("vne_set_log_dir") - * func vneSetLogDir(_ path: UnsafePointer) { - * vne::log::Logging::setAppLogDirectory(String(cString: path)) + * // 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 { vneSetLogDir($0) } + * dir.withCString { vne_set_log_dir($0) } * * Android (JNI, call from your Activity / Application.onCreate): * From 661309024c6f39a3bb56acf15751d3007334d4c0 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 22:02:21 -0400 Subject: [PATCH 09/16] docs: enhance ConsoleLogSink flush documentation to clarify output stream behavior for different log levels --- src/vertexnova/logging/core/console_log_sink.cpp | 3 +++ src/vertexnova/logging/core/console_log_sink.h | 5 +++-- src/vertexnova/logging/core/log_sink.h | 5 +++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vertexnova/logging/core/console_log_sink.cpp b/src/vertexnova/logging/core/console_log_sink.cpp index f872fff..505f4f5 100644 --- a/src/vertexnova/logging/core/console_log_sink.cpp +++ b/src/vertexnova/logging/core/console_log_sink.cpp @@ -76,6 +76,9 @@ void ConsoleLogSink::log(const std::string& name, 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(); diff --git a/src/vertexnova/logging/core/console_log_sink.h b/src/vertexnova/logging/core/console_log_sink.h index 9bffef6..bf83c60 100644 --- a/src/vertexnova/logging/core/console_log_sink.h +++ b/src/vertexnova/logging/core/console_log_sink.h @@ -71,9 +71,10 @@ class VNE_LOGGING_API ConsoleLogSink : public ILogSink { uint32_t line) override; /** - * @brief Flushes the console stream that the given level writes to. + * @brief Flushes console output for the given level. * - * Flushes std::cerr for eError / eFatal, std::cout for all other levels. + * 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. */ diff --git a/src/vertexnova/logging/core/log_sink.h b/src/vertexnova/logging/core/log_sink.h index 93d5fe3..937afd0 100644 --- a/src/vertexnova/logging/core/log_sink.h +++ b/src/vertexnova/logging/core/log_sink.h @@ -62,8 +62,9 @@ class VNE_LOGGING_API ILogSink { /** * @brief Flushes the output stream that corresponds to the given log level. * - * Implementations should flush only the stream the level was written to - * (e.g. ConsoleLogSink flushes std::cerr for errors, std::cout otherwise). + * Implementations flush the stream(s) appropriate for the level. ConsoleLogSink + * flushes std::cout for sub-error levels; for eError and above it flushes both + * std::cout and std::cerr so prior stdout output is not left buffered. * File sinks ignore the level and always flush their file stream. * * @param level The level of the message that triggered the flush. From 857f08e242c48d8e00214c1747199512ec15720a Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 22:04:10 -0400 Subject: [PATCH 10/16] fix: change default flush level from Trace to Error in LoggerConfig for improved logging behavior --- include/vertexnova/logging/logging.h | 2 +- src/vertexnova/logging/logging.cpp | 2 +- tests/logging_path_test.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/vertexnova/logging/logging.h b/include/vertexnova/logging/logging.h index e8c6879..9dd9247 100644 --- a/include/vertexnova/logging/logging.h +++ b/include/vertexnova/logging/logging.h @@ -53,7 +53,7 @@ struct VNE_LOGGING_API LoggerConfig { std::string file_path; //!< The path to the file where logs will be written, if applicable. LogLevel log_level = LogLevel::eInfo; //!< The log level for filtering messages (e.g., trace, debug, info, //!< warn, error, fatal). - LogLevel flush_level = LogLevel::eTrace; //!< The log level at which the logger will flush its output. + 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). }; diff --git a/src/vertexnova/logging/logging.cpp b/src/vertexnova/logging/logging.cpp index 59cfd82..13961b0 100644 --- a/src/vertexnova/logging/logging.cpp +++ b/src/vertexnova/logging/logging.cpp @@ -171,7 +171,7 @@ LoggerConfig Logging::defaultLoggerConfig() { config.console_pattern = "%x [%l] %v"; config.file_pattern = "%x [%n] [%l] [%!] %v"; config.log_level = LogLevel::eInfo; - config.flush_level = LogLevel::eTrace; + config.flush_level = LogLevel::eError; config.async = false; #ifdef VNE_PLATFORM_WEB diff --git a/tests/logging_path_test.cpp b/tests/logging_path_test.cpp index 66aa22a..5631561 100644 --- a/tests/logging_path_test.cpp +++ b/tests/logging_path_test.cpp @@ -224,7 +224,7 @@ TEST_F(LoggingPathTest, DefaultLoggerConfig_HasValidDefaults) { EXPECT_FALSE(config.console_pattern.empty()); EXPECT_FALSE(config.file_pattern.empty()); EXPECT_EQ(config.log_level, LogLevel::eInfo); - EXPECT_EQ(config.flush_level, LogLevel::eTrace); + EXPECT_EQ(config.flush_level, LogLevel::eError); EXPECT_FALSE(config.async); #ifdef VNE_PLATFORM_WEB From cf634f4800fd8bbbde2a16ffc6b226b30495e3d8 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 22:06:03 -0400 Subject: [PATCH 11/16] refactor: unify flush method signatures across log sinks to improve consistency and simplify usage --- src/vertexnova/logging/core/async_logger.cpp | 3 +-- .../logging/core/console_log_sink.cpp | 5 +++++ .../logging/core/console_log_sink.h | 5 +++++ src/vertexnova/logging/core/file_log_sink.cpp | 2 +- src/vertexnova/logging/core/file_log_sink.h | 7 +----- .../logging/core/log_dispatcher.cpp | 7 ++++++ src/vertexnova/logging/core/log_dispatcher.h | 7 ++++++ src/vertexnova/logging/core/log_sink.h | 22 ++++++++++++++----- src/vertexnova/logging/core/sync_logger.cpp | 3 +-- tests/core/console_log_sink_test.cpp | 1 + tests/core/file_log_sink_test.cpp | 6 ++--- tests/core/mocks/log_sink_mock.h | 7 +++++- 12 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/vertexnova/logging/core/async_logger.cpp b/src/vertexnova/logging/core/async_logger.cpp index 85febca..739d5f5 100644 --- a/src/vertexnova/logging/core/async_logger.cpp +++ b/src/vertexnova/logging/core/async_logger.cpp @@ -62,8 +62,7 @@ void AsyncLogger::log(const std::string& category_name, } void AsyncLogger::flush() { - dispatcher_->flush(log_sinks_, LogLevel::eTrace); // flush cout - dispatcher_->flush(log_sinks_, LogLevel::eError); // flush cerr + dispatcher_->flush(log_sinks_); } std::string AsyncLogger::getName() const { diff --git a/src/vertexnova/logging/core/console_log_sink.cpp b/src/vertexnova/logging/core/console_log_sink.cpp index 505f4f5..ada40e7 100644 --- a/src/vertexnova/logging/core/console_log_sink.cpp +++ b/src/vertexnova/logging/core/console_log_sink.cpp @@ -74,6 +74,11 @@ void ConsoleLogSink::log(const std::string& name, } } +void ConsoleLogSink::flush() { + std::cout.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 diff --git a/src/vertexnova/logging/core/console_log_sink.h b/src/vertexnova/logging/core/console_log_sink.h index bf83c60..175601b 100644 --- a/src/vertexnova/logging/core/console_log_sink.h +++ b/src/vertexnova/logging/core/console_log_sink.h @@ -70,6 +70,11 @@ class VNE_LOGGING_API ConsoleLogSink : public ILogSink { const std::string& function, uint32_t line) override; + /** + * @brief Flushes all console streams (stdout and stderr). + */ + void flush() override; + /** * @brief Flushes console output for the given level. * diff --git a/src/vertexnova/logging/core/file_log_sink.cpp b/src/vertexnova/logging/core/file_log_sink.cpp index a18ab66..39f8c54 100644 --- a/src/vertexnova/logging/core/file_log_sink.cpp +++ b/src/vertexnova/logging/core/file_log_sink.cpp @@ -64,7 +64,7 @@ void FileLogSink::log(const std::string& name, file_stream_ << formatted_log << '\n'; } -void FileLogSink::flush(LogLevel /*level*/) { +void FileLogSink::flush() { if (file_stream_.is_open()) { file_stream_.flush(); } diff --git a/src/vertexnova/logging/core/file_log_sink.h b/src/vertexnova/logging/core/file_log_sink.h index 7d24ddb..dcd7e6f 100644 --- a/src/vertexnova/logging/core/file_log_sink.h +++ b/src/vertexnova/logging/core/file_log_sink.h @@ -71,13 +71,8 @@ class VNE_LOGGING_API FileLogSink : public ILogSink { /** * @brief Flushes the log file. - * - * The level parameter is accepted for interface compatibility but is not - * used — there is only one output stream. - * - * @param level Unused. */ - void flush(LogLevel level) override; + void flush() override; /** * @brief Gets the current log pattern. diff --git a/src/vertexnova/logging/core/log_dispatcher.cpp b/src/vertexnova/logging/core/log_dispatcher.cpp index fa56745..57e841a 100644 --- a/src/vertexnova/logging/core/log_dispatcher.cpp +++ b/src/vertexnova/logging/core/log_dispatcher.cpp @@ -46,6 +46,13 @@ void LogDispatcher::dispatch(const std::vector>& log_s }); } +void LogDispatcher::flush(const std::vector>& log_sinks) { + log_queue_worker_.flush(); + for (auto& sink : log_sinks) { + sink->flush(); + } +} + void LogDispatcher::flush(const std::vector>& log_sinks, LogLevel level) { log_queue_worker_.flush(); for (auto& sink : log_sinks) { diff --git a/src/vertexnova/logging/core/log_dispatcher.h b/src/vertexnova/logging/core/log_dispatcher.h index 0f5fff8..6d8f772 100644 --- a/src/vertexnova/logging/core/log_dispatcher.h +++ b/src/vertexnova/logging/core/log_dispatcher.h @@ -75,6 +75,13 @@ class VNE_LOGGING_API LogDispatcher { std::string function, uint32_t line); + /** + * @brief Drains the queue and fully flushes every sink. + * + * @param log_sinks The collection of log sinks to flush. + */ + void flush(const std::vector>& log_sinks); + /** * @brief Drains the queue and flushes the stream matching the given level. * diff --git a/src/vertexnova/logging/core/log_sink.h b/src/vertexnova/logging/core/log_sink.h index 937afd0..59e2c0e 100644 --- a/src/vertexnova/logging/core/log_sink.h +++ b/src/vertexnova/logging/core/log_sink.h @@ -60,16 +60,26 @@ class VNE_LOGGING_API ILogSink { uint32_t line) = 0; /** - * @brief Flushes the output stream that corresponds to the given log level. + * @brief Flushes all buffered output from this sink. * - * Implementations flush the stream(s) appropriate for the level. ConsoleLogSink - * flushes std::cout for sub-error levels; for eError and above it flushes both - * std::cout and std::cerr so prior stdout output is not left buffered. - * File sinks ignore the level and always flush their file stream. + * 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) = 0; + 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 1a1c430..e222be3 100644 --- a/src/vertexnova/logging/core/sync_logger.cpp +++ b/src/vertexnova/logging/core/sync_logger.cpp @@ -68,8 +68,7 @@ void SyncLogger::log(const std::string& category_name, void SyncLogger::flush() { std::lock_guard lock(mutex_); for (auto& sink : log_sinks_) { - sink->flush(LogLevel::eTrace); // flush cout (cout is the buffered stream) - sink->flush(LogLevel::eError); // flush cerr (unbuffered in practice, but be explicit) + sink->flush(); } } diff --git a/tests/core/console_log_sink_test.cpp b/tests/core/console_log_sink_test.cpp index bfdd609..eb6184e 100644 --- a/tests/core/console_log_sink_test.cpp +++ b/tests/core/console_log_sink_test.cpp @@ -123,6 +123,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)); } diff --git a/tests/core/file_log_sink_test.cpp b/tests/core/file_log_sink_test.cpp index 3976f13..8968969 100644 --- a/tests/core/file_log_sink_test.cpp +++ b/tests/core/file_log_sink_test.cpp @@ -61,7 +61,7 @@ TEST_F(FileLogSinkTest, LogWritesToFile) { "TestFile", "TestFunction", 42); - file_sync.flush(log::LogLevel::eTrace); + file_sync.flush(); } std::ifstream infile(unique_file); std::string line; @@ -154,7 +154,7 @@ TEST_F(FileLogSinkTest, FlushWritesToFile) { EXPECT_FALSE(found_before) << "Message should not be present before flush."; // Now flush and check for the message, retry a few times for robustness - file_sink.flush(log::LogLevel::eTrace); + file_sink.flush(); bool found_after = false; for (int attempt = 0; attempt < 3 && !found_after; ++attempt) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); @@ -184,7 +184,7 @@ TEST_F(FileLogSinkTest, SetPatternChangesLogFormat) { "TestFile", "TestFunction", 42); - file_sink.flush(log::LogLevel::eTrace); + file_sink.flush(); } std::ifstream infile(unique_file); std::string line; diff --git a/tests/core/mocks/log_sink_mock.h b/tests/core/mocks/log_sink_mock.h index 7552283..2331167 100644 --- a/tests/core/mocks/log_sink_mock.h +++ b/tests/core/mocks/log_sink_mock.h @@ -52,7 +52,12 @@ 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)); From e251893648b71b3c82f5fd68506b74789945b121 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 22:06:55 -0400 Subject: [PATCH 12/16] test: add tests for setAppLogDirectory to verify path setting and reset behavior --- tests/logging_path_test.cpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) 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 //============================================================================== From 46b69609f78ae366710bbb6f3d7e094ab887d199 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 22:23:12 -0400 Subject: [PATCH 13/16] fix: remove NO_COLOR environment variable check from detectColorSupport function --- src/vertexnova/logging/core/text_color.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vertexnova/logging/core/text_color.cpp b/src/vertexnova/logging/core/text_color.cpp index 94d054b..864a669 100644 --- a/src/vertexnova/logging/core/text_color.cpp +++ b/src/vertexnova/logging/core/text_color.cpp @@ -66,9 +66,6 @@ bool detectColorSupport() { || defined(VNE_PLATFORM_ANDROID) return false; #else - if (std::getenv("NO_COLOR") != nullptr) { - return false; - } #if !defined(VNE_PLATFORM_WIN) && !defined(_WIN32) if (!::isatty(::fileno(::stdout))) { return false; From a4bf7f60410c5ce39bd9d9170cb415e8a1a206f9 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 22:25:55 -0400 Subject: [PATCH 14/16] refactor: remove unnecessary includes for non-Windows platforms in text_color.cpp --- src/vertexnova/logging/core/text_color.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vertexnova/logging/core/text_color.cpp b/src/vertexnova/logging/core/text_color.cpp index 864a669..b45801f 100644 --- a/src/vertexnova/logging/core/text_color.cpp +++ b/src/vertexnova/logging/core/text_color.cpp @@ -15,10 +15,6 @@ #include #include -#if !defined(VNE_PLATFORM_WIN) && !defined(_WIN32) -#include -#include -#endif namespace { From a7a26b5395863fe53f98b4e371e118e90e72cb09 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 22:26:44 -0400 Subject: [PATCH 15/16] refactor: update detectColorSupport function to simplify color detection logic --- src/vertexnova/logging/core/text_color.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vertexnova/logging/core/text_color.cpp b/src/vertexnova/logging/core/text_color.cpp index b45801f..646d6c1 100644 --- a/src/vertexnova/logging/core/text_color.cpp +++ b/src/vertexnova/logging/core/text_color.cpp @@ -50,23 +50,22 @@ std::atomic g_color_detected{-1}; * - iOS / visionOS: Xcode console doesn't support ANSI codes * - Android: logcat doesn't support ANSI codes * - * On other platforms, runtime detection is also performed: - * - Respects NO_COLOR env var (https://no-color.org/) - * - Checks isatty(stdout) to disable colors in Xcode debugger, piped - * output, and CI environments that redirect stdout. + * 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) || defined(VNE_PLATFORM_VISIONOS) \ || defined(VNE_PLATFORM_ANDROID) return false; #else -#if !defined(VNE_PLATFORM_WIN) && !defined(_WIN32) - if (!::isatty(::fileno(::stdout))) { + if (std::getenv("NO_COLOR") != nullptr) { return false; } -#endif return true; #endif } From cf3e6a5a9c6db1b79530f53a03a37324d8796746 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Thu, 21 May 2026 22:27:45 -0400 Subject: [PATCH 16/16] refactor: remove unnecessary blank line in text_color.cpp --- src/vertexnova/logging/core/text_color.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vertexnova/logging/core/text_color.cpp b/src/vertexnova/logging/core/text_color.cpp index 646d6c1..14178c8 100644 --- a/src/vertexnova/logging/core/text_color.cpp +++ b/src/vertexnova/logging/core/text_color.cpp @@ -15,7 +15,6 @@ #include #include - namespace { /**