Skip to content

feat(logging): add CerrLogger std::cerr backend (3/6)#724

Open
kamcheungting-db wants to merge 4 commits into
apache:mainfrom
kamcheungting-db:logging-block3-cerr
Open

feat(logging): add CerrLogger std::cerr backend (3/6)#724
kamcheungting-db wants to merge 4 commits into
apache:mainfrom
kamcheungting-db:logging-block3-cerr

Conversation

@kamcheungting-db

Copy link
Copy Markdown

What

Third of the logging stack.

First concrete backend and the default sink when spdlog isn't compiled in. Writes one line per record to std::cerr — 2026-06-10T18:06:17.518Z error [295021] table.cc:42] msg (UTC ISO-8601 ms timestamp, level, OS thread id, file:line, message).

Level threshold is a lock-free atomic; a mutex guards each whole-line write so concurrent records don't interleave; Log/Flush swallow exceptions to uphold the noexcept contract. Becomes the seed default, replacing the no-op placeholder.

Testing

compiled and run with clang++ -std=c++23 -stdlib=libc++ (clang 18) — line format, level filtering, and a 6-thread × 40-line no-interleave test all pass.

Re-add spdlog as a build dependency to serve as the foundation for the
logging system. Adapted to current structure: spdlog joins the core
iceberg lib interface lists (roaring has since moved to iceberg_data).

Co-authored-by: Isaac
This is the first of six small PRs that together add a logging system to
iceberg-cpp. It introduces LogLevel, the severity scale everything else builds
on: trace, debug, info, warn, error, critical, fatal, plus an `off` sentinel for
turning logging off entirely.

Levels are ordered from most to least verbose, so deciding whether something
should be logged is a plain `level >= threshold` comparison. Two helpers come
with it: ToString to print a level, and LogLevelFromString to parse one
(case-insensitive, returning a Result for unrecognized input). Both follow the
same shape as CounterUnit in metrics/counter.h.

There is no implementation behind the header yet, so this PR only adds the
header, wires the new src/iceberg/logging directory into the build, and adds a
test covering the round-trip, ordering, and parsing.

Co-authored-by: Isaac
@kamcheungting-db kamcheungting-db changed the title feat: [Iceberg Logger] [Part-3] CerrLogger std::cerr backend feat(logging): add CerrLogger std::cerr backend Jun 11, 2026
@kamcheungting-db kamcheungting-db changed the title feat(logging): add CerrLogger std::cerr backend feat(logging): add CerrLogger std::cerr backend (3/6) Jun 11, 2026
Second of the logging stack. This defines what a logger is and where the rest of
the code finds one, with no concrete backend yet.

Logger is a small abstract interface: ShouldLog to test a level, Log to emit an
already-formatted record, SetLevel/level, Flush, and a shared no-op instance.
Records are passed as LogMessage, which owns its formatted text (so a sink may
safely hold onto it) and carries the source location plus a reserved slot for
structured key/values we may add later. The interface spells out two rules for
implementations: never call back into logging from inside Log/Flush, and keep
level() a true lower bound for ShouldLog.

There is also one default logger per process, designed to be cheap to reach on
the logging path: a lock-free atomic level check drops disabled messages
outright, and when a message does need logging the current logger comes from a
thread-local cache that only refreshes when the default actually changes, so the
steady state has no lock or reference-count traffic. SetDefaultLogger and
SetDefaultLevel swap it safely under a mutex. The seed default is the no-op
logger; CerrLogger and the spdlog backend arrive in later PRs.

Also adds the build-generated, .cc-only config header used later to pick the
backend, and tests for the interface and the default-logger lifecycle.

Co-authored-by: Isaac
Third of the logging stack. This is the first concrete backend, and the default
sink when spdlog is not compiled in.

CerrLogger writes one line per record to std::cerr, for example:

  2026-06-10T18:06:17.518Z error [295021] table.cc:42] opened snapshot 7

a UTC ISO-8601 timestamp with millisecond precision, the level, the OS thread
id, the source file:line, then the message. The level threshold is a lock-free
atomic, and a mutex guards each whole-line write so messages from different
threads never interleave. Log and Flush wrap their work in try/catch so a logging
call never throws, honoring the interface's noexcept contract.

Being pure standard library, it is always compiled and becomes the seed default
logger, replacing the no-op placeholder from the previous PR. Adds tests for the
line format, level filtering, and concurrent writes.

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