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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@ Read the matching file in `guides/` when the task needs more detail:
- `guides/concurrency.md` - thread-safety contracts, callback dispatch,
mutex ordering, and shutdown invariants.
- `guides/build.md` - submodules, configure/build/test/bench flow.
- `guides/ci.md` - CI environment parity, toolchain compatibility,
sanitizer/timing test guidance, and CI-failure workflow.
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ See the macro examples below or browse the `examples/` folder for focused demons

Recent focused examples include:

- `examples/example_logit_memory_logger.cpp` - in-memory snapshots plus shared `ILogReader` and `ILogSubscriber` macros.
- `examples/example_logit_mdbx_logger.cpp` - persistent MDBX storage plus the same shared read/callback macros and MDBX-specific session/payload APIs.
- `examples/example_logit_otlp_http.cpp` - OTLP/HTTP export with batching, retries, optional compression, and contextual trace/span fields.
- `examples/example_logit_prometheus_payload.cpp` - callback-based Prometheus payload emission with custom registry metrics.
- `examples/example_logit_prometheus_server.cpp` - embedded `/metrics` endpoint with built-in and application metrics.
Expand Down Expand Up @@ -229,6 +231,57 @@ at the `Logger` layer and do not take the per-backend execution mutex, but they
still synchronize on the memory backend's own mutex while copying the current
buffer.

### Common stored-log API

Use `ILogReader` and `ILogSubscriber` when application code should work with
either `MemoryLogger` or `MdbxLogger`. The shared macro helpers return
`LogRecordSnapshot` records and avoid depending on backend-specific storage:

```cpp
#include <logit.hpp>

int main() {
// This can be a MemoryLogger index or an MdbxLogger index.
const int backend_index = 0;

const int64_t now_ms = LOGIT_CURRENT_TIMESTAMP_MS();
const auto recent = LOGIT_READ_RECENT_ASC(backend_index, 100, 0);
const auto window = LOGIT_READ_RANGE(
backend_index,
now_ms - 60LL * 60 * 1000,
now_ms + 1,
0);

std::vector<logit::LogRecordSnapshot> live_updates;
const uint64_t callback_id = LOGIT_ADD_LOG_CALLBACK(
backend_index,
([&live_updates](const logit::LogRecordSnapshot& record) {
live_updates.push_back(record);
}));

LOGIT_INFO_TO(backend_index, "visible through read and callback APIs");
LOGIT_WAIT();
LOGIT_REMOVE_LOG_CALLBACK(backend_index, callback_id);

(void)recent;
(void)window;
(void)live_updates;
}
```

`LOGIT_READ_RANGE`, `LOGIT_READ_RECENT_ASC`, and
`LOGIT_READ_RECENT_DESC` use `ILogReader`. `LOGIT_ADD_LOG_CALLBACK` and
`LOGIT_REMOVE_LOG_CALLBACK` use `ILogSubscriber`; callbacks receive a
`LogRecordSnapshot` after the backend has written the record. Snapshots own
their string fields, so they can be copied or stored by value. Callback
dispatch follows registration order. This is the preferred fallback-friendly
API between `MemoryLogger` and `MdbxLogger`.

`LOGIT_GET_BUFFERED_STRINGS` and `LOGIT_GET_BUFFERED_ENTRIES` are convenience
helpers for the `MemoryLogger` snapshot buffer. They are useful for local
diagnostics panes, but code that should switch between in-memory and MDBX
storage should prefer the shared `LOGIT_READ_*` and callback macros above.

File-based backends also expose persisted-file access through
`LOGIT_LIST_LOG_FILES(index)`, `LOGIT_READ_LOG_FILE(index, path)`, and
`LOGIT_READ_LOG_FILES(index, paths)`. These helpers read only what has already
Expand Down
53 changes: 52 additions & 1 deletion docs/mainpage.dox
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Key characteristics:
- **Flexible formatting and routing.** Customize output patterns, mix console/file/system backends, or supply custom logger implementations.
- **Async by default.** Each backend is served by the task executor with configurable queue sizes and overflow policies, plus helpers such as `LOGIT_WARN_ONCE` or `LOGIT_ERROR_THROTTLE` to keep repeated messages under control.

See the macro examples below or browse the `examples/` directory for focused demonstrations, including queue tuning and crash handling.
See the macro examples below or browse the `examples/` directory for focused demonstrations, including queue tuning and crash handling. `examples/example_logit_memory_logger.cpp` and `examples/example_logit_mdbx_logger.cpp` show the shared read/callback API for in-memory and MDBX-backed stored logs.

\section macro_first_usage_sec Macro-first usage

Expand Down Expand Up @@ -141,6 +141,57 @@ full object footprint of each `BufferedLogEntry`. Snapshot reads avoid the
`Logger` execution mutex, but they still synchronize on the memory backend's
own mutex while copying the current buffer.

\subsection macro_examples_shared_reader Common stored-log API

Use `ILogReader` and `ILogSubscriber` when application code should work with
either `MemoryLogger` or `MdbxLogger`. The shared macro helpers return
`LogRecordSnapshot` records and avoid depending on backend-specific storage:

\code{.cpp}
#include <logit.hpp>

int main() {
// This can be a MemoryLogger index or an MdbxLogger index.
const int backend_index = 0;

const int64_t now_ms = LOGIT_CURRENT_TIMESTAMP_MS();
const auto recent = LOGIT_READ_RECENT_ASC(backend_index, 100, 0);
const auto window = LOGIT_READ_RANGE(
backend_index,
now_ms - 60LL * 60 * 1000,
now_ms + 1,
0);

std::vector<logit::LogRecordSnapshot> live_updates;
const uint64_t callback_id = LOGIT_ADD_LOG_CALLBACK(
backend_index,
([&live_updates](const logit::LogRecordSnapshot& record) {
live_updates.push_back(record);
}));

LOGIT_INFO_TO(backend_index, "visible through read and callback APIs");
LOGIT_WAIT();
LOGIT_REMOVE_LOG_CALLBACK(backend_index, callback_id);

(void)recent;
(void)window;
(void)live_updates;
}
\endcode

`LOGIT_READ_RANGE`, `LOGIT_READ_RECENT_ASC`, and
`LOGIT_READ_RECENT_DESC` use `ILogReader`. `LOGIT_ADD_LOG_CALLBACK` and
`LOGIT_REMOVE_LOG_CALLBACK` use `ILogSubscriber`; callbacks receive a
`LogRecordSnapshot` after the backend has written the record. Snapshots own
their string fields, so they can be copied or stored by value. Callback
dispatch follows registration order. This is the preferred fallback-friendly
API between `MemoryLogger` and `MdbxLogger`.

`LOGIT_GET_BUFFERED_STRINGS` and `LOGIT_GET_BUFFERED_ENTRIES` are convenience
helpers for the `MemoryLogger` snapshot buffer. They are useful for local
diagnostics panes, but code that should switch between in-memory and MDBX
storage should prefer the shared `LOGIT_READ_*` and callback macros above.

File-based backends also expose persisted-file access through
`LOGIT_LIST_LOG_FILES(index)`, `LOGIT_READ_LOG_FILE(index, path)`, and
`LOGIT_READ_LOG_FILES(index, paths)`. These helpers return only what has
Expand Down
47 changes: 28 additions & 19 deletions examples/example_logit_mdbx_logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ int main() {
std::cerr << "[MdbxLogger error callback] " << msg << std::endl;
};

// Add to the registry in single_mode so it does not duplicate console output.
// After this call the logger index depends on what was added before it.
LOGIT_ADD_LOGGER_SINGLE_MODE(
// Add a console logger for live observation (optional).
LOGIT_ADD_CONSOLE_DEFAULT();

// Add to the registry as a regular backend so standard LOGIT_* macros
// write both to MDBX and to the console logger above.
LOGIT_ADD_LOGGER(
logit::MdbxLogger,
(mdbx_config),
logit::SimpleLogFormatter,
Expand All @@ -65,9 +68,6 @@ int main() {
// The MDBX logger is now the last added backend; its index is:
const int mdbx_index = static_cast<int>(logit::Logger::get_instance().logger_count()) - 1;

// Also add a console logger for live observation (optional).
LOGIT_ADD_CONSOLE_DEFAULT();

// ------------------------------------------------------------------
// 2. Log messages via standard macros
// ------------------------------------------------------------------
Expand Down Expand Up @@ -96,7 +96,7 @@ int main() {
LOGIT_WAIT();

// ------------------------------------------------------------------
// 3. Read the MDBX logger directly via typed macro helpers
// 3. Read through the common ILogReader API, then use MDBX-only extras
// ------------------------------------------------------------------
LOGIT_WITH_LOGGER_AS(mdbx_index, logit::MdbxLogger, mdbx) {
// Session metadata
Expand All @@ -109,9 +109,14 @@ int main() {
std::cout << " schema_ver: " << session_opt->schema_version << std::endl;
}

// The LOGIT_READ_* macros work with both MemoryLogger and MdbxLogger.
// All records in a wide time window (last 24 hours).
const int64_t now_ms = LOGIT_CURRENT_TIMESTAMP_MS();
auto all_records = mdbx->read_range(now_ms - 24 * 60 * 60 * 1000, now_ms + 1);
auto all_records = LOGIT_READ_RANGE(
mdbx_index,
now_ms - 24LL * 60 * 60 * 1000,
now_ms + 1,
0);

std::cout << "\n--- All records (" << all_records.size() << ") ---" << std::endl;
for (const auto& r : all_records) {
Expand Down Expand Up @@ -163,9 +168,11 @@ int main() {
auto today_ms = std::chrono::milliseconds(static_cast<int64_t>(midnight_t) * 1000);
auto tomorrow_ms = today_ms + std::chrono::hours(24);

auto day_records = mdbx->read_range(
auto day_records = LOGIT_READ_RANGE(
mdbx_index,
today_ms.count(),
tomorrow_ms.count());
tomorrow_ms.count(),
0);

std::cout << "\n--- Records for today (" << day_records.size() << ") ---" << std::endl;
for (const auto& r : day_records) {
Expand All @@ -176,20 +183,22 @@ int main() {
std::cerr << "Date query example skipped: " << e.what() << std::endl;
}

// read_recent: last 100 records in ascending order
auto recent = mdbx->read_recent(100, 0, logit::LogReadOrder::Ascending);
std::cout << "\n--- read_recent(100) ascending (" << recent.size() << ") ---" << std::endl;
// read_recent: last 100 records in ascending order, through ILogReader.
auto recent = LOGIT_READ_RECENT_ASC(mdbx_index, 100, 0);
std::cout << "\n--- LOGIT_READ_RECENT_ASC(100) (" << recent.size() << ") ---" << std::endl;
for (const auto& r : recent) {
std::cout << " [" << logit::to_string(r.level) << "] "
<< r.timestamp_ms << " " << r.message << std::endl;
}

// Live subscription: snapshot + real-time updates
std::vector<logit::LogRecordView> live_updates;
uint64_t cb_id = mdbx->add_log_callback(
[&live_updates](const logit::LogRecordView& v) {
// Live subscription: the LOGIT_* callback macros also work with both
// MemoryLogger and MdbxLogger.
std::vector<logit::LogRecordSnapshot> live_updates;
const uint64_t cb_id = LOGIT_ADD_LOG_CALLBACK(
mdbx_index,
([&live_updates](const logit::LogRecordSnapshot& v) {
live_updates.push_back(v);
});
}));

LOGIT_INFO("Live event 1 via callback");
LOGIT_INFO("Live event 2 via callback");
Expand All @@ -202,7 +211,7 @@ int main() {
<< r.message << std::endl;
}

if (mdbx->remove_log_callback(cb_id)) {
if (LOGIT_REMOVE_LOG_CALLBACK(mdbx_index, cb_id)) {
std::cout << "Callback removed" << std::endl;
}

Expand Down
79 changes: 71 additions & 8 deletions examples/example_logit_memory_logger.cpp
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
/// \file example_logit_memory_logger.cpp
/// \brief Demonstrates in-memory snapshots, range reads, and live subscriptions.

// #define LOGIT_BASE_PATH "E:\\_repoz\\log-it-cpp" <- set via CMake

#include <cstdint>
#include <iostream>
#include <mutex>
#include <vector>
#include <logit.hpp>

int main() {
std::cout << "Starting in-memory snapshot logger example..." << std::endl;

LOGIT_ADD_CONSOLE_DEFAULT();
LOGIT_ADD_MEMORY_LOGGER_SINGLE_MODE(100, 64 * 1024, 60 * 60 * 1000);
const int memory_index = static_cast<int>(logit::Logger::get_instance().logger_count()) - 1;

LOGIT_INFO("This goes to the regular backends");
LOGIT_INFO_TO(1, "Remote control can fetch this later");
LOGIT_WARN_TO(1, "Latest warning for the control plane");
LOGIT_SECTION_TO(1, "Network Settings");
LOGIT_RAW_TO(1, "Protocol: https");
LOGIT_RAW_TO(1, "Transport Mode: standard");
LOGIT_RAW_TO(1, "DNS Server: 1.1.1.1");
LOGIT_INFO_TO(memory_index, "Remote control can fetch this later");
LOGIT_WARN_TO(memory_index, "Latest warning for the control plane");
LOGIT_SECTION_TO(memory_index, "Network Settings");
LOGIT_RAW_TO(memory_index, "Protocol: https");
LOGIT_RAW_TO(memory_index, "Transport Mode: standard");
LOGIT_RAW_TO(memory_index, "DNS Server: 1.1.1.1");
LOGIT_WAIT();

const auto recent_lines = LOGIT_GET_BUFFERED_STRINGS(1);
const auto recent_entries = LOGIT_GET_BUFFERED_ENTRIES(1);
const auto recent_lines = LOGIT_GET_BUFFERED_STRINGS(memory_index);
const auto recent_entries = LOGIT_GET_BUFFERED_ENTRIES(memory_index);

std::cout << "Buffered strings:" << std::endl;
for (const auto& line : recent_lines) {
Expand All @@ -31,5 +39,60 @@ int main() {
<< entry.timestamp_ms << " " << entry.message << std::endl;
}

const auto recent_records = LOGIT_READ_RECENT_ASC(memory_index, 3, 0);
std::cout << "Recent records via ILogReader:" << std::endl;
for (const auto& record : recent_records) {
std::cout << " [" << logit::to_string(record.level) << "] "
<< record.timestamp_ms << " " << record.message << std::endl;
}

const int64_t now_ms = LOGIT_CURRENT_TIMESTAMP_MS();
const auto range_records = LOGIT_READ_RANGE(
memory_index,
now_ms - 60LL * 60 * 1000,
now_ms + 1,
0);
std::cout << "Range records for the last hour: "
<< range_records.size() << std::endl;

std::vector<logit::LogRecordSnapshot> live_updates;
std::mutex live_mutex;
const uint64_t callback_id = LOGIT_ADD_LOG_CALLBACK(
memory_index,
([&live_updates, &live_mutex](const logit::LogRecordSnapshot& view) {
std::lock_guard<std::mutex> lock(live_mutex);
live_updates.push_back(view);
}));

LOGIT_INFO_TO(memory_index, "Live callback event 1");
LOGIT_ERROR_TO(memory_index, "Live callback event 2");
LOGIT_WAIT();

std::size_t live_count = 0;
{
std::lock_guard<std::mutex> lock(live_mutex);
live_count = live_updates.size();
std::cout << "Live updates received:" << std::endl;
for (const auto& record : live_updates) {
std::cout << " [" << logit::to_string(record.level) << "] "
<< record.message << std::endl;
}
}

if (LOGIT_REMOVE_LOG_CALLBACK(memory_index, callback_id)) {
std::cout << "Callback removed" << std::endl;
}

LOGIT_WARN_TO(memory_index, "Stored after callback removal");
LOGIT_WAIT();

{
std::lock_guard<std::mutex> lock(live_mutex);
std::cout << "Live updates after unsubscribe: "
<< live_updates.size() << " (was " << live_count << ")"
<< std::endl;
}

LOGIT_SHUTDOWN();
return 0;
}
1 change: 1 addition & 0 deletions guides/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ These files are shared AI-agent instruction files for `log-it-cpp`.
| `header-impl-RU.md` | Russian localization of the header ownership playbook. |
| `logging-macros.md` | Macro-first logging guidance for agents and maintainers. |
| `build.md` | Build, test, example, and benchmark flows. |
| `ci.md` | CI environment parity, toolchain compatibility, sanitizer/timing test guidance, and CI-failure workflow. |
| `commits.md` | Commit message conventions and grouping rules. |
| `git-workflow.md` | Branch policy, naming conventions, and required steps before starting work. |

Expand Down
Loading
Loading