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
62 changes: 62 additions & 0 deletions README-RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ int main() {
LOGIT_SYSERR_ERROR("Ошибка удаления временного каталога");
```

## Обратное давление и горячее изменение размера

Асинхронный `TaskExecutor` поддерживает как очередь на основе `std::deque` под мьютексом, так и опциональный lock-free MPSC ring
(включается флагом `LOGIT_USE_MPSC_RING`). Политики переполнения (`Block`, `DropNewest`, `DropOldest`) ведут себя одинаково в
обеих конфигурациях; в MPSC-режиме `DropOldest` намеренно отбрасывает **входящую** задачу, чтобы не нарушать порядок уже приня
тых. Кольцевой буфер по умолчанию вмещает `LOGIT_TASK_EXECUTOR_DEFAULT_RING_CAPACITY` задач (1024) и может быть перенастроен ком
бинацией `LOGIT_SET_MAX_QUEUE(...)` с этим макросом, если приложению требуется другой базовый объём. В сборках с MPSC допускаетс
я "горячее" изменение размера очереди без потери принятых задач — продюсеры кратковременно ждут, пока поток-воркер пересобирает
буфер.

## Возможности

- **Гибкое форматирование логов**:
Expand Down Expand Up @@ -667,6 +677,58 @@ LogIt++ зависит от *time-shield-cpp*, который находится

LogIt++ включает библиотеку *fmt* для форматирования с `{}`. Чтобы использовать макросы `LOGIT_FMT_*` и `LOGIT_SCOPE_FMT_*`, соберите библиотеку с опцией CMake `-DLOGIT_WITH_FMT=ON`.

## Опции CMake

Все параметры сборки перечислены ниже:

- `LOGIT_CPP_BUILD_TESTS` (по умолчанию: ON, если проект собирается на верхнем уровне) — сборка тестов.
- `LOGIT_CPP_BUILD_EXAMPLES` (по умолчанию: OFF) — сборка примеров.
- `LOGIT_BENCH_ENABLE` (по умолчанию: OFF) — сборка бенчмарков; `LOGIT_BENCH_WITH_SPDLOG` (по умолчанию: OFF) добавляет сравнение со spdlog.
- `LOGIT_WITH_GZIP` / `LOGIT_WITH_ZSTD` (по умолчанию: OFF) — поддержка gzip или zstd для ротируемых файлов.
- `LOGIT_WITH_FMT` (по умолчанию: OFF) — подключить макросы в стиле `{}`; `LOGIT_USE_SUBMODULES` (по умолчанию: OFF) разрешает использовать вложенные зависимости fmt/time-shield при отсутствии системных пакетов.
- `LOGIT_WITH_SYSLOG` (по умолчанию: ON на Unix-подобных системах) — сборка бэкенда syslog.
- `LOGIT_WITH_WIN_EVENT_LOG` (по умолчанию: ON в Windows) — сборка бэкенда Windows Event Log.
- `LOGIT_FORCE_ASYNC_OFF` (по умолчанию: OFF) — принудительно отключить асинхронное выполнение даже в многопоточных сборках.
- `LOGIT_USE_MPSC_RING` (по умолчанию: ON) — использовать lock-free очередь вместо варианта на `std::deque`.
- `LOGIT_ENABLE_DROP_OLDEST_SLOWPATH` (по умолчанию: ON) — скомпилировать медленный путь для `DropOldest`, когда кольцо заполнено.
- `LOGIT_EMSCRIPTEN` (по умолчанию: ON при сборке Emscripten) — подстройка под однопоточные среды WebAssembly.

## Бенчмарки

Запустите `./build/bench/logit_bench`, чтобы получить полный набор измерений (sync/async × null/file × количество продюсеров × размер сообщений). Результаты дописываются в `bench/results/latency.csv` по одной строке на каждую библиотеку/комбинацию. При необходимости сократите нагрузку с помощью переменных окружения `LOGIT_BENCH_TOTAL` и `LOGIT_BENCH_WARMUP`.

### Что на самом деле измеряет бенчмарк

Харнесс меряет end-to-end латентность (*вызов лога → доставка в sink*) и суммарную пропускную. Он полезен для поиска регрессий и сравнения дизайна пайплайнов, но это **не** идеальное соревнование «кто быстрее». LogIt++ осознанно тратит больше работы в духе Python `icecream`: один `LOGIT_*` может парсить имена аргументов, собирать `args_array` из `VariableValue` и опционально форматировать структуру. Классические printf-логгеры вроде spdlog оптимизируются под быстрое форматирование строк и очереди, без этой «леденцовой» ветки. Для корректного сравнения держите оба лагеря в одном режиме:

- *Только текст / passthrough* показывает стоимость dispatch/очереди/sink и ближе всего к поведению spdlog по умолчанию.
- *IceCream-стиль метаданных* (`LOGIT_*` с захватом аргументов) включает парсинг имён и упаковку значений; тут LogIt++ делает больше работы на вызов намеренно.

Асинхронные цифры включают enqueue + пробуждение воркера/планирование ОС + работу sink; для file sink добавляется разброс из-за буферов/flush.

### Последний снимок (05.12.2025)

- Сборка: `Release`, `LOGIT_BENCH_ENABLE=ON`, `LOGIT_BENCH_WITH_SPDLOG=ON`, `LOGIT_USE_MPSC_RING=ON` (по умолчанию).
- Нагрузка: `LOGIT_BENCH_TOTAL=10000`, 4 продюсера, размер сообщений 200 байт для таблицы сравнения (остальные комбинации см. в `bench/results/latency-2025-12-05-10k.csv`).
- Метрики: медианная задержка (`p50`) в наносекундах и достигнутая пропускная способность (сообщений/с).
- Железо: 3 vCPU (Intel Xeon E5-2673 v4 @ 2.30GHz) в виртуальной машине, одна NUMA-нода.
- Данные: обновлено по `bench/results/latency-2025-12-05-10k.csv` (05.12.2025, 03:18 UTC).

| Режим | Приёмник | LogIt++ p50 | Пропускная (LogIt++) | spdlog p50 | Пропускная (spdlog) |
|-------|----------|-------------|----------------------|------------|---------------------|
| Sync | Null | 119 нс | 2 127 704 сооб./с | 86 нс | 5 803 783 сооб./с |
| Sync | File | 130 нс | 1 035 690 сооб./с | 87 нс | 1 593 987 сооб./с |
| Async | Null | 20 916 нс | 1 846 272 сооб./с | 1 248 779 нс | 1 303 573 сооб./с |
| Async | File | 255 323 нс | 651 384 сооб./с | 5 001 140 нс | 1 153 976 сооб./с |

**Выводы:** LogIt++ держит sub-µs p50 в синхронных режимах, даже с путём метаданных в стиле IceCream; spdlog выигрывает по скорости на null/file за счёт более лёгкого форматирования. В async обе стороны включают enqueue + пробуждения + sink: LogIt++ остаётся в десятках–сотнях микросекунд, тогда как адаптер spdlog в этой конфигурации уходит в миллисекунды.

### Как устроен бенч-харнесс (LatencyRecorder)

- `bench/LatencyRecorder.hpp` заранее резервирует слоты и ведёт `Token {slot, t0_ns, active}` → `Summary {p50, p99, p999}` с защитой от повторных `complete()` на один слот. Доступны методы `recorded()`, `wait_for_all()` и `finalize()` для end-to-end измерений между продюсерами и консюмером.
- Адаптер LogIt кладёт номер слота в `LogRecord::line` (см. `bench/adapters/LogItAdapter.cpp`). Приёмник вызывает `LatencyRecorder::complete_slot()`, когда видит неотрицательный номер строки; никаких дополнительных полей в записи не требуется.


## Системные бэкенды

LogIt++ может отправлять сообщения в системные журналы.
Expand Down
65 changes: 64 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ policies (`Block`, `DropNewest`, `DropOldest`) behave consistently across both
implementations, with the MPSC build intentionally dropping the *incoming* task
for `DropOldest` to keep accepted work ordered. The ring build also allows
"hot" queue resizes where producers briefly wait while the worker rebuilds the
ring buffer without losing in-flight tasks. See
ring buffer without losing in-flight tasks. The default MPSC buffer holds
`LOGIT_TASK_EXECUTOR_DEFAULT_RING_CAPACITY` tasks (1024 by default) and can be
retuned by combining `LOGIT_SET_MAX_QUEUE(...)` with the compile-time macro if
your workload needs a different baseline. See
[`docs/TaskExecutor.md`](docs/TaskExecutor.md) for a full breakdown and tuning
tips.

Expand Down Expand Up @@ -706,6 +709,22 @@ If you are using an IDE like **Visual Studio** or **CLion**, you can add the inc

LogIt++ includes the *fmt* library for `{}`-based formatting. To use the `LOGIT_FMT_*` and `LOGIT_SCOPE_FMT_*` macros, build the library with the CMake option `-DLOGIT_WITH_FMT=ON`.

## CMake options

The following toggles cover all build-time features:

- `LOGIT_CPP_BUILD_TESTS` (default: ON when the project is the root build) — build the test suite.
- `LOGIT_CPP_BUILD_EXAMPLES` (default: OFF) — build the example programs.
- `LOGIT_BENCH_ENABLE` (default: OFF) — build benchmarks; `LOGIT_BENCH_WITH_SPDLOG` (default: OFF) also builds the spdlog comparisons.
- `LOGIT_WITH_GZIP` / `LOGIT_WITH_ZSTD` (defaults: OFF) — enable gzip or zstd support for rotated files.
- `LOGIT_WITH_FMT` (default: OFF) — include the `{}`-style formatting macros; enable `LOGIT_USE_SUBMODULES` (default: OFF) to pull the bundled fmt/time-shield fallbacks when system packages are missing.
- `LOGIT_WITH_SYSLOG` (default: ON on Unix-like targets) — build the syslog backend.
- `LOGIT_WITH_WIN_EVENT_LOG` (default: ON on Windows) — build the Windows Event Log backend.
- `LOGIT_FORCE_ASYNC_OFF` (default: OFF) — force synchronous logging even in multi-threaded builds.
- `LOGIT_USE_MPSC_RING` (default: ON) — use the lock-free task queue instead of the mutex-backed deque.
- `LOGIT_ENABLE_DROP_OLDEST_SLOWPATH` (default: ON) — compile the slow-path used by `DropOldest` when the ring is full.
- `LOGIT_EMSCRIPTEN` (default: ON under Emscripten toolchains) — adjust the build for single-threaded WebAssembly environments.

## System Backends

LogIt++ can forward messages to system logging facilities.
Expand Down Expand Up @@ -750,6 +769,50 @@ Run `./build/bench/logit_bench` to record the full matrix (sync/async × null/fi
are appended to `bench/results/latency.csv` with one row per library/combination. Override the workload via `LOGIT_BENCH_TOTAL`
and `LOGIT_BENCH_WARMUP` environment variables if you need a lighter run.

### What this benchmark measures

The harness times end-to-end latency (*log call → delivery into the sink*) and aggregate throughput. It is great for spotting
regressions and comparing pipeline designs, but it is **not** a perfect “fastest logger wins” contest. LogIt++ intentionally does
extra work inspired by Python’s `icecream`: a single `LOGIT_*` call can extract argument names, build `args_array` with
`VariableValue`, and optionally format those structured values. Classic printf-style loggers such as spdlog focus on fast string
formatting and queueing instead of this metadata path. If you want an apples-to-apples view, keep the comparison within the same
mode:

- *Text-only/passthrough* shows dispatch/queue/sink cost and is the closest to spdlog’s default path.
- *Metadata-heavy* (`LOGIT_*` with argument capture) includes parsing and packing the structured arguments; LogIt++ will do more
work per call here by design.

Async numbers also include enqueue + worker wakeup/scheduling + sink time; file sinks add I/O variance from buffering and flush
policies.

### Latest snapshot (Dec 05, 2025)

- Build: `Release`, `LOGIT_BENCH_ENABLE=ON`, `LOGIT_BENCH_WITH_SPDLOG=ON`, `LOGIT_USE_MPSC_RING=ON` (default).
- Workload: `LOGIT_BENCH_TOTAL=10000`, 4 producers, message size 200 bytes for the comparison table (all other sizes/counts
are in `bench/results/latency-2025-12-05-10k.csv`).
- Metrics: median (`p50`) latency in nanoseconds and achieved throughput (messages/sec).
- Hardware: 3 vCPU VM (Intel Xeon E5-2673 v4 @ 2.30GHz), single NUMA node.
- Data: refreshed from `bench/results/latency-2025-12-05-10k.csv` (Dec 05, 2025 @ 03:18 UTC).

| Mode | Sink | LogIt++ p50 | LogIt++ throughput | spdlog p50 | spdlog throughput |
|------|------|-------------|--------------------|------------|-------------------|
| Sync | Null | 119 ns | 2,127,704 msg/s | 86 ns | 5,803,783 msg/s |
| Sync | File | 130 ns | 1,035,690 msg/s | 87 ns | 1,593,987 msg/s |
| Async | Null | 20,916 ns | 1,846,272 msg/s | 1,248,779 ns | 1,303,573 msg/s |
| Async | File | 255,323 ns | 651,384 msg/s | 5,001,140 ns | 1,153,976 msg/s |

**Takeaways:** LogIt++ keeps sub-microsecond p50s in synchronous modes while carrying the IceCream-style metadata path; spdlog’s lean formatting stays faster on the null/file sinks. Asynchronously, both numbers include enqueue + worker wakeups + sink work; LogIt++ stays in the tens-to-hundreds of microseconds, while the spdlog adapter lands in low-to-mid milliseconds for this run.

### Benchmark harness notes (LatencyRecorder)

- `bench/LatencyRecorder.hpp` preallocates slots and tracks `Token {slot, t0_ns, active}` → `Summary {p50, p99, p999}` with per-
slot deduplication (duplicate `complete()` calls are ignored). It exposes `recorded()`, `wait_for_all()`, and `finalize()` for
end-to-end timing across producers/consumers.
- The LogIt adapter stores the benchmark slot in `LogRecord::line` (see `bench/adapters/LogItAdapter.cpp`). Sinks call
`LatencyRecorder::complete_slot()` when they observe a non-negative line number, so no extra payload is needed inside the log
record.


---

## Documentation
Expand Down
Loading