From e281eb9f8cf993669877f5ab20c143f79bfe2c59 Mon Sep 17 00:00:00 2001 From: LimiNode <20008174+LimiNode@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:08:44 +0300 Subject: [PATCH 1/2] feat(config): add MDBX runtime factory support Translate materialized MDBX backend fields into MdbxLogger::Config when the optional backend is enabled. Keep the non-serializable error callback unbound and cover the runtime path in the configuration contract test. --- CHANGELOG.md | 1 + .../logit/configuration_runtime_builtins.hpp | 44 +++++++++++++++++++ tests/configuration_contract_test.cpp | 30 +++++++++++++ 3 files changed, 75 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc0b872..8cacd09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file. and parser frontends and hot reload remain outside this layer. - Added a built-in runtime factory that converts materialized file, memory, OTLP, and Prometheus plans into logger instances using typed bindings. +- Added MDBX runtime factory support when the optional MDBX backend is enabled. ### Fixed diff --git a/include/logit_cpp/logit/configuration_runtime_builtins.hpp b/include/logit_cpp/logit/configuration_runtime_builtins.hpp index ed9c3bf..b67249b 100644 --- a/include/logit_cpp/logit/configuration_runtime_builtins.hpp +++ b/include/logit_cpp/logit/configuration_runtime_builtins.hpp @@ -196,6 +196,43 @@ namespace configuration_runtime_detail { return valid; } +#ifdef LOGIT_WITH_MDBX + inline bool read_mdbx_config(const ConfigurationMaterializedLogger& logger, + MdbxLogger::Config& config, + ConfigurationDiagnostics& diagnostics) { + bool valid = true; + std::uint64_t unsigned_value = 0; + std::int64_t signed_value = 0; + std::string compression; + valid = read_string(logger, "path", config.path, diagnostics) && valid; + valid = read_string(logger, "app_name", config.app_name, diagnostics) && valid; + if (read_unsigned(logger, "session_id", unsigned_value, diagnostics)) config.session_id = unsigned_value; else valid = false; + valid = read_bool(logger, "async", config.async, diagnostics) && valid; + valid = read_bool(logger, "drop_on_overflow", config.drop_on_overflow, diagnostics) && valid; + if (read_unsigned(logger, "max_queue_size", unsigned_value, diagnostics)) config.max_queue_size = static_cast(unsigned_value); else valid = false; + if (read_unsigned(logger, "max_batch_size", unsigned_value, diagnostics)) config.max_batch_size = static_cast(unsigned_value); else valid = false; + if (read_signed(logger, "flush_interval_ms", signed_value, diagnostics)) config.flush_interval_ms = static_cast(signed_value); else valid = false; + if (read_unsigned(logger, "large_payload_threshold", unsigned_value, diagnostics)) config.large_payload_threshold = static_cast(unsigned_value); else valid = false; + if (read_unsigned(logger, "payload_preview_size", unsigned_value, diagnostics)) config.payload_preview_size = static_cast(unsigned_value); else valid = false; + valid = read_bool(logger, "store_large_payloads_separately", config.store_large_payloads_separately, diagnostics) && valid; + if (read_string(logger, "payload_compression", compression, diagnostics)) { + if (compression == "none") config.payload_compression = MdbxPayloadCompression::None; + else if (compression == "gzip") config.payload_compression = MdbxPayloadCompression::Gzip; + else if (compression == "zstd") config.payload_compression = MdbxPayloadCompression::Zstd; + else { + diagnostics.add(ConfigurationErrorCode::InvalidValue, + "backend.payload_compression", + "Unknown MDBX payload compression"); + valid = false; + } + } else { + valid = false; + } + if (read_signed(logger, "payload_compression_level", signed_value, diagnostics)) config.payload_compression_level = static_cast(signed_value); else valid = false; + return valid; + } +#endif + #ifdef LOGIT_WITH_OTLP inline bool read_otlp_format(const ConfigurationMaterializedLogger& logger, OtlpJsonFormatConfig& config, @@ -350,6 +387,13 @@ namespace configuration_runtime_detail { if (!configuration_runtime_detail::read_memory_config(logger, config, diagnostics)) return std::shared_ptr(); return std::shared_ptr(new MemoryLogger(config)); } +#ifdef LOGIT_WITH_MDBX + if (logger.type == "mdbx") { + MdbxLogger::Config config; + if (!configuration_runtime_detail::read_mdbx_config(logger, config, diagnostics)) return std::shared_ptr(); + return std::shared_ptr(new MdbxLogger(config)); + } +#endif #ifdef LOGIT_WITH_OTLP if (logger.type == "otlp_http") { OtlpHttpLogger::Config config; diff --git a/tests/configuration_contract_test.cpp b/tests/configuration_contract_test.cpp index 5bf2c7f..d7ed28f 100644 --- a/tests/configuration_contract_test.cpp +++ b/tests/configuration_contract_test.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -513,6 +514,35 @@ void test_builtin_runtime_factory_consumes_materialized_plan() { } void test_builtin_runtime_factory_supports_payload_backends() { +#if defined(LOGIT_WITH_MDBX) + { + const std::string path = "configuration_runtime_mdbx_test.mdbx"; + std::remove(path.c_str()); + std::remove((path + "-lck").c_str()); + logit::ConfigurationDocument document; + document.schema_version = 1; + logit::ConfigurationLoggerSpec logger; + logger.id = "mdbx-runtime"; + logger.type = "mdbx"; + logger.backend["path"] = logit::ConfigurationValue::make_string(path); + logger.backend["async"] = logit::ConfigurationValue::make_boolean(false); + document.loggers.push_back(logger); + + logit::BuiltinConfigurationMaterializer materializer; + logit::ConfigurationPlan plan; + logit::ConfigurationDiagnostics diagnostics; + assert(logit::materialize_configuration(document, materializer, plan, diagnostics)); + RecordingBindingResolver resolver; + logit::BuiltinConfigurationLoggerFactory factory; + std::vector> loggers; + assert(logit::instantiate_configuration(plan, resolver, factory, loggers, diagnostics)); + assert(loggers.size() == 1); + assert(std::dynamic_pointer_cast(loggers[0])); + loggers.clear(); + std::remove(path.c_str()); + std::remove((path + "-lck").c_str()); + } +#endif #if defined(LOGIT_WITH_OTLP) { logit::ConfigurationDocument document; From 95e05850891b311fcffe4eee75be523254d957bd Mon Sep 17 00:00:00 2001 From: LimiNode <20008174+LimiNode@users.noreply.github.com> Date: Thu, 29 Oct 2026 04:44:42 +0300 Subject: [PATCH 2/2] fix(config): close MDBX runtime contract gaps Support the optional typed MDBX on_error binding, reject incompatible bindings fail-closed, and validate gzip/zstd payload compression against compiled features before instantiation. Remove the obsolete Emscripten Node flag used by the floating emsdk lane. --- .github/workflows/ci.yml | 6 +- CHANGELOG.md | 2 + .../logit/configuration_builtins.hpp | 22 +++++++ .../logit_cpp/logit/configuration_runtime.hpp | 8 +++ .../logit/configuration_runtime_builtins.hpp | 15 ++++- tests/configuration_contract_test.cpp | 59 +++++++++++++++++++ 6 files changed, 108 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e90d45..5c932f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -580,9 +580,9 @@ jobs: run: cmake --build build-ems --target ems_console ems_async_flush ems_single_thread_executor -j - name: Run smoke tests run: | - node --no-experimental-fetch build-ems/tests/ems_console.js - node --no-experimental-fetch build-ems/tests/ems_async_flush.js - node --no-experimental-fetch build-ems/tests/ems_single_thread_executor.js + node build-ems/tests/ems_console.js + node build-ems/tests/ems_async_flush.js + node build-ems/tests/ems_single_thread_executor.js - name: Upload logs if: failure() uses: actions/upload-artifact@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cacd09..860d5ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ All notable changes to this project will be documented in this file. - Added a built-in runtime factory that converts materialized file, memory, OTLP, and Prometheus plans into logger instances using typed bindings. - Added MDBX runtime factory support when the optional MDBX backend is enabled. +- Added optional typed MDBX `on_error` binding support and build-feature-aware + validation for MDBX payload compression. ### Fixed diff --git a/include/logit_cpp/logit/configuration_builtins.hpp b/include/logit_cpp/logit/configuration_builtins.hpp index 77fa2cc..80af160 100644 --- a/include/logit_cpp/logit/configuration_builtins.hpp +++ b/include/logit_cpp/logit/configuration_builtins.hpp @@ -282,6 +282,28 @@ namespace logit { valid = validate_type(logger, "payload_compression_level", ConfigurationValue::Type::SignedInteger, diagnostics) && valid; valid = validate_enum(logger, "payload_compression", "none", "gzip", "zstd", diagnostics) && valid; + ConfigurationFieldMap::const_iterator compression = logger.backend.find("payload_compression"); + if (compression != logger.backend.end()) { + std::string value; + if (compression->second.get_string(value)) { +#if !defined(LOGIT_HAS_ZLIB) + if (value == "gzip") { + diagnostics.add(ConfigurationErrorCode::UnavailableBackend, + "backend.payload_compression", + "MDBX gzip payload compression is unavailable in this build"); + valid = false; + } +#endif +#if !defined(LOGIT_HAS_ZSTD) + if (value == "zstd") { + diagnostics.add(ConfigurationErrorCode::UnavailableBackend, + "backend.payload_compression", + "MDBX zstd payload compression is unavailable in this build"); + valid = false; + } +#endif + } + } return valid; } diff --git a/include/logit_cpp/logit/configuration_runtime.hpp b/include/logit_cpp/logit/configuration_runtime.hpp index ea05cbc..1dc5e14 100644 --- a/include/logit_cpp/logit/configuration_runtime.hpp +++ b/include/logit_cpp/logit/configuration_runtime.hpp @@ -31,6 +31,14 @@ namespace logit { return it != m_values.end() && static_cast(it->second.value); } + /// \brief Returns whether a binding name was supplied, regardless of its type. + /// + /// This is useful for optional bindings: a factory can distinguish an + /// absent callback from a present callback with an incompatible type. + bool contains_name(const std::string& name) const { + return m_values.find(name) != m_values.end(); + } + template void set(const std::string& name, const std::shared_ptr& value) { m_values[name] = Entry(value, std::type_index(typeid(T))); diff --git a/include/logit_cpp/logit/configuration_runtime_builtins.hpp b/include/logit_cpp/logit/configuration_runtime_builtins.hpp index b67249b..a8589fd 100644 --- a/include/logit_cpp/logit/configuration_runtime_builtins.hpp +++ b/include/logit_cpp/logit/configuration_runtime_builtins.hpp @@ -198,6 +198,7 @@ namespace configuration_runtime_detail { #ifdef LOGIT_WITH_MDBX inline bool read_mdbx_config(const ConfigurationMaterializedLogger& logger, + const ConfigurationBindingSet& bindings, MdbxLogger::Config& config, ConfigurationDiagnostics& diagnostics) { bool valid = true; @@ -229,6 +230,18 @@ namespace configuration_runtime_detail { valid = false; } if (read_signed(logger, "payload_compression_level", signed_value, diagnostics)) config.payload_compression_level = static_cast(signed_value); else valid = false; + if (bindings.contains_name("on_error")) { + std::shared_ptr > callback = + bindings.get >("on_error"); + if (!callback) { + diagnostics.add(ConfigurationErrorCode::InstantiationFailed, + "binding.on_error", + "MDBX on_error binding has an incompatible type"); + valid = false; + } else { + config.on_error = *callback; + } + } return valid; } #endif @@ -390,7 +403,7 @@ namespace configuration_runtime_detail { #ifdef LOGIT_WITH_MDBX if (logger.type == "mdbx") { MdbxLogger::Config config; - if (!configuration_runtime_detail::read_mdbx_config(logger, config, diagnostics)) return std::shared_ptr(); + if (!configuration_runtime_detail::read_mdbx_config(logger, bindings, config, diagnostics)) return std::shared_ptr(); return std::shared_ptr(new MdbxLogger(config)); } #endif diff --git a/tests/configuration_contract_test.cpp b/tests/configuration_contract_test.cpp index d7ed28f..51382ad 100644 --- a/tests/configuration_contract_test.cpp +++ b/tests/configuration_contract_test.cpp @@ -94,6 +94,27 @@ struct PayloadBindingResolver : logit::ConfigurationBindingResolver { } }; +struct OptionalErrorBindingResolver : logit::ConfigurationBindingResolver { + bool provide_error = false; + bool provide_wrong_type = false; + + bool resolve(const logit::ConfigurationMaterializedLogger&, + logit::ConfigurationBindingSet& bindings, + logit::ConfigurationDiagnostics&) const override { + if (provide_error) { + if (provide_wrong_type) { + bindings.set("on_error", std::shared_ptr(new int(1))); + } else { + std::function callback = [](const std::string&) {}; + bindings.set("on_error", + std::shared_ptr>( + new std::function(callback))); + } + } + return true; + } +}; + struct RecordingLoggerFactory : logit::ConfigurationLoggerFactory { mutable int calls = 0; int fail_at = -1; @@ -539,9 +560,47 @@ void test_builtin_runtime_factory_supports_payload_backends() { assert(loggers.size() == 1); assert(std::dynamic_pointer_cast(loggers[0])); loggers.clear(); + + OptionalErrorBindingResolver optional_resolver; + optional_resolver.provide_error = true; + assert(logit::instantiate_configuration(plan, optional_resolver, factory, loggers, diagnostics)); + assert(loggers.size() == 1); + assert(std::dynamic_pointer_cast(loggers[0])); + loggers.clear(); + + optional_resolver.provide_wrong_type = true; + std::shared_ptr sentinel(new logit::MemoryLogger()); + loggers.push_back(sentinel); + assert(!logit::instantiate_configuration(plan, optional_resolver, factory, loggers, diagnostics)); + assert(diagnostics.has_code(logit::ConfigurationErrorCode::InstantiationFailed)); + assert(loggers.size() == 1); + assert(loggers[0] == sentinel); + std::remove(path.c_str()); std::remove((path + "-lck").c_str()); } + +#if !defined(LOGIT_HAS_ZLIB) || !defined(LOGIT_HAS_ZSTD) + { + logit::ConfigurationDocument document; + document.schema_version = 1; + logit::ConfigurationLoggerSpec logger; + logger.id = "mdbx-unavailable-compression"; + logger.type = "mdbx"; +#if !defined(LOGIT_HAS_ZLIB) + logger.backend["payload_compression"] = logit::ConfigurationValue::make_string("gzip"); +#else + logger.backend["payload_compression"] = logit::ConfigurationValue::make_string("zstd"); +#endif + document.loggers.push_back(logger); + logit::BuiltinConfigurationMaterializer materializer; + logit::ConfigurationPlan plan; + logit::ConfigurationDiagnostics diagnostics; + assert(!logit::materialize_configuration(document, materializer, plan, diagnostics)); + assert(diagnostics.has_code(logit::ConfigurationErrorCode::UnavailableBackend)); + assert(diagnostics.items()[0].path == "loggers[0].backend.payload_compression"); + } +#endif #endif #if defined(LOGIT_WITH_OTLP) {