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 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..14be777 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,44 @@ 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(); +} + +std::string make_nested_db_root(const std::string& path) { +#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 + 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 +} + 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 +259,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 = make_nested_db_root(path); + 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 +501,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();