From 7240c54640a24bfb440698de1d2c588d12af39e7 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Sat, 10 Oct 2026 00:16:33 +0300 Subject: [PATCH 1/3] docs(config): propose versioned loading contract Define a parser-neutral configuration model with schema versioning, strict validation, C++-owned defaults, backend capability checks, and programmatic callback binding. Keep hot reload outside the first API and link the proposal from the roadmap. --- .../0009-configuration-loading-contract.md | 141 ++++++++++++++++++ docs/adr/README.md | 4 + docs/future-plans.md | 7 +- 3 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 docs/adr/0009-configuration-loading-contract.md diff --git a/docs/adr/0009-configuration-loading-contract.md b/docs/adr/0009-configuration-loading-contract.md new file mode 100644 index 0000000..25bcc89 --- /dev/null +++ b/docs/adr/0009-configuration-loading-contract.md @@ -0,0 +1,141 @@ +# ADR 0009: Versioned configuration loading contract + +- Status: Proposed +- Date: 2026-10-10 + +## Context + +LogIt++ exposes several backend-specific `Config` structures. Applications can +construct those structures directly today, but there is no stable file or +properties contract for selecting multiple loggers and applying their options. +Adding a parser before defining that contract would create a second set of +defaults and validation rules, and could make JSON and properties inputs behave +differently. + +The first configuration API must describe static construction only. Watching a +file and hot reload introduce lifecycle, replacement, and failure semantics +that should be designed after the initial load model is stable. + +## Decision + +Configuration loading v1 will use a parser-neutral semantic model. JSON and +properties frontends, whether built in or optional, must map into that same +model before validation and logger construction. No frontend is allowed to +instantiate a backend directly with frontend-specific defaults. + +The model has these top-level requirements: + +- `schema_version` is required and selects a versioned contract. A loader must + reject unsupported versions rather than silently applying a different schema. +- `loggers` is a required array. Each entry has a stable `id`, a `type`, and + backend options. Logger ids must be unique within one document. +- Common options are represented once: `level`, `async`, + `use_dedicated_executor`, `queue_capacity`, `queue_policy`, and `formatter`. + A backend may reject an option that it cannot support; it must not silently + ignore it. +- Backend-specific sections contain only options owned by the selected backend. + The initial supported mapping will cover the existing public backend + `Config` structures, including file/unique-file, console, memory, MDBX, + OTLP, Prometheus, syslog, event log, and Windows debug configurations where + the target platform and optional feature are available. +- When a field is missing, the loader uses the default of the corresponding + C++ `Config`. Defaults are therefore owned by the C++ configuration types + and must not be duplicated as an independent parser default table. +- Unknown logger types, unknown fields, duplicate ids, malformed values, + unsupported combinations, and unavailable optional backends are validation + errors. They must produce a diagnostic containing the document path and a + stable error category; v1 does not silently ignore unknown input. +- `level`, `queue_policy`, compression settings, and formatter settings use + named values defined by the public API. Numeric or frontend-specific aliases + are not part of the v1 contract unless explicitly added to the schema. +- Formatter configuration is data-only in v1 (for example pattern and JSON + mode). Callback/function members such as `on_payload`, `on_error`, and + collection callbacks are not serializable. They require programmatic binding + after loading and are never represented as executable data in JSON or + properties. + +An illustrative JSON representation of the semantic model is: + +```json +{ + "schema_version": 1, + "loggers": [ + { + "id": "application-file", + "type": "file", + "level": "info", + "async": true, + "queue_policy": "block", + "formatter": { "pattern": "%v", "json": false }, + "backend": { + "directory": "logs", + "compress_level": 1 + } + } + ] +} +``` + +The exact frontend syntax may differ for properties, but it must produce the +same semantic values, defaults, validation results, and diagnostics as this +model. The example does not add fields to any backend `Config` by itself. + +The load pipeline is: + +```text +source (JSON/properties) + -> parser-neutral semantic model + -> schema and value validation + -> backend Config construction using C++ defaults + -> logger instantiation +``` + +Loading is an explicit operation that either returns a complete validated +configuration/instance set or returns diagnostics without partially applying +the document. v1 does not define file watching, hot reload, transactional +replacement of live loggers, or environment-variable interpolation. + +## Consequences + +The contract gives JSON and properties the same behavior and keeps existing C++ +`Config` defaults authoritative. Strict validation makes deployment mistakes +visible and gives callers actionable paths such as `loggers[1].queue_policy`. +The model can be tested without making a particular JSON library a core +dependency; a JSON frontend may remain optional or thin. + +The initial implementation must maintain an explicit mapping for each backend, +including platform and feature availability checks. Adding a new backend or +serializable option requires updating the schema, mapping, validation, and +documentation together. Callbacks and other executable behavior remain an +intentional programmatic boundary. + +## Alternatives considered + +### Bind JSON directly to each `Config` + +Rejected for v1. It duplicates parser behavior across backends, makes defaults +drift from C++, and leaves no shared semantics for a properties frontend. + +### Permissive unknown-field handling + +Rejected for v1. Ignoring a misspelled backend or option can produce a logger +that appears configured while silently using an unintended default. Forward +compatibility is handled by `schema_version` and future schema revisions. + +### Make `nlohmann::json` a required core dependency + +Rejected at the contract stage. The semantic model must stay independent of a +parser library so the header-only core and optional-feature builds do not gain +an unnecessary mandatory dependency. + +### Include hot reload in the first API + +Rejected. Reload requires decisions about ownership, in-flight records, +failure rollback, callback rebinding, and concurrent readers. Those semantics +should follow a stable static-load contract rather than constrain it in advance. + +## References + +- [Roadmap: Configuration loading](../future-plans.md) +- [ADR 0002: Shared and dedicated asynchronous executors](0002-async-executor-model.md) +- [ADR 0008: Binary log record format research prototype](0008-binary-log-record-format-research.md) diff --git a/docs/adr/README.md b/docs/adr/README.md index 0882ba3..fc0b246 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -31,3 +31,7 @@ explains **why the current boundary or trade-off exists**. - [0006 — Explicit capability for concurrent dispatch](0006-concurrent-dispatch-capability.md) - [0007 — Transparent compressed-file reads](0007-transparent-compressed-file-reads.md) - [0008 — Binary log record format research prototype](0008-binary-log-record-format-research.md) + +## Proposed decisions + +- [0009 — Versioned configuration loading contract](0009-configuration-loading-contract.md) diff --git a/docs/future-plans.md b/docs/future-plans.md index 65408b5..78d3673 100644 --- a/docs/future-plans.md +++ b/docs/future-plans.md @@ -50,8 +50,9 @@ Legend: feature is enabled; disabled-feature and malformed-input paths remain failures and are covered by tests. - [ ] **Configuration loading** — design a versioned JSON/properties mapping to - the existing backend configuration. Treat file watching/hot reload as a - follow-up, not part of the first configuration API. + the existing backend configuration. The proposed contract is recorded in + [ADR 0009](adr/0009-configuration-loading-contract.md). Treat file + watching/hot reload as a follow-up, not part of the first configuration API. - [ ] **Extended filtering** — evaluate source/file, message, tag/MDC, and range filters; define their cost and ordering before adding public API. - [x] **Benchmark follow-up** — formatted and passthrough public-macro scenarios, @@ -80,4 +81,4 @@ decisions or compatibility changes; keep this file focused on status and next steps. Record measurements with their environment instead of turning one local run into a universal performance claim. -Last reviewed: 2026-09-19. +Last reviewed: 2026-10-10. From 7f289daed5334e0a65106abf5d8bb3d2bc9a9ebb Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Sat, 10 Oct 2026 16:38:22 +0300 Subject: [PATCH 2/3] docs(config): clarify loading contract boundaries Separate backend, formatter, and strategy defaults; keep backend-native queue semantics lossless; and require callback/resource binding before instantiation. Preserve strict validation and the no-hot-reload scope. --- .../0009-configuration-loading-contract.md | 69 ++++++++++++------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/docs/adr/0009-configuration-loading-contract.md b/docs/adr/0009-configuration-loading-contract.md index 25bcc89..eb6a64d 100644 --- a/docs/adr/0009-configuration-loading-contract.md +++ b/docs/adr/0009-configuration-loading-contract.md @@ -29,18 +29,27 @@ The model has these top-level requirements: reject unsupported versions rather than silently applying a different schema. - `loggers` is a required array. Each entry has a stable `id`, a `type`, and backend options. Logger ids must be unique within one document. -- Common options are represented once: `level`, `async`, - `use_dedicated_executor`, `queue_capacity`, `queue_policy`, and `formatter`. - A backend may reject an option that it cannot support; it must not silently - ignore it. +- The semantic model distinguishes three default owners: + - backend-native fields use defaults from the selected backend's public C++ + `Config` (for example `FileLogger::Config` or `MdbxLogger::Config`); + - formatter fields use the selected formatter's public `Config` (the v1 + default formatter is `SimpleLogFormatter::Config`); + - logger-strategy fields use explicit loader/API defaults matching current + runtime behavior. In particular, an omitted `level` means + `LogLevel::LOG_LVL_TRACE`. +- A semantic option is exposed at a shared level only when the selected backend + has a lossless mapping for it. Otherwise it remains backend-specific, or its + presence is a validation error; values are never silently ignored. - Backend-specific sections contain only options owned by the selected backend. The initial supported mapping will cover the existing public backend `Config` structures, including file/unique-file, console, memory, MDBX, OTLP, Prometheus, syslog, event log, and Windows debug configurations where the target platform and optional feature are available. -- When a field is missing, the loader uses the default of the corresponding - C++ `Config`. Defaults are therefore owned by the C++ configuration types - and must not be duplicated as an independent parser default table. +- Executor-style `queue_capacity` and `queue_policy` are therefore available + only for backends whose public contract uses that executor configuration. + Backend-native queues retain their own fields and semantics, such as MDBX or + OTLP `max_queue_size` and `drop_on_overflow`; v1 does not pretend these are + interchangeable with executor queue settings. - Unknown logger types, unknown fields, duplicate ids, malformed values, unsupported combinations, and unavailable optional backends are validation errors. They must produce a diagnostic containing the document path and a @@ -50,9 +59,9 @@ The model has these top-level requirements: are not part of the v1 contract unless explicitly added to the schema. - Formatter configuration is data-only in v1 (for example pattern and JSON mode). Callback/function members such as `on_payload`, `on_error`, and - collection callbacks are not serializable. They require programmatic binding - after loading and are never represented as executable data in JSON or - properties. + collection callbacks are not serializable. They require a programmatic + binding/resolver before logger construction and are never represented as + executable data in JSON or properties. An illustrative JSON representation of the semantic model is: @@ -63,12 +72,14 @@ An illustrative JSON representation of the semantic model is: { "id": "application-file", "type": "file", - "level": "info", - "async": true, - "queue_policy": "block", - "formatter": { "pattern": "%v", "json": false }, + "strategy": { + "level": "info", + "formatter": { "pattern": "%v", "json": false } + }, "backend": { "directory": "logs", + "async": true, + "queue_policy": "block", "compress_level": 1 } } @@ -86,28 +97,38 @@ The load pipeline is: source (JSON/properties) -> parser-neutral semantic model -> schema and value validation - -> backend Config construction using C++ defaults + -> backend and formatter Config materialization using their defaults + -> programmatic binding/resolution of callbacks and external resources -> logger instantiation ``` -Loading is an explicit operation that either returns a complete validated -configuration/instance set or returns diagnostics without partially applying +The conceptual API has two phases: loading/validation returns a complete plan; +instantiation consumes that plan together with programmatic bindings and +resources. A convenience API may compose the phases, but it must resolve all +required bindings before constructing a logger. A callback-dependent backend +without its required binding is a `missing_programmatic_binding` diagnostic at +the logger path, not a successfully instantiated inert backend. Backends that +explicitly document an inert/no-output mode may opt into that mode instead. + +The final operation is atomic with respect to the document: it returns a +complete validated plan/instance set or diagnostics without partially applying the document. v1 does not define file watching, hot reload, transactional replacement of live loggers, or environment-variable interpolation. ## Consequences -The contract gives JSON and properties the same behavior and keeps existing C++ -`Config` defaults authoritative. Strict validation makes deployment mistakes -visible and gives callers actionable paths such as `loggers[1].queue_policy`. +The contract gives JSON and properties the same behavior while keeping backend, +formatter, and strategy defaults explicit. Strict validation makes deployment +mistakes visible and gives callers actionable paths such as +`loggers[1].backend.queue_policy`. The model can be tested without making a particular JSON library a core dependency; a JSON frontend may remain optional or thin. The initial implementation must maintain an explicit mapping for each backend, -including platform and feature availability checks. Adding a new backend or -serializable option requires updating the schema, mapping, validation, and -documentation together. Callbacks and other executable behavior remain an -intentional programmatic boundary. +including platform and feature availability checks and any required callback +bindings. Adding a new backend or serializable option requires updating the +schema, mapping, validation, and documentation together. Callbacks and other +executable behavior remain an intentional programmatic boundary. ## Alternatives considered From 4ad17357effbe40a1c65680ed43caf3f35e6822f Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Sun, 11 Oct 2026 02:43:24 +0300 Subject: [PATCH 3/3] docs(config): clarify dedicated queue validation Require executor queue fields to use an effective dedicated executor and reject unsupported combinations. Keep shared TaskExecutor configuration outside logger documents and align the illustrative file configuration. --- docs/adr/0009-configuration-loading-contract.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/adr/0009-configuration-loading-contract.md b/docs/adr/0009-configuration-loading-contract.md index eb6a64d..ec333a2 100644 --- a/docs/adr/0009-configuration-loading-contract.md +++ b/docs/adr/0009-configuration-loading-contract.md @@ -50,6 +50,12 @@ The model has these top-level requirements: Backend-native queues retain their own fields and semantics, such as MDBX or OTLP `max_queue_size` and `drop_on_overflow`; v1 does not pretend these are interchangeable with executor queue settings. +- For an executor-style backend, explicitly supplied `queue_capacity` or + `queue_policy` requires an effective dedicated-executor configuration. If + `use_dedicated_executor` is false or absent, those fields produce an + `unsupported_combination` validation diagnostic rather than being silently + ignored. A logger document does not implicitly configure the shared global + `TaskExecutor`; a future global-executor scope would be a separate contract. - Unknown logger types, unknown fields, duplicate ids, malformed values, unsupported combinations, and unavailable optional backends are validation errors. They must produce a diagnostic containing the document path and a @@ -79,6 +85,8 @@ An illustrative JSON representation of the semantic model is: "backend": { "directory": "logs", "async": true, + "use_dedicated_executor": true, + "queue_capacity": 8192, "queue_policy": "block", "compress_level": 1 }