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: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@
[submodule "external/zlib"]
path = external/zlib
url = https://github.com/madler/zlib
[submodule "external/mdbx-containers"]
path = external/mdbx-containers
url = https://github.com/NewYaroslav/mdbx-containers.git
[submodule "external/kurlyk"]
path = external/kurlyk
url = https://github.com/NewYaroslav/kurlyk.git
70 changes: 58 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ option(LOGIT_WITH_CONTEXT "Enable MDC/NDC diagnostic context support" OFF)
option(LOGIT_WITH_OTLP "Enable OTLP/HTTP log export via optional kurlyk dependency" OFF)
option(LOGIT_WITH_PROMETHEUS "Enable Prometheus text payload support" OFF)
option(LOGIT_WITH_PROMETHEUS_SERVER "Enable Prometheus HTTP server backend" OFF)
option(LOGIT_WITH_MDBX "Enable MDBX structured log storage backend" OFF)
option(LOGIT_USE_SUBMODULES "Allow bundled optional dependency fallback" OFF)
option(LOGIT_WITH_SYSLOG "Enable POSIX syslog backend" ON)
option(LOGIT_WITH_WIN_EVENT_LOG "Enable Windows Event Log backend" ON)
Expand All @@ -33,7 +34,7 @@ endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Dependency: TimeShield
find_package(TimeShield 1.0.4 QUIET CONFIG)
find_package(TimeShield 1.0.6 QUIET CONFIG)
if(NOT TimeShield_FOUND)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/time-shield-cpp/CMakeLists.txt")
add_subdirectory(external/time-shield-cpp)
Expand Down Expand Up @@ -185,6 +186,40 @@ if(LOGIT_WITH_PROMETHEUS_SERVER)
endif()
endif()

# ---------- MDBX ----------
if(LOGIT_WITH_MDBX)
if(EMSCRIPTEN)
message(FATAL_ERROR "LOGIT_WITH_MDBX is not supported for Emscripten.")
endif()
if(MSVC)
message(FATAL_ERROR "LOGIT_WITH_MDBX is not supported with MSVC until mdbx-containers supports MSVC.")
endif()

if(NOT TARGET mdbx_containers::mdbx_containers)
find_package(mdbx_containers QUIET CONFIG)
endif()

if(NOT TARGET mdbx_containers::mdbx_containers AND LOGIT_USE_SUBMODULES)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/mdbx-containers/CMakeLists.txt")
set(MDBXC_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(MDBXC_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(MDBXC_USE_ASAN OFF CACHE BOOL "" FORCE)
set(MDBXC_DEPS_MODE BUNDLED CACHE STRING "Dependency mode for MDBX: AUTO|SYSTEM|BUNDLED" FORCE)
add_subdirectory(external/mdbx-containers EXCLUDE_FROM_ALL)
endif()
endif()

if(NOT TARGET mdbx_containers::mdbx_containers)
message(FATAL_ERROR "mdbx-containers not found. Install it or add it as external/mdbx-containers and enable LOGIT_USE_SUBMODULES.")
endif()

target_compile_definitions(log-it-cpp INTERFACE LOGIT_WITH_MDBX=1)
target_link_libraries(log-it-cpp INTERFACE
$<BUILD_INTERFACE:mdbx_containers::mdbx_containers>
$<INSTALL_INTERFACE:mdbx_containers::mdbx_containers>
)
endif()

# ---------- GZIP (zlib) ----------
if(LOGIT_WITH_GZIP)
if(NOT TARGET ZLIB::ZLIB)
Expand Down Expand Up @@ -248,27 +283,38 @@ install(DIRECTORY include/ DESTINATION include)

install(TARGETS log-it-cpp EXPORT log-it-cppTargets)

set(_logit_install_export_supported ON)

# install(EXPORT) requires all linked targets to be in an export set.
# When kurlyk is pre-installed (IMPORTED target) the export works.
# When kurlyk is a submodule (non-IMPORTED), packaging is unsupported.
# The FATAL_ERROR is deferred to install time so development builds with
# submodules still work; only `cmake --install` is blocked.
# When optional dependencies are pre-installed (IMPORTED targets), the
# export works. When they are submodules (non-IMPORTED), packaging is
# unsupported. The FATAL_ERROR is deferred to install time so development
# builds with submodules still work; only `cmake --install` is blocked.
if(LOGIT_WITH_OTLP AND TARGET kurlyk)
get_target_property(_kurlyk_imported kurlyk IMPORTED)
if(NOT _kurlyk_imported)
set(_logit_install_export_supported OFF)
install(CODE [[
message(FATAL_ERROR
"log-it-cpp: Installing with LOGIT_WITH_OTLP=ON and bundled kurlyk is not supported. "
"Install kurlyk separately and use find_package(kurlyk), or disable LOGIT_WITH_OTLP for install.")
]])
else()
install(EXPORT log-it-cppTargets
FILE log-it-cppTargets.cmake
NAMESPACE log-it-cpp::
DESTINATION lib/cmake/log-it-cpp
)
endif()
else()
endif()

if(LOGIT_WITH_MDBX AND TARGET mdbx_containers::mdbx_containers)
get_target_property(_mdbx_containers_imported mdbx_containers::mdbx_containers IMPORTED)
if(NOT _mdbx_containers_imported)
set(_logit_install_export_supported OFF)
install(CODE [[
message(FATAL_ERROR
"log-it-cpp: Installing with LOGIT_WITH_MDBX=ON and bundled mdbx-containers is not supported. "
"Install mdbx-containers separately and use find_package(mdbx_containers), or disable LOGIT_WITH_MDBX for install.")
]])
endif()
endif()

if(_logit_install_export_supported)
install(EXPORT log-it-cppTargets
FILE log-it-cppTargets.cmake
NAMESPACE log-it-cpp::
Expand Down
3 changes: 3 additions & 0 deletions cmake/log-it-cppConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@

include(CMakeFindDependencyMacro)
find_dependency(TimeShield)
if(@LOGIT_WITH_MDBX@)
find_dependency(mdbx_containers)
endif()

include("${CMAKE_CURRENT_LIST_DIR}/log-it-cppTargets.cmake")
252 changes: 252 additions & 0 deletions examples/example_logit_mdbx_logger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/// \file example_logit_mdbx_logger.cpp
/// \brief Demonstrates structured log storage with MdbxLogger, including macros,
/// querying by date range, and reading sessions/payloads.

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

#include <logit.hpp>

#ifdef LOGIT_WITH_MDBX

#include <chrono>
#include <cstdio>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <vector>

namespace {

std::string make_db_path() {
std::ostringstream os;
os << logit::get_exec_dir() << "/example_mdbx_logger_" << LOGIT_CURRENT_TIMESTAMP_MS() << ".mdbx";
return os.str();
}

void cleanup_db(const std::string& path) {
std::remove(path.c_str());
std::remove((path + "-lck").c_str());
}

} // namespace

int main() {
const std::string db_path = make_db_path();
cleanup_db(db_path);

std::cout << "MdbxLogger example starting, db=" << db_path << std::endl;

// ------------------------------------------------------------------
// 1. Configure and add the MDBX backend
// ------------------------------------------------------------------
logit::MdbxLogger::Config mdbx_config;
mdbx_config.path = db_path;
mdbx_config.app_name = "example-mdbx";
mdbx_config.async = true;
mdbx_config.max_queue_size = 256;
mdbx_config.max_batch_size = 32;
mdbx_config.large_payload_threshold = 256;
mdbx_config.store_large_payloads_separately = true;

// Optional: capture write errors instead of writing to stderr
mdbx_config.on_error = [](const std::string& msg) {
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(
logit::MdbxLogger,
(mdbx_config),
logit::SimpleLogFormatter,
("[%l] %v"));

// The MDBX logger is now the last added backend; its index is:
const int mdbx_index = static_cast<int>(logit::Logger::get_instance().get_all_strategy_snapshots().size()) - 1;

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

// ------------------------------------------------------------------
// 2. Log messages via standard macros
// ------------------------------------------------------------------
LOGIT_INFO("Application started");
LOGIT_WARN("Disk usage is above 80%%");

{
LOGIT_SCOPE_INFO("process_batch");
LOGIT_INFO("Processing 42 items");
LOGIT_DEBUG("Detail: item id=7, status=pending");

// This message is larger than large_payload_threshold and will spill
// into the separate log_payloads table with a preview kept inline.
std::string big;
big.reserve(512);
for (int i = 0; i < 50; ++i) {
big += "chunk-" + std::to_string(i) + " ";
}
LOGIT_INFO(big);
}

LOGIT_ERROR("Connection timeout to upstream-3");
LOGIT_FATAL("Critical: unable to recover transaction state");

// Wait until the async queue is flushed.
LOGIT_WAIT();

// ------------------------------------------------------------------
// 3. Read the MDBX logger directly via typed macro helpers
// ------------------------------------------------------------------
LOGIT_WITH_LOGGER_AS(mdbx_index, logit::MdbxLogger, mdbx) {
// Session metadata
auto session_opt = mdbx->read_session(mdbx->session_id());
if (session_opt) {
std::cout << "\n--- Session ---" << std::endl;
std::cout << " app_name: " << session_opt->app_name << std::endl;
std::cout << " process_id: " << session_opt->process_id << std::endl;
std::cout << " started_ms: " << session_opt->start_time_ms << std::endl;
std::cout << " schema_ver: " << session_opt->schema_version << std::endl;
}

// 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);

std::cout << "\n--- All records (" << all_records.size() << ") ---" << std::endl;
for (const auto& r : all_records) {
std::cout << " [" << logit::to_string(r.level) << "] "
<< r.timestamp_ms << " seq=" << r.sequence
<< " msg=\"" << r.message << "\"";
if (r.payload_id != 0) {
std::cout << " [payload_id=" << r.payload_id << "]";
}
std::cout << std::endl;
}

// Level-based client-side filter
std::cout << "\n--- Records with level >= WARN ---" << std::endl;
for (const auto& r : all_records) {
if (static_cast<int>(r.level) >= static_cast<int>(logit::LogLevel::LOG_LVL_WARN)) {
std::cout << " [" << logit::to_string(r.level) << "] "
<< r.timestamp_ms << " " << r.message << std::endl;
}
}

// Spilled payloads (decompressed transparently)
std::cout << "\n--- Spilled payloads ---" << std::endl;
for (const auto& r : all_records) {
if (r.payload_id != 0) {
auto data_opt = mdbx->read_payload_data(r.payload_id);
if (data_opt) {
std::cout << " payload_id=" << r.payload_id
<< " size=" << data_opt->size()
<< " preview=\"" << r.message << "\""
<< std::endl;
} else {
std::cout << " payload_id=" << r.payload_id << " FAILED to read/decompress"
<< std::endl;
}
}
}

// Query by today's local midnight
try {
auto now_local = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::tm* tm_now = std::localtime(&now_local);
std::tm tm_midnight = *tm_now;
tm_midnight.tm_hour = 0;
tm_midnight.tm_min = 0;
tm_midnight.tm_sec = 0;
std::time_t midnight_t = std::mktime(&tm_midnight);

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(
today_ms.count(),
tomorrow_ms.count());

std::cout << "\n--- Records for today (" << day_records.size() << ") ---" << std::endl;
for (const auto& r : day_records) {
std::cout << " [" << logit::to_string(r.level) << "] "
<< r.timestamp_ms << " " << r.message << std::endl;
}
} catch (const std::exception& e) {
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;
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_updates.push_back(v);
});

LOGIT_INFO("Live event 1 via callback");
LOGIT_INFO("Live event 2 via callback");
LOGIT_WAIT();

std::cout << "\n--- Live updates received (" << live_updates.size()
<< ") ---" << std::endl;
for (const auto& r : live_updates) {
std::cout << " [" << logit::to_string(r.level) << "] "
<< r.message << std::endl;
}

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

LOGIT_INFO("Event after unsubscribe");
LOGIT_WAIT();
std::cout << "Live updates after unsubscribe: " << live_updates.size()
<< std::endl;

// Statistics
std::cout << "\n--- Statistics ---" << std::endl;
std::cout << " dropped: " << mdbx->dropped_count() << std::endl;
std::cout << " failed_writes: " << mdbx->failed_export_count() << std::endl;
} else {
std::cerr << "MDBX logger not found at index " << mdbx_index << std::endl;
LOGIT_SHUTDOWN();
cleanup_db(db_path);
return 1;
}

// ------------------------------------------------------------------
// 5. Graceful shutdown and cleanup
// ------------------------------------------------------------------
LOGIT_SHUTDOWN();

// After shutdown the session end_time_ms is persisted.
LOGIT_WITH_LOGGER_AS(mdbx_index, logit::MdbxLogger, mdbx) {
auto session_opt = mdbx->read_session(mdbx->session_id());
if (session_opt) {
std::cout << " session end_ms: " << session_opt->end_time_ms << std::endl;
}
}

cleanup_db(db_path);
std::cout << "\nMdbxLogger example completed." << std::endl;
return 0;
}

#else

#include <iostream>

int main() {
std::cout << "MdbxLogger example requires LOGIT_WITH_MDBX=ON." << std::endl;
return 0;
}

#endif
1 change: 1 addition & 0 deletions external/kurlyk
Submodule kurlyk added at f683f0
1 change: 1 addition & 0 deletions external/mdbx-containers
Submodule mdbx-containers added at fac400
2 changes: 1 addition & 1 deletion external/time-shield-cpp
Submodule time-shield-cpp updated 114 files
Loading
Loading