Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ 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.
- Added optional typed MDBX `on_error` binding support and build-feature-aware
validation for MDBX payload compression.

### Fixed

Expand Down
22 changes: 22 additions & 0 deletions include/logit_cpp/logit/configuration_builtins.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
8 changes: 8 additions & 0 deletions include/logit_cpp/logit/configuration_runtime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ namespace logit {
return it != m_values.end() && static_cast<bool>(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 <typename T>
void set(const std::string& name, const std::shared_ptr<T>& value) {
m_values[name] = Entry(value, std::type_index(typeid(T)));
Expand Down
57 changes: 57 additions & 0 deletions include/logit_cpp/logit/configuration_runtime_builtins.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,56 @@ namespace configuration_runtime_detail {
return valid;
}

#ifdef LOGIT_WITH_MDBX
inline bool read_mdbx_config(const ConfigurationMaterializedLogger& logger,
const ConfigurationBindingSet& bindings,
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<std::size_t>(unsigned_value); else valid = false;
if (read_unsigned(logger, "max_batch_size", unsigned_value, diagnostics)) config.max_batch_size = static_cast<std::size_t>(unsigned_value); else valid = false;
if (read_signed(logger, "flush_interval_ms", signed_value, diagnostics)) config.flush_interval_ms = static_cast<int>(signed_value); else valid = false;
if (read_unsigned(logger, "large_payload_threshold", unsigned_value, diagnostics)) config.large_payload_threshold = static_cast<std::size_t>(unsigned_value); else valid = false;
if (read_unsigned(logger, "payload_preview_size", unsigned_value, diagnostics)) config.payload_preview_size = static_cast<std::size_t>(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<int>(signed_value); else valid = false;
if (bindings.contains_name("on_error")) {
std::shared_ptr<std::function<void(const std::string&)> > callback =
bindings.get<std::function<void(const std::string&)> >("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

#ifdef LOGIT_WITH_OTLP
inline bool read_otlp_format(const ConfigurationMaterializedLogger& logger,
OtlpJsonFormatConfig& config,
Expand Down Expand Up @@ -350,6 +400,13 @@ namespace configuration_runtime_detail {
if (!configuration_runtime_detail::read_memory_config(logger, config, diagnostics)) return std::shared_ptr<ILogger>();
return std::shared_ptr<ILogger>(new MemoryLogger(config));
}
#ifdef LOGIT_WITH_MDBX
if (logger.type == "mdbx") {
MdbxLogger::Config config;
if (!configuration_runtime_detail::read_mdbx_config(logger, bindings, config, diagnostics)) return std::shared_ptr<ILogger>();
return std::shared_ptr<ILogger>(new MdbxLogger(config));
}
#endif
#ifdef LOGIT_WITH_OTLP
if (logger.type == "otlp_http") {
OtlpHttpLogger::Config config;
Expand Down
89 changes: 89 additions & 0 deletions tests/configuration_contract_test.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <cassert>
#include <cstdio>
#include <functional>
#include <map>
#include <memory>
Expand Down Expand Up @@ -93,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<int>(new int(1)));
} else {
std::function<void(const std::string&)> callback = [](const std::string&) {};
bindings.set("on_error",
std::shared_ptr<std::function<void(const std::string&)>>(
new std::function<void(const std::string&)>(callback)));
}
}
return true;
}
};

struct RecordingLoggerFactory : logit::ConfigurationLoggerFactory {
mutable int calls = 0;
int fail_at = -1;
Expand Down Expand Up @@ -513,6 +535,73 @@ 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<std::shared_ptr<logit::ILogger>> loggers;
assert(logit::instantiate_configuration(plan, resolver, factory, loggers, diagnostics));
assert(loggers.size() == 1);
assert(std::dynamic_pointer_cast<logit::MdbxLogger>(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<logit::MdbxLogger>(loggers[0]));
loggers.clear();

optional_resolver.provide_wrong_type = true;
std::shared_ptr<logit::ILogger> 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)
{
logit::ConfigurationDocument document;
Expand Down
Loading