From 61898970ab8a0b19dcb2cea7400107be660873a2 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Mon, 1 Jun 2026 13:37:17 +0300 Subject: [PATCH 1/4] fix(mdbx): report initialization failures Notify the configured error callback for constructor-time storage failures so logger registration can surface the cause before the exception prevents a broken backend from being added. Constraint: Keep MdbxLogger C++17-compatible and preserve constructor rethrow semantics Confidence: high Scope-risk: narrow Not-tested: Full test suite; only mdbx_logger_test was run Co-Authored-By: Claude Opus 4.8 --- .../logit_cpp/logit/loggers/MdbxLogger.hpp | 38 ++++++++-- tests/mdbx_logger_test.cpp | 76 +++++++++++++++++++ 2 files changed, 107 insertions(+), 7 deletions(-) diff --git a/include/logit_cpp/logit/loggers/MdbxLogger.hpp b/include/logit_cpp/logit/loggers/MdbxLogger.hpp index 79b9cab..9d76c49 100644 --- a/include/logit_cpp/logit/loggers/MdbxLogger.hpp +++ b/include/logit_cpp/logit/loggers/MdbxLogger.hpp @@ -45,7 +45,7 @@ namespace logit { bool store_large_payloads_separately = true;///< Store large messages in `log_payloads`. MdbxPayloadCompression payload_compression = MdbxPayloadCompression::None; ///< Payload compression. int payload_compression_level = 6; ///< Compression level for gzip/zstd. - std::function on_error; ///< Optional callback invoked on write errors instead of stderr. + std::function on_error; ///< Optional callback invoked on initialization and write errors instead of stderr. }; /// \struct SessionView @@ -70,12 +70,20 @@ namespace logit { explicit MdbxLogger(const Config& config) : m_config(config) { - normalize_config(); - validate_compression_config(); - open_storage(); - m_session_id = open_session(); - if (m_config.async) { - m_worker = std::thread(&MdbxLogger::worker_loop, this); + try { + normalize_config(); + validate_compression_config(); + open_storage(); + m_session_id = open_session(); + if (m_config.async) { + m_worker = std::thread(&MdbxLogger::worker_loop, this); + } + } catch (const std::exception& e) { + report_init_error(std::string("MdbxLogger initialization error: ") + e.what()); + throw; + } catch (...) { + report_init_error("MdbxLogger initialization error"); + throw; } } @@ -623,12 +631,28 @@ namespace logit { db_config.no_subdir = true; db_config.sync_durable = true; + ensure_storage_parent(db_config); m_connection = mdbxc::Connection::create(db_config); m_sessions.reset(new SessionTable(m_connection, "log_sessions")); m_records.reset(new RecordTable(m_connection, "log_records_by_time")); m_payloads.reset(new PayloadTable(m_connection, "log_payloads")); } + void ensure_storage_parent(const mdbxc::Config& db_config) const { + if (!db_config.read_only && db_config.no_subdir) { + mdbxc::create_directories(db_config.pathname); + } + } + + void report_init_error(const std::string& message) const noexcept { + if (m_config.on_error) { + try { + m_config.on_error(message); + } catch (...) { + } + } + } + uint64_t open_session() { std::lock_guard db_lock(m_db_mutex); auto txn = m_connection->transaction(mdbxc::TransactionMode::WRITABLE); diff --git a/tests/mdbx_logger_test.cpp b/tests/mdbx_logger_test.cpp index c37dd90..67c9bff 100644 --- a/tests/mdbx_logger_test.cpp +++ b/tests/mdbx_logger_test.cpp @@ -9,6 +9,9 @@ #include #include #include +#if __cplusplus >= 201703L +#include +#endif namespace { @@ -23,11 +26,29 @@ std::string make_db_path(const std::string& suffix) { return os.str(); } +std::string make_nested_db_path(const std::string& suffix) { + std::ostringstream os; + os << logit::get_exec_dir() + << "/mdbx_logger_test_" + << suffix + << "_" + << LOGIT_CURRENT_TIMESTAMP_MS() + << "/nested/logs.mdbx"; + return os.str(); +} + void cleanup_db(const std::string& path) { std::remove(path.c_str()); std::remove((path + "-lck").c_str()); } +void cleanup_path_tree(const std::string& path) { + cleanup_db(path); +#if __cplusplus >= 201703L + std::filesystem::remove_all(std::filesystem::u8path(path).parent_path().parent_path()); +#endif +} + logit::LogRecord make_record(logit::LogLevel level, int64_t timestamp_ms, int line) { return logit::LogRecord( level, @@ -223,6 +244,59 @@ void test_on_error_callback() { cleanup_db(path); } +void test_nested_parent_directory_created() { + const std::string path = make_nested_db_path("nested"); + cleanup_path_tree(path); + + { + logit::MdbxLogger::Config config; + config.path = path; + config.async = false; + + logit::MdbxLogger logger(config); + logger.log(make_record(logit::LogLevel::LOG_LVL_INFO, 6100, 51), "nested-ok"); + + auto records = logger.read_range(6100, 6101); + assert(records.size() == 1); + assert(records[0].message == "nested-ok"); + logger.shutdown(); + } + + cleanup_path_tree(path); +} + +void test_init_error_callback_and_rethrow() { + const std::string path = make_nested_db_path("init_error"); + cleanup_path_tree(path); + + std::vector errors; + logit::MdbxLogger::Config config; + config.path = path; + config.async = false; + config.on_error = [&errors](const std::string& msg) { + errors.push_back(msg); + }; + + const std::string blocker = path.substr(0, path.find("/nested/logs.mdbx")); + FILE* blocker_file = std::fopen(blocker.c_str(), "wb"); + assert(blocker_file != nullptr); + std::fclose(blocker_file); + + bool thrown = false; + try { + logit::MdbxLogger logger(config); + } catch (const std::exception&) { + thrown = true; + } + + assert(thrown); + assert(!errors.empty()); + assert(errors[0].find("MdbxLogger initialization error") != std::string::npos); + + std::remove(blocker.c_str()); + cleanup_path_tree(path); +} + void test_read_range_empty_and_limits() { const std::string path = make_db_path("range"); cleanup_db(path); @@ -412,6 +486,8 @@ int main() { #endif test_counters_zero_for_sync_writes(); test_on_error_callback(); + test_nested_parent_directory_created(); + test_init_error_callback_and_rethrow(); test_read_range_empty_and_limits(); test_read_recent(); test_callback_sync(); From 2df2f287fc9720c16e51a3c1f3f5b227d12bc48b Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Mon, 1 Jun 2026 13:49:57 +0300 Subject: [PATCH 2/4] test(mdbx): derive init error test root Use filesystem path decomposition in the init failure regression test so the blocker path follows the same path semantics as the nested MDBX path under test. Constraint: Keep the change limited to test robustness Confidence: high Scope-risk: narrow Co-Authored-By: Claude Opus 4.8 --- tests/mdbx_logger_test.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/mdbx_logger_test.cpp b/tests/mdbx_logger_test.cpp index 67c9bff..be73454 100644 --- a/tests/mdbx_logger_test.cpp +++ b/tests/mdbx_logger_test.cpp @@ -37,6 +37,15 @@ std::string make_nested_db_path(const std::string& suffix) { return os.str(); } +std::string make_nested_db_root(const std::string& path) { +#if __cplusplus >= 202002L + const auto root = std::filesystem::u8path(path).parent_path().parent_path().u8string(); + return std::string(root.begin(), root.end()); +#else + return std::filesystem::u8path(path).parent_path().parent_path().u8string(); +#endif +} + void cleanup_db(const std::string& path) { std::remove(path.c_str()); std::remove((path + "-lck").c_str()); @@ -277,7 +286,7 @@ void test_init_error_callback_and_rethrow() { errors.push_back(msg); }; - const std::string blocker = path.substr(0, path.find("/nested/logs.mdbx")); + const std::string blocker = make_nested_db_root(path); FILE* blocker_file = std::fopen(blocker.c_str(), "wb"); assert(blocker_file != nullptr); std::fclose(blocker_file); From 6dd5f469260786bb0d83d1f474e6361851a8b45a Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Mon, 1 Jun 2026 14:05:54 +0300 Subject: [PATCH 3/4] test(mdbx): guard filesystem path helper Keep the MDBX regression test self-contained for pre-C++17 parses even though the MDBX backend is configured as C++17-only. Constraint: Preserve current CMake LOGIT_WITH_MDBX C++17 requirement Confidence: high Scope-risk: narrow Co-Authored-By: Claude Opus 4.8 --- tests/mdbx_logger_test.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/mdbx_logger_test.cpp b/tests/mdbx_logger_test.cpp index be73454..14be777 100644 --- a/tests/mdbx_logger_test.cpp +++ b/tests/mdbx_logger_test.cpp @@ -38,11 +38,17 @@ std::string make_nested_db_path(const std::string& suffix) { } std::string make_nested_db_root(const std::string& path) { -#if __cplusplus >= 202002L +#if __cplusplus >= 201703L +# if __cplusplus >= 202002L const auto root = std::filesystem::u8path(path).parent_path().parent_path().u8string(); return std::string(root.begin(), root.end()); -#else +# else return std::filesystem::u8path(path).parent_path().parent_path().u8string(); +# endif +#else + const std::string marker = "/nested/logs.mdbx"; + const std::string::size_type pos = path.rfind(marker); + return pos == std::string::npos ? std::string() : path.substr(0, pos); #endif } From ebb35525bf4503f0c724d7988449d4ecf6baebe4 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Mon, 1 Jun 2026 14:34:37 +0300 Subject: [PATCH 4/4] chore(mdbx): update mdbx-containers Pull the latest mdbx-containers fixes into the MDBX logger initialization PR so path handling uses the updated dependency behavior. Constraint: Keep unrelated local guide edits out of this PR update Confidence: high Scope-risk: narrow Co-Authored-By: Claude Opus 4.8 --- external/mdbx-containers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/mdbx-containers b/external/mdbx-containers index fac4001..095e46c 160000 --- a/external/mdbx-containers +++ b/external/mdbx-containers @@ -1 +1 @@ -Subproject commit fac400198b4e121db63df47fd908c4e30d5723ef +Subproject commit 095e46c3bff0305ab14a9718a4c4054701133c69