Skip to content

feat(logging): add spdlog backend behind ICEBERG_SPDLOG (5/6) - #726

Open
kamcheungting-db wants to merge 1 commit into
apache:mainfrom
kamcheungting-db:logging-block5-spdlog
Open

feat(logging): add spdlog backend behind ICEBERG_SPDLOG (5/6)#726
kamcheungting-db wants to merge 1 commit into
apache:mainfrom
kamcheungting-db:logging-block5-spdlog

Conversation

@kamcheungting-db

@kamcheungting-dbkamcheungting-db commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Part 5 of the logging stack (builds on #725). Adds the spdlog backend and the build option that selects it — the default in production builds.

What's here

  • SpdLogger wraps spdlog::logger (synchronous), maps levels (fatal/critical → spdlog critical; the abort stays in the macro layer), and forwards source location. Default sink is a colored stderr sink.
  • New ICEBERG_SPDLOG CMake option (ON by default). When OFF, the build has no spdlog dependency at all and CerrLogger is the default.
  • pattern property is honored here via spdlog set_pattern (the cerr backend keeps its fixed layout); level works on both backends.
  • SpdLogger lives in logging/internal/ and is not installed — apps get it via the default logger or the "spdlog" registry type.

Testsspdlog_logger_test (compiled only when spdlog is ON): level mapping incl. fatal→critical, source-location forwarding, and the pattern property. clang/libc++ with spdlog 1.15.3.

This pull request and its description were written by Isaac.

@kamcheungting-dbkamcheungting-db changed the title feat: [Iceberg Logger] [Part-5] spdlog backend (ICEBERG_SPDLOG)feat(logging): add spdlog backend behind ICEBERG_SPDLOGJun 11, 2026
@kamcheungting-dbkamcheungting-db changed the title feat(logging): add spdlog backend behind ICEBERG_SPDLOGfeat(logging): add spdlog backend behind ICEBERG_SPDLOG (5/6)Jun 11, 2026
@kamcheungting-db
kamcheungting-dbforce-pushed the logging-block5-spdlog branch 2 times, most recently from c6831cb to 2b80ee1CompareJune 14, 2026 06:16
@kamcheungting-db
kamcheungting-dbforce-pushed the logging-block5-spdlog branch 14 times, most recently from dd50f86 to 17a616eCompareJune 22, 2026 10:24
@kamcheungting-db
kamcheungting-dbforce-pushed the logging-block5-spdlog branch 6 times, most recently from 7e81c6d to 2684d7cCompareJune 24, 2026 18:39
@kamcheungting-db
kamcheungting-dbforce-pushed the logging-block5-spdlog branch 2 times, most recently from 289b0fb to ef127cdCompareJune 30, 2026 20:36
@kamcheungting-db
kamcheungting-dbforce-pushed the logging-block5-spdlog branch 2 times, most recently from b337dd0 to 56fbf5eCompareJuly 16, 2026 06:30
@kamcheungting-db
kamcheungting-dbforce-pushed the logging-block5-spdlog branch 3 times, most recently from db413fa to fd92bc2CompareJuly 16, 2026 07:20
@manuzhang
manuzhang requested a review from CopilotJuly 20, 2026 07:27

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a selectable spdlog-backed logging sink to Iceberg C++ (guarded by ICEBERG_SPDLOG in CMake), introduces the public logging macro headers (log_macros.h / short_log_macros.h), and extends the test/build wiring to cover macro behavior and the spdlog backend.

Changes:

  • Add internal::SpdLogger (spdlog backend) and route the process default logger to it when ICEBERG_HAS_SPDLOG is enabled.
  • Introduce installed logging macro headers (ICEBERG_LOG_*, plus opt-in bare LOG_* aliases) and MSVC preprocessor flags needed for __VA_OPT__.
  • Add unit tests for logging macros and the spdlog backend; update CMake/Meson build definitions accordingly.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 7 comments.

Show a summary per file
FileDescription
src/iceberg/test/spdlog_logger_test.ccAdds unit tests for the spdlog-backed logger behavior.
src/iceberg/test/meson.buildIncludes the new logging-related tests in the Meson test target.
src/iceberg/test/macros_test.ccAdds tests for formatting, level gating, runtime-format safety, and fatal-abort behavior in macros.
src/iceberg/test/macros_active_level_test.ccAdds tests for compile-time log stripping via ICEBERG_LOG_ACTIVE_LEVEL.
src/iceberg/test/CMakeLists.txtWires the new tests into CMake and adds MSVC /Zc:preprocessor for test compilation.
src/iceberg/meson.buildCompiles the spdlog backend source in the Meson build.
src/iceberg/logging/short_log_macros.hAdds opt-in bare LOG_* aliases for the Iceberg-prefixed macros.
src/iceberg/logging/meson.buildGenerates logging/config.h and installs the new public logging headers in Meson.
src/iceberg/logging/logger.hExtends the logging API docs and adds the FatalHandler hook API.
src/iceberg/logging/logger.ccSelects spdlog vs CerrLogger as the default logger based on ICEBERG_HAS_SPDLOG and implements fatal-handler storage.
src/iceberg/logging/log_macros.hIntroduces the public logging macros and internal helpers used by those macros.
src/iceberg/logging/internal/spdlog_logger.hDeclares the internal spdlog-backed SpdLogger sink (not installed).
src/iceberg/logging/internal/spdlog_logger.ccImplements the spdlog-backed sink (pattern support, level mapping, forwarding).
src/iceberg/logging/config.h.inAdds the build-generated backend selection header template (ICEBERG_HAS_SPDLOG).
src/iceberg/CMakeLists.txtGenerates iceberg/logging/config.h, gates spdlog linkage/compilation behind ICEBERG_SPDLOG, and exports /Zc:preprocessor on MSVC.
meson.buildAdds /Zc:preprocessor to MSVC project arguments for Meson builds.
CMakeLists.txtIntroduces the ICEBERG_SPDLOG option (default ON).
cmake_modules/IcebergThirdpartyToolchain.cmakeAvoids resolving the spdlog dependency when ICEBERG_SPDLOG is OFF.

Comment on lines +99 to +108
template <typename MakeMessage>
void LogToExplicitRuntime(Logger& logger, LogLevel level,
const std::source_location& location,
MakeMessage&& make_message) noexcept {
EmitIfEnabled(logger, level, location, std::forward<MakeMessage>(make_message));
if (level == LogLevel::kFatal) {
logger.Flush();
std::abort();
}
}
Comment on lines +65 to +72
Status SpdLogger::Initialize(
const std::unordered_map<std::string, std::string>& properties) {
if (auto it = properties.find(std::string(kPatternProperty)); it != properties.end()) {
logger_->set_pattern(it->second);
}
// Apply "level" via the base implementation.
return Logger::Initialize(properties);
}
Comment on lines +81 to +92
void SpdLogger::Log(LogMessage&& message) noexcept {
try {
spdlog::source_loc loc{message.location.file_name(),
static_cast<int>(message.location.line()),
message.location.function_name()};
// Pass the pre-formatted text as an argument ("{}") so any braces in the
// message are not re-interpreted as a format string.
logger_->log(loc, ToSpdLevel(message.level), "{}", message.message);
} catch (...) {
// Logging must never throw.
}
}
Comment on lines +94 to +99
void SpdLogger::Flush() noexcept {
try {
logger_->flush();
} catch (...) {
}
}
Comment on lines +25 to +31
/// INTERNAL, NOT INSTALLED. It is only included from .cc files (logger.cc and
/// spdlog_logger.cc) after config.h, and only when the project is built with
/// ICEBERG_SPDLOG=ON. SpdLogger is not a consumer-constructible public type --
/// applications obtain it via the default logger or the "logger-impl"="spdlog"
/// registry factory.

#include "iceberg/logging/config.h"
Comment on lines +21 to +30
# Generate the logging backend config header. ALWAYS generated (not gated by
# ICEBERG_SPDLOG) so logging/logger.cc can include it in both ON and OFF builds;
# only the definedness of ICEBERG_HAS_SPDLOG varies. Generated into the build
# tree (already on ICEBERG_INCLUDES), included as "iceberg/logging/config.h", and
# NOT installed (it must never appear in a public/installed header).
if(ICEBERG_SPDLOG)
set(ICEBERG_HAS_SPDLOG ON)
endif()
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/logging/config.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/logging/config.h")
Comment on lines +84 to +95
template <typename MakeMessage>
void LogToCurrentRuntime(LogLevel level, const std::source_location& location,
MakeMessage&& make_message) noexcept {
const std::shared_ptr<Logger>& logger = CurrentLogger();
if (logger) {
EmitIfEnabled(*logger, level, location, std::forward<MakeMessage>(make_message));
}
if (level == LogLevel::kFatal) {
if (logger) logger->Flush();
std::abort();
}
}
@manuzhangmanuzhang added this to the 0.4.0 milestone Jul 21, 2026
@wgtmac

Copy link
Copy Markdown
Member

It's time to rebase it :)

kamcheungting-db added a commit to kamcheungting-db/iceberg-cpp that referenced this pull request Jul 30, 2026
The FatalHandler only ran for fixed ICEBERG_LOG_FATAL; reaching kFatal via the
runtime-level ICEBERG_LOG(kFatal, ...) or ICEBERG_LOG_TO(sink, kFatal, ...) aborted
without invoking it (and only formatted when ShouldLog passed). Extract the fatal
sequence into a shared DispatchFatal (format once -> emit-if-enabled -> flush ->
run handler -> abort) and route LogFatal, LogToCurrentRuntime, and
LogToExplicitRuntime through it. Adds death tests for the two runtime paths.
Co-authored-by: Isaac
message.location.function_name()};
// Pass the pre-formatted text as an argument ("{}") so any braces in the
// message are not re-interpreted as a format string.
logger_->log(loc, ToSpdLevel(message.level), "{}", message.message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-formats an already formatted message through fmt for every emitted record, adding another full copy and possibly an allocation for long messages. Please call the raw-message overload with spdlog::string_view_t{message.message.data(), message.message.size()} instead.

// logger_ is a hard precondition: SpdLogger is not consumer-constructible (it is
// obtained via the default logger or the "spdlog" registry factory, both of which
// pass a real logger), so Initialize/Log/Flush may dereference it unconditionally.
ICEBERG_DCHECK(logger_ != nullptr, "SpdLogger requires a non-null spdlog::logger");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ICEBERG_DCHECK disappears under NDEBUG, and the constructor dereferences logger_ immediately afterwards. Since shared_ptr is nullable and the header only documents the synchronous requirement, an empty input becomes a release crash. Please reject null in a factory or encode the non-null requirement in the API.

Comment threadsrc/iceberg/logging/meson.build Outdated
# build/src/iceberg/logging/config.h (resolved via include_directories('..'),
# which exposes both the source and build trees); not installed.
logging_config_data = configuration_data()
logging_config_data.set('ICEBERG_HAS_SPDLOG', 1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CMake supports ICEBERG_SPDLOG=OFF, but Meson always enables this backend and unconditionally links spdlog. Please add a matching Meson feature option so the no-spdlog/Cerr configuration is available in both supported build systems.

EXPECT_TRUE(logger.ShouldLog(LogLevel::kError));
}

TEST(SpdLoggerTest, ForwardsMessageToSink) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only verifies the payload, so the advertised source-location forwarding is untested. Please use a %s:%# %! %v pattern and assert the file, line, and function fields.

Fifth block: the default production backend and the build option that selects it.
- SpdLogger wraps spdlog::logger (kCritical/kFatal -> spdlog critical, others 1:1),
forwarding the pre-formatted message and source location. Synchronous only in
v1 (spdlog's source_loc is a non-owning const char*, unsafe with async sinks).
It lives in logging/internal/, is gated by #ifdef ICEBERG_HAS_SPDLOG, and is
NOT installed -- consumers obtain it via the default logger or the registry,
never by including spdlog headers.
- New ICEBERG_SPDLOG CMake option (default ON). config.h is ALWAYS generated
(only ICEBERG_HAS_SPDLOG's definedness varies) so logger.cc compiles in both
configurations; MakeDefaultLogger() prefers SpdLogger when compiled in, else
CerrLogger.
- Critically, ICEBERG_SPDLOG=OFF now UNWIRES the previously-unconditional spdlog
link (interface-lib lists + resolve_spdlog_dependency), not just the new
source -- so an OFF build has no spdlog dependency at all.
spdlog_logger_test (compiled only on the ON path) covers the level mapping
including fatal->critical and source-location forwarding.
Co-authored-by: Isaac
Sign up for freeto 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.

4 participants

@kamcheungting-db@wgtmac@manuzhang