Use this file when you need a fast, project-specific mental model before
changing log-it-cpp. It complements the narrower playbooks in guides/ and
does not replace them.
log-it-cpp is a header-only C++ logging library. Its public surface is a
macro-first facade (include/logit_cpp/logit/log_macros.hpp) backed by a
singleton dispatcher (include/logit_cpp/logit/Logger.hpp), formatter
strategies (include/logit_cpp/logit/formatter/), logger backends
(include/logit_cpp/logit/loggers/), utility DTOs/helpers
(include/logit_cpp/logit/utils/), and private execution internals
(include/logit_cpp/logit/detail/).
The project is not a DDD application. There are no HTTP/WebSocket endpoints, database layers, event buses, repositories, or product domains. Treat the "domains" below as library subsystems.
Prefer these umbrella headers instead of recreating include order manually:
| Header | Purpose |
|---|---|
include/logit_cpp/logit.hpp |
Full public entry point: config, enums, utilities, formatters, loggers, Logger, and macros. |
include/logit_cpp/logit/utils.hpp |
Public utilities module and DTOs such as LogRecord, BufferedLogEntry, LogFileInfo, and VariableValue. |
include/logit_cpp/logit/formatter.hpp |
Formatter interfaces, SimpleLogFormatter, and pattern compiler support. |
include/logit_cpp/logit/loggers.hpp |
Logger backend umbrella plus required shared internals. |
Installed consumers include from include/logit_cpp, so examples use
#include <logit.hpp> or #include <logit/loggers.hpp>.
Leaf headers follow the aggregate-first NHR rule. If direct leaf access is needed, include the nearest umbrella first:
#include <logit/loggers.hpp>
#include <logit/loggers/MemoryLogger.hpp>See guides/header-impl.md before changing include structure.
| Subsystem | Main files | Responsibility |
|---|---|---|
| Macro facade | log_macros.hpp |
Public logging, setup, query, queue, and short-name macros. Normal application code should stay here. |
| Dispatcher | Logger.hpp |
Owns logger/formatter strategies, filtering, single-mode routing, targeted logging, snapshots, shutdown. |
| Logger interfaces | loggers/ILogger.hpp |
Backend contract for sinks, parameters, buffered snapshots, persisted file access, and flush/wait. |
| Logger backends | ConsoleLogger.hpp, FileLogger.hpp, UniqueFileLogger.hpp, MemoryLogger.hpp, SyslogLogger.hpp, EventLogLogger.hpp, CrashLogger.hpp |
Concrete sinks. Platform-specific backends provide stubs or aliases where unsupported. |
| Formatter interfaces | formatter/ILogFormatter.hpp, formatter/SimpleLogFormatter.hpp |
Strategy interface and default pattern/JSON formatter. |
| Pattern compiler | formatter/compiler/PatternCompiler.hpp |
Parses formatting patterns into FormatInstruction objects used by SimpleLogFormatter. |
| Utilities and DTOs | utils/*.hpp, enums.hpp, config.hpp |
Public data structures, value formatting, argument parsing, paths, tags, encoding, config macros. |
| Async internals | detail/TaskExecutor.hpp, detail/MpscRingAny.hpp |
Shared task queue, backpressure, MPSC variant, Emscripten variant. |
| File compression internals | detail/CompressionWorker.hpp |
Optional rotated-file compression using zlib, zstd, or external commands. |
| Tests | tests/ |
Runtime behavior, include contracts, optional features, ODR checks, Emscripten smoke tests. |
| Examples | examples/ |
User-facing usage patterns for macros, memory/file/custom/system loggers. |
| Benchmarks | bench/ |
Adapter-based latency benchmark harness. Not normal library API. |
The practical dependency direction is:
config.hppandenums.hppprovide shared public constants and enums.utils.hppaggregates utility DTOs/helpers.formatter.hppdepends on utilities and exposes formatter strategies.loggers.hppdepends on utilities and detail helpers, then exposes backend implementations.Logger.hppcombinesILoggerandILogFormatterstrategies.log_macros.hppis the macro facade overLoggerandTaskExecutor.
Avoid these dependency shapes:
- Public leaf headers directly including sibling modules when the nearest umbrella exists.
logit/detail/*including publiclogit/...headers.- Consumer examples or application-style tests depending on
logit/detail/*. - New code that requires users to include files in a fragile custom order.
- Vendor or generated paths leaking into public headers.
Namespaces:
- Public API lives in
namespace logit. - Private implementation details use
namespace logit::detail, written in the existing C++11-compatible stylenamespace logit { namespace detail { ... } }. - Benchmarks use
namespace logit_bench.
- Facade:
log_macros.hpphides low-levelLoggerandLogRecordconstruction from normal users. - Singleton:
Logger::get_instance()anddetail::TaskExecutor::get_instance()are process-wide coordinators. Both intentionally use static pointer singletons to survive shutdown/static-destruction ordering. - Strategy:
Loggerstores anILoggersink and anILogFormatterformatter per strategy. - Interface-based extension: custom sinks implement
ILogger; custom formatters implementILogFormatter. - Adapter:
bench/adapters/*adapts LogIt and spdlog to the benchmark harness. Do not copy benchmark shortcuts into public API without review. - Task executor: async loggers enqueue lambdas into
detail::TaskExecutor. Queue semantics are documented indocs/TaskExecutor.md. - Compiler/interpreter:
PatternCompilerturns pattern strings intoFormatInstructionobjects that are applied during formatting. - DTOs: public snapshot/file records are small structs in
utils/.
Preferred for new code:
- Add extension points through
ILogger,ILogFormatter, public DTOs, and macros rather than exposing internalLoggerStrategystate. - Prefer macro-level examples for user documentation.
- Keep async behavior behind
TaskExecutorunless the backend is explicitly synchronous, likeMemoryLogger.
Be cautious copying:
- Manual
LogRecordconstruction inbench/adapters/LogItAdapter.cpp; it is a benchmark-only low-level adapter. - Platform stubs in
FileLogger,UniqueFileLogger,SyslogLogger, andEventLogLogger; those exist to keep unsupported targets buildable. - C++17-only conveniences unless a C++11 fallback is also maintained.
The canonical style rules live in guides/cpp_style.md.
Important project-specific points:
- Classes, structs, and enum types use
CamelCase; methods usesnake_case. - Member fields use
m_. - Constants and macros use
UPPER_SNAKE_CASE. - Public enum values currently use a mix:
LogLevelhas legacyLOG_LVL_*,TextColorandRotationNaminguseCamelCase, andCompressTypeuses names such asGZIPandEXTERNAL_CMD. Preserve existing public names unless a task explicitly requires a breaking rename. - Headers begin with
#pragma onceand a non-reservedLOGIT_CPP_HEADER_*_INCLUDEDinclude guard. - Doxygen comments are English and usually use
/// \brief. - Project headers appear before system headers when adding include lists.
- Keep documentation and sources in UTF-8. Use
u8"..."for non-ASCII C++ string literals. - The default standard is C++11 (
CMakeLists.txt). Guard C++17-only code with#if __cplusplus >= 201703Las inVariableValue.hpp,Logger.hpp, andlog_macros.hpp.
Error handling style:
- Logging backends usually avoid throwing through the logging path. File and
console paths report internal failures to
std::cerr. - Unsupported feature APIs return neutral values (
"",0,0.0, empty vectors, orLogFileReadResult{ok=false}) rather than throwing. - File APIs only expose persisted files owned by the backend; they do not drain async queues.
Pointer ownership:
- Logger registration transfers ownership with
std::unique_ptr. Loggerstores strategies asstd::shared_ptr<LoggerStrategy>only so log dispatch can copy a stable snapshot outside the logger-list lock.- Avoid adding
std::shared_ptrownership to public APIs unless shared lifetime is genuinely required. - C++17 macros use
std::make_unique; C++11 macro branches usestd::unique_ptr<T>(new T(...)). Keep both branches in sync.
Reuse these helpers instead of writing local equivalents:
| Need | Reuse |
|---|---|
| Current timestamps and monotonic time | LOGIT_CURRENT_TIMESTAMP_MS(), LOGIT_MONOTONIC_MS() from config.hpp; TimeShield for conversions. |
Formatting printf-style strings |
logit::format() in utils/format.hpp. |
| Capturing macro arguments | split_arguments() and args_to_array() in utils/argument_utils.hpp; VariableValue in utils/VariableValue.hpp. |
| Structured memory snapshots | BufferedLogEntry and MemoryLogger. |
| Persisted log file metadata/results | LogFileInfo, LogFileReadResult, and ILogger file APIs. |
| Path handling for file loggers | utils/path_utils.hpp helpers. |
| Tags | utils/tag_utils.hpp and LOGIT_*_TAG macros. |
| Encoding conversions | utils/encoding_utils.hpp. |
| Async work and backpressure | detail::TaskExecutor through public macros when possible. |
| Rotation compression | detail::CompressionWorker through FileLogger::Config. |
There are no JSON, HTTP, WebSocket, database, or serialization utility layers
beyond the simple JSON string formatting inside SimpleLogFormatter.
Use this for DTO-style data exposed by logger APIs.
- Add a PascalCase header in
include/logit_cpp/logit/utils/, for exampleLogArchiveInfo.hpp. - Put the struct in
namespace logit, give fields safe defaults, and document it with/// \brief. - Include it from
include/logit_cpp/logit/utils.hpp. - Add or update an include-contract test like
tests/include_log_file_info_nhr_test.cpp. - If the DTO affects public APIs, update
README.md,README-RU.md, anddocs/mainpage.dox.
Compact style example:
#pragma once
#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_UTILS_LOG_ARCHIVE_INFO_HPP_INCLUDED
#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_UTILS_LOG_ARCHIVE_INFO_HPP_INCLUDED
#include <cstdint>
#include <string>
namespace logit {
/// \brief Metadata for an archived log artifact.
struct LogArchiveInfo {
std::string path;
int64_t created_ms = 0;
bool compressed = false;
};
} // namespace logit
#endif // LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_UTILS_LOG_ARCHIVE_INFO_HPP_INCLUDEDUse this when adding a new sink such as a network, ring-buffer, or platform backend. Do not add HTTP/WebSocket endpoints unless the user explicitly asks for that feature; none exist today.
- Add
include/logit_cpp/logit/loggers/NewLogger.hpp. - Derive from
logit::ILoggerand implement all pure virtual methods:log,get_string_param,get_int_param,get_float_param,set_log_level,get_log_level, andwait. - If the backend is async, enqueue work through
detail::TaskExecutorand make the destructor/wait path flush or stop safely. - Add it to
include/logit_cpp/logit/loggers.hpp. - Add registration macros in both C++17 and C++11 branches of
log_macros.hppif it should be user-facing. - Add tests under
tests/and an example underexamples/if the backend is a public feature.
Minimal skeleton:
class NewLogger : public logit::ILogger {
public:
void log(const logit::LogRecord& record, const std::string& message) override {
if (!record.raw_mode &&
static_cast<int>(record.log_level) < m_log_level.load(std::memory_order_relaxed)) {
return;
}
// Write message to the sink.
}
std::string get_string_param(const logit::LoggerParam&) const override { return std::string(); }
int64_t get_int_param(const logit::LoggerParam&) const override { return 0; }
double get_float_param(const logit::LoggerParam&) const override { return 0.0; }
void set_log_level(logit::LogLevel level) override { m_log_level.store(static_cast<int>(level)); }
logit::LogLevel get_log_level() const override {
return static_cast<logit::LogLevel>(m_log_level.load());
}
void wait() override {}
private:
std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(logit::LogLevel::LOG_LVL_TRACE));
};Use this when adding a new % token to SimpleLogFormatter.
- Add a new
FormatInstruction::FormatTypevalue informatter/compiler/PatternCompiler.hpp. - Update parser logic in
PatternCompiler::compile(...)so the token creates that instruction. - Update
FormatInstruction::apply(...)to render the token fromLogRecord, TimeShield date/time data, or another existing source. - Add tests that format a record through
SimpleLogFormatter; avoid testing only the parser in isolation. - Document the pattern token in README/mainpage if it is public.
- Do not mutate
LogRecordfields except for its intentional mutableargs_arraycache insideLogger::print(). - Preserve macro-first usage. Ordinary examples/tests should not manually build
LogRecordor call low-levelLogger::log()unless they test internals, adapters, or extension contracts. - Keep
Loggerthread-safety: copy strategy snapshots underm_loggers_mx, then invoke backends under each strategy'sexec_mx. - Backend snapshot APIs (
MemoryLogger, file APIs) must be safe whilelog()may run concurrently. - Async lambdas capturing
thisrequire destructor orwait()logic that drains pending tasks before members disappear. TaskExecutorsemantics differ by build: deque, MPSC, and single-threaded Emscripten. Readdocs/TaskExecutor.mdbefore changing queue behavior.QueuePolicy::DropOldestintentionally drops incoming tasks in MPSC builds; do not "fix" this without updating docs and tests.- Keep Emscripten support buildable. Unsupported file/system backends use stubs or disabled CMake options.
- Optional dependency features must stay behind compile definitions:
LOGIT_WITH_FMT,LOGIT_HAS_ZLIB,LOGIT_HAS_ZSTD,LOGIT_HAS_SYSLOG,LOGIT_HAS_WIN_EVENT_LOG, andLOGIT_EMSCRIPTEN. - Preserve public include paths and public macro names for backward compatibility.
- Do not edit
external/vendored submodules except for an explicit dependency update task.
- Runtime behavior tests live as one executable per
tests/*.cpp; the test name is the file stem fromtests/CMakeLists.txt. - Include-contract tests use the NHR pattern, for example
include_formatter_nhr_test.cppandinclude_loggers_nhr_test.cpp. - Optional feature tests are conditionally excluded when flags are off:
fmt_macros_test.cpp, gzip/zstd compression tests. - ODR tests in
tests/odr/are compiled manually in CI to catch header-only singleton and inline issues. - Emscripten tests are separate under
tests/ems/. - Backpressure tests include TSAN labels for queue behavior.
When changing public include structure, add an include-contract test. When
changing async behavior, run the relevant backpressure tests and prefer a full
ctest --output-on-failure pass.
| Path | Purpose | Edit when |
|---|---|---|
AGENTS.md |
Root agent rules and playbook index. | Agent workflow or repository-wide guidance changes. |
guides/ |
Detailed agent playbooks. | A recurring agent workflow needs documented project-specific rules. |
include/logit_cpp/logit.hpp |
Full umbrella header. | Public entry-point composition changes. |
include/logit_cpp/logit/config.hpp |
Config macros and default paths/patterns. | Adding compile-time knobs or default settings. |
include/logit_cpp/logit/enums.hpp |
Public enums and enum-to-string helpers. | Adding public enum values or logger parameters. |
include/logit_cpp/logit/utils.hpp |
Utilities umbrella. | Adding/removing public utility headers. |
include/logit_cpp/logit/utils/ |
DTOs, argument/value/path/tag/encoding helpers. | Adding public data structures or reusable helper functions. |
include/logit_cpp/logit/formatter.hpp |
Formatter umbrella. | Adding formatter-facing public headers. |
include/logit_cpp/logit/formatter/ |
Formatter interfaces and pattern compiler. | Changing output formatting or formatter extension points. |
include/logit_cpp/logit/loggers.hpp |
Logger backend umbrella. | Adding/removing public backend headers. |
include/logit_cpp/logit/loggers/ |
Concrete sinks and ILogger. |
Adding or changing backend behavior. |
include/logit_cpp/logit/detail/ |
Private executor, compression, stream, scope internals. | Internal library mechanics only; avoid from consumer code. |
include/logit_cpp/logit/Logger.hpp |
Singleton dispatcher and strategy management. | Changing routing, filtering, snapshot, or shutdown behavior. |
include/logit_cpp/logit/log_macros.hpp |
Public macro API. | Adding macro families, setup/query macros, or C++11/17 macro branches. |
tests/ |
Unit, integration, include, ODR, optional feature tests. | Any behavior or include-contract change. |
examples/ |
User-facing examples. | Public workflow or new backend examples. |
bench/ |
Benchmark harness and adapters. | Performance scenarios or benchmark-specific adapter changes. |
docs/ |
Doxygen mainpage and design notes. | Public documentation or deeper design notes. |
cmake/ |
Package config and pkg-config templates. | Install/export metadata changes. |
vcpkg-overlay/ |
Local vcpkg port. | Packaging behavior changes. |
.github/workflows/ |
CI and publishing workflows. | Build matrix, verification, or release automation changes. |
external/ |
Vendored submodules. | Explicit dependency updates only. |
external/vendor trees.- Generated or local build trees such as
build/,build-mingw*/, andtmp/. .github/doxygen-awesome-css/unless updating the docs theme submodule.- Public macro names, enum values, and include paths unless the task is a deliberate breaking change.
- Benchmark internals when changing normal library behavior, except to update benchmark coverage for a performance-related feature.
- Read the narrow playbook that matches the task:
guides/logging-macros.md,guides/header-impl.md,guides/build.md, orguides/commits.md. - Use umbrella headers and preserve include policy.
- Keep C++11 compatibility unless the code path is explicitly C++17-gated.
- Keep async and snapshot APIs thread-safe.
- Add tests matching the changed surface.
- Update README, Doxygen, examples, or vcpkg metadata when public behavior changes.