Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
63e17d0
feat: add cross-platform logging example and enhance logger configura…
ajeetsinghyadav May 22, 2026
ca685f9
feat: enhance file sink configuration to support append mode
ajeetsinghyadav May 22, 2026
bd89f5b
feat: update flush methods to accept log level for better control ove…
ajeetsinghyadav May 22, 2026
13cb1b1
feat: enhance logging file path configuration using std::filesystem f…
ajeetsinghyadav May 22, 2026
ad6d9bb
refactor: update flush method signature to remove default log level p…
ajeetsinghyadav May 22, 2026
9e608da
chore: include <cstdio> header in text_color.cpp for improved functio…
ajeetsinghyadav May 22, 2026
8d3f4be
refactor: use scoped calls for isatty and fileno in detectColorSuppor…
ajeetsinghyadav May 22, 2026
ff70fa8
docs: update iOS/visionOS logging example to clarify C wrapper usage …
ajeetsinghyadav May 22, 2026
6613090
docs: enhance ConsoleLogSink flush documentation to clarify output st…
ajeetsinghyadav May 22, 2026
857f08e
fix: change default flush level from Trace to Error in LoggerConfig f…
ajeetsinghyadav May 22, 2026
cf634f4
refactor: unify flush method signatures across log sinks to improve c…
ajeetsinghyadav May 22, 2026
e251893
test: add tests for setAppLogDirectory to verify path setting and res…
ajeetsinghyadav May 22, 2026
46b6960
fix: remove NO_COLOR environment variable check from detectColorSuppo…
ajeetsinghyadav May 22, 2026
a4bf7f6
refactor: remove unnecessary includes for non-Windows platforms in te…
ajeetsinghyadav May 22, 2026
a7a26b5
refactor: update detectColorSupport function to simplify color detect…
ajeetsinghyadav May 22, 2026
cf3e6a5
refactor: remove unnecessary blank line in text_color.cpp
ajeetsinghyadav May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions examples/06_platform_logging/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Comment thread
ajeetsinghyadav marked this conversation as resolved.

add_executable(06_PlatformLogging main.cpp)

target_link_libraries(06_PlatformLogging
PRIVATE
vne::logging
)

target_include_directories(06_PlatformLogging
PRIVATE
$<BUILD_INTERFACE:${VNE_INCLUDE_DIR}>
$<BUILD_INTERFACE:${VNE_SRC_DIR}>
)

endif()
121 changes: 121 additions & 0 deletions examples/06_platform_logging/main.cpp
Original file line number Diff line number Diff line change
@@ -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<CChar>)
*
* // --- 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 <filesystem>

#include <vertexnova/logging/logging.h>

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(<writable path>) 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;
}
3 changes: 3 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
54 changes: 41 additions & 13 deletions include/vertexnova/logging/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Comment thread
ajeetsinghyadav marked this conversation as resolved.
*/
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();

Expand Down Expand Up @@ -255,6 +282,7 @@ class VNE_LOGGING_API Logging {

private:
static std::shared_ptr<LogManager> 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
Expand Down
2 changes: 1 addition & 1 deletion src/vertexnova/logging/core/async_logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/vertexnova/logging/core/console_log_sink.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Comment thread
ajeetsinghyadav marked this conversation as resolved.
}

std::string ConsoleLogSink::getPattern() const {
return pattern_;
}
Expand Down
14 changes: 11 additions & 3 deletions src/vertexnova/logging/core/console_log_sink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
4 changes: 1 addition & 3 deletions src/vertexnova/logging/core/file_log_sink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
7 changes: 7 additions & 0 deletions src/vertexnova/logging/core/log_dispatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,12 @@ void LogDispatcher::flush(const std::vector<std::unique_ptr<ILogSink>>& log_sink
}
}

void LogDispatcher::flush(const std::vector<std::unique_ptr<ILogSink>>& log_sinks, LogLevel level) {
log_queue_worker_.flush();
for (auto& sink : log_sinks) {
sink->flush(level);
}
}

} // namespace log
} // namespace vne
14 changes: 10 additions & 4 deletions src/vertexnova/logging/core/log_dispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::unique_ptr<ILogSink>>& 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<std::unique_ptr<ILogSink>>& log_sinks, LogLevel level);

private:
// Deleted copy constructor and assignment operator
LogDispatcher(const LogDispatcher&) = delete;
Expand Down
21 changes: 17 additions & 4 deletions src/vertexnova/logging/core/log_sink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion src/vertexnova/logging/core/sync_logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
Loading
Loading