Skip to content

feat(logging): zero-allocation format->dispatch path (LogRecord view)#795

Open
kamcheungting-db wants to merge 1 commit into
apache:mainfrom
kamcheungting-db:logging-zero-alloc
Open

feat(logging): zero-allocation format->dispatch path (LogRecord view)#795
kamcheungting-db wants to merge 1 commit into
apache:mainfrom
kamcheungting-db:logging-zero-alloc

Conversation

@kamcheungting-db

Copy link
Copy Markdown
Contributor

Makes the logging format → dispatch path allocation-free, addressing the zero-allocation request on #725. Builds on the merged logging blocks (#722/#723/#724).

What changed

  • Sinks now receive a non-owning LogRecord ({ level, std::string_view message, std::source_location, std::span<const LogAttribute> }) instead of an owned LogMessage&&. The views are valid only for the duration of the Log() call.
  • The function-style Log() / FormatAndEmit format into a reused per-thread FormatBuffer() via std::format_to (clear() keeps capacity), so logging performs no heap allocation after the buffer warms up. std::format_to with a std::format_string keeps compile-time format-string checking.
  • LogMessage/Builder stay as the owned/retained type and convert implicitly to LogRecord, so logger->Log(builder.Build()) is unchanged.
  • Synchronous sinks (CerrLogger, no-op) read the view directly with no allocation; retaining sinks (the test CapturingLogger) deep-copy into an owned LogMessage, so existing assertions are unaffected.

Why now: every production sink consumes the record synchronously, so the owned-std::string model wasn't buying anything in practice — better to change the interface before external adoption.

Tests: new logging_zero_alloc_test (its own binary; global operator new counter + a negative control proving the probe works) asserts the format→dispatch path does 0 heap allocations after warmup. Existing logging tests pass unchanged; ASan/TSan clean.

Out of scope (documented): CerrLogger's own rendered-line std::format and ICEBERG_LOG_RUNTIME_FMT (vformat) still allocate.

The open macro/spdlog/registry PRs (#725/#726/#737) will be rebased onto this interface once it lands.

This pull request and its description were written by Isaac.

Sinks now receive a non-owning `LogRecord` { level, string_view message,
source_location, span<const LogAttribute> } instead of an owned `LogMessage&&`.
The macros/function-style Log() format into a reused per-thread `FormatBuffer()`
(std::format_to + clear-keeps-capacity), so logging does no heap allocation after
the buffer warms up. `LogMessage`/`Builder` stay as the owned/retained type and
convert implicitly to `LogRecord`, so `logger->Log(builder.Build())` is unchanged.

Synchronous sinks (Cerr/Spd/Noop) read the view directly; retaining sinks
(test CapturingLogger) deep-copy into an owned LogMessage. Adds a dedicated
logging_zero_alloc_test (global operator-new counter + negative control) proving
the path is allocation-free. CerrLogger's own line rendering and
ICEBERG_LOG_RUNTIME_FMT still allocate (documented; out of this change's scope).

Co-authored-by: Isaac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant