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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ option(ICEBERG_S3 "Build with S3 support" OFF)
option(ICEBERG_SIGV4 "Build with SigV4 support" OFF)
option(ICEBERG_BUNDLE_AWSSDK "Bundle AWS SDK for S3/SigV4 support" ON)
option(ICEBERG_BUNDLE_THRIFT "Bundle Thrift (from Arrow) for Hive catalog" ON)
option(ICEBERG_SPDLOG "Use spdlog as the default logging backend" ON)
option(ICEBERG_ENABLE_ASAN "Enable Address Sanitizer" OFF)
option(ICEBERG_ENABLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF)

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ cmake --install build
| `ICEBERG_BUILD_BUNDLE` | `ON` | Build the battery-included library |
| `ICEBERG_BUILD_REST` | `ON` | Build REST catalog client |
| `ICEBERG_BUILD_REST_INTEGRATION_TESTS` | `OFF` | Build REST catalog integration tests |
| `ICEBERG_SPDLOG` | `ON` | Use spdlog as the default logging backend |
| `ICEBERG_BUILD_HIVE` | `OFF` | Build Hive (HMS) catalog client |
| `ICEBERG_BUILD_SQL_CATALOG` | `OFF` | Build SQL catalog client |
| `ICEBERG_SQL_SQLITE` | `OFF` | Build the SQLite connector for the SQL catalog |
Expand Down Expand Up @@ -124,6 +125,7 @@ Meson-specific options (configured via `-D<option>=<value>`):
|--------|---------|-------------|
| `rest` | `enabled` | Build REST catalog client |
| `rest_integration_test` | `disabled` | Build integration test for REST catalog |
| `spdlog` | `enabled` | Use spdlog as the default logging backend |
| `tests` | `enabled` | Build tests |

### Build Examples
Expand Down
4 changes: 3 additions & 1 deletion cmake_modules/IcebergThirdpartyToolchain.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,9 @@ resolve_nanoarrow_dependency()
resolve_croaring_dependency()
resolve_utf8proc_dependency()
resolve_nlohmann_json_dependency()
resolve_spdlog_dependency()
if(ICEBERG_SPDLOG)
resolve_spdlog_dependency()
endif()

if(ICEBERG_S3 OR ICEBERG_SIGV4)
if(ICEBERG_SIGV4 AND NOT ICEBERG_BUILD_REST)
Expand Down
7 changes: 7 additions & 0 deletions meson.options
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ option(
value: 'disabled',
)

option(
'spdlog',
type: 'feature',
description: 'Use spdlog as the default logging backend',
value: 'enabled',
)

option('tests', type: 'feature', description: 'Build tests', value: 'enabled')

option(
Expand Down
19 changes: 15 additions & 4 deletions src/iceberg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

set(ICEBERG_INCLUDES "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src>"
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>")

set(ICEBERG_SOURCES
arrow_c_data_guard_internal.cc
arrow_c_data_util.cc
Expand Down Expand Up @@ -55,6 +56,7 @@ set(ICEBERG_SOURCES
location_provider.cc
logging/cerr_logger.cc
logging/logger.cc
logging/spdlog_logger.cc
manifest/manifest_adapter.cc
manifest/manifest_entry.cc
manifest/manifest_filter_manager.cc
Expand Down Expand Up @@ -157,29 +159,38 @@ list(APPEND
ICEBERG_STATIC_BUILD_INTERFACE_LIBS
"$<IF:$<BOOL:${NANOARROW_VENDORED}>,nanoarrow::nanoarrow_static,$<IF:$<TARGET_EXISTS:nanoarrow::nanoarrow_static>,nanoarrow::nanoarrow_static,nanoarrow::nanoarrow_shared>>"
nlohmann_json::nlohmann_json
spdlog::spdlog
utf8proc::utf8proc
ZLIB::ZLIB)
list(APPEND
ICEBERG_SHARED_BUILD_INTERFACE_LIBS
"$<IF:$<BOOL:${NANOARROW_VENDORED}>,nanoarrow::nanoarrow_static,$<IF:$<TARGET_EXISTS:nanoarrow::nanoarrow_shared>,nanoarrow::nanoarrow_shared,nanoarrow::nanoarrow_static>>"
nlohmann_json::nlohmann_json
spdlog::spdlog
utf8proc::utf8proc
ZLIB::ZLIB)
list(APPEND
ICEBERG_STATIC_INSTALL_INTERFACE_LIBS
"$<IF:$<BOOL:${NANOARROW_VENDORED}>,iceberg::nanoarrow_static,$<IF:$<TARGET_EXISTS:nanoarrow::nanoarrow_static>,nanoarrow::nanoarrow_static,nanoarrow::nanoarrow_shared>>"
"$<IF:$<BOOL:${NLOHMANN_JSON_VENDORED}>,iceberg::nlohmann_json,$<IF:$<TARGET_EXISTS:nlohmann_json::nlohmann_json>,nlohmann_json::nlohmann_json,nlohmann_json::nlohmann_json>>"
"$<IF:$<BOOL:${SPDLOG_VENDORED}>,iceberg::spdlog,spdlog::spdlog>"
"$<IF:$<BOOL:${UTF8PROC_VENDORED}>,iceberg::utf8proc,utf8proc::utf8proc>")
list(APPEND
ICEBERG_SHARED_INSTALL_INTERFACE_LIBS
"$<IF:$<BOOL:${NANOARROW_VENDORED}>,iceberg::nanoarrow_static,$<IF:$<TARGET_EXISTS:nanoarrow::nanoarrow_shared>,nanoarrow::nanoarrow_shared,nanoarrow::nanoarrow_static>>"
"$<IF:$<BOOL:${NLOHMANN_JSON_VENDORED}>,iceberg::nlohmann_json,$<IF:$<TARGET_EXISTS:nlohmann_json::nlohmann_json>,nlohmann_json::nlohmann_json,nlohmann_json::nlohmann_json>>"
"$<IF:$<BOOL:${SPDLOG_VENDORED}>,iceberg::spdlog,spdlog::spdlog>"
"$<IF:$<BOOL:${UTF8PROC_VENDORED}>,iceberg::utf8proc,utf8proc::utf8proc>")

if(ICEBERG_SPDLOG)
set(ICEBERG_HAS_SPDLOG ON)
list(APPEND ICEBERG_STATIC_BUILD_INTERFACE_LIBS spdlog::spdlog)
list(APPEND ICEBERG_SHARED_BUILD_INTERFACE_LIBS spdlog::spdlog)
list(APPEND ICEBERG_STATIC_INSTALL_INTERFACE_LIBS
"$<IF:$<BOOL:${SPDLOG_VENDORED}>,iceberg::spdlog,spdlog::spdlog>")
list(APPEND ICEBERG_SHARED_INSTALL_INTERFACE_LIBS
"$<IF:$<BOOL:${SPDLOG_VENDORED}>,iceberg::spdlog,spdlog::spdlog>")
endif()

configure_file("${CMAKE_CURRENT_SOURCE_DIR}/logging/config.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/logging/config.h")

add_iceberg_lib(iceberg
SOURCES
${ICEBERG_SOURCES}
Expand Down
25 changes: 25 additions & 0 deletions src/iceberg/logging/config.h.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

#pragma once

// Internal, build-generated configuration; not installed.
// ICEBERG_HAS_SPDLOG is defined when the spdlog backend is available.

#cmakedefine ICEBERG_HAS_SPDLOG
72 changes: 34 additions & 38 deletions src/iceberg/logging/log_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,65 +79,61 @@ void LogToCurrent(LogLevel level, const std::source_location& location,
}
}

/// \brief Runtime-level variant against the current logger: emit if enabled, then
/// flush + abort when level == kFatal (using the same acquired logger).
/// \brief Format once, emit if enabled, flush, run the fatal handler, and abort.
///
/// Formatting is independent of ShouldLog so the handler always receives the
/// message. \p logger may be null.
template <typename MakeMessage>
[[noreturn]] void DispatchFatal(Logger* logger, const std::source_location& location,
MakeMessage&& make_message) noexcept {
std::string message;
try {
message = std::forward<MakeMessage>(make_message)();
} catch (...) {
message = "<fmt error>";
}
if (logger != nullptr) {
if (logger->ShouldLog(LogLevel::kFatal)) {
Emit(*logger, LogLevel::kFatal, location, std::string(message));
}
logger->Flush();
}
if (auto handler = GetFatalHandler()) {
try {
handler(location, message);
} catch (...) { // a throwing handler must not prevent the abort
}
}
std::abort();
}

template <typename MakeMessage>
void LogToCurrentRuntime(LogLevel level, const std::source_location& location,
MakeMessage&& make_message) noexcept {
const std::shared_ptr<Logger>& logger = CurrentLogger();
if (level == LogLevel::kFatal) {
DispatchFatal(logger.get(), location, std::forward<MakeMessage>(make_message));
}
if (logger) {
EmitIfEnabled(*logger, level, location, std::forward<MakeMessage>(make_message));
}
if (level == LogLevel::kFatal) {
if (logger) logger->Flush();
std::abort();
}
}

/// \brief Runtime-level variant against an explicit logger: emit if enabled, then
/// flush + abort when level == kFatal.
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();
DispatchFatal(&logger, location, std::forward<MakeMessage>(make_message));
}
EmitIfEnabled(logger, level, location, std::forward<MakeMessage>(make_message));
}

/// \brief Fatal path: acquire the effective (scoped-or-default) logger ONCE, emit
/// if enabled, flush that same logger, run any registered FatalHandler, then
/// abort. Never returns.
///
/// The message is always formatted here (independent of ShouldLog) so the handler
/// receives it even when the fatal record itself is filtered out. The handler runs
/// after emit+flush and before abort; if it does not itself terminate the process,
/// std::abort() still runs.
template <typename MakeMessage>
[[noreturn]] void LogFatal(const std::source_location& location,
MakeMessage&& make_message) noexcept {
std::string message;
try {
message = std::forward<MakeMessage>(make_message)();
} catch (...) {
message = "<fmt error>";
}
auto logger = GetCurrentLogger();
if (logger) {
if (logger->ShouldLog(LogLevel::kFatal)) {
Emit(*logger, LogLevel::kFatal, location, std::string(message));
}
logger->Flush();
}
if (auto handler = GetFatalHandler()) {
try {
handler(location, message);
} catch (...) { // a throwing handler must not prevent the abort
}
}
std::abort();
DispatchFatal(logger.get(), location, std::forward<MakeMessage>(make_message));
}

} // namespace iceberg::internal
Expand Down
14 changes: 9 additions & 5 deletions src/iceberg/logging/logger.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <utility>

#include "iceberg/logging/cerr_logger.h"
#include "iceberg/logging/spdlog_logger_internal.h"

namespace iceberg {

Expand All @@ -42,11 +43,14 @@ class NoopLogger final : public Logger {
bool IsNoop() const override { return true; }
};

/// \brief Construct the process default logger for this build configuration.
///
/// Uses the always-available std::cerr sink. The spdlog backend (preferred when
/// compiled in) is wired into this factory in a later block.
std::shared_ptr<Logger> MakeDefaultLogger() { return std::make_shared<CerrLogger>(); }
/// \brief Construct the configured process default logger.
std::shared_ptr<Logger> MakeDefaultLogger() {
#ifdef ICEBERG_HAS_SPDLOG
return std::make_shared<internal::SpdLogger>();
#else
return std::make_shared<CerrLogger>();
#endif
}

/// \brief The process-global default-logger slot.
struct DefaultSlot {
Expand Down
8 changes: 8 additions & 0 deletions src/iceberg/logging/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
# specific language governing permissions and limitations
# under the License.

# Generate the internal, non-installed logging backend config.
logging_config_data = configuration_data()
if spdlog_enabled
logging_config_data.set('ICEBERG_HAS_SPDLOG', 1)
endif
configure_file(output: 'config.h', configuration: logging_config_data)

# Install only public logging headers.
install_headers(
[
'cerr_logger.h',
Expand Down
100 changes: 100 additions & 0 deletions src/iceberg/logging/spdlog_logger.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

#include "iceberg/logging/spdlog_logger_internal.h"

#ifdef ICEBERG_HAS_SPDLOG

# include <memory>
# include <string>
# include <unordered_map>
# include <utility>

# include <spdlog/common.h>
# include <spdlog/sinks/stdout_color_sinks.h>

namespace iceberg::internal {

namespace {

spdlog::level::level_enum ToSpdLevel(LogLevel level) noexcept {
switch (level) {
case LogLevel::kTrace:
return spdlog::level::trace;
case LogLevel::kDebug:
return spdlog::level::debug;
case LogLevel::kInfo:
return spdlog::level::info;
case LogLevel::kWarn:
return spdlog::level::warn;
case LogLevel::kError:
return spdlog::level::err;
case LogLevel::kCritical:
case LogLevel::kFatal:
// spdlog has no fatal level; the macro layer handles termination.
return spdlog::level::critical;
case LogLevel::kOff:
return spdlog::level::off;
}
return spdlog::level::off;
}

} // namespace

SpdLogger::SpdLogger(LogLevel level)
: SpdLogger(spdlog::logger("iceberg",
std::make_shared<spdlog::sinks::stderr_color_sink_mt>()),
level) {}

SpdLogger::SpdLogger(spdlog::logger logger, LogLevel level)
: logger_(std::move(logger)), level_(level) {
logger_.set_level(spdlog::level::trace);
}

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);
}
return Logger::Initialize(properties);
}

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()};
// LogMessage is already formatted; use spdlog's raw-message overload.
logger_.log(loc, ToSpdLevel(message.level),
spdlog::string_view_t{message.message.data(), message.message.size()});
} catch (...) {
// Logging must never throw.
}
}

void SpdLogger::Flush() noexcept {
try {
logger_.flush();
} catch (...) {
}
}

} // namespace iceberg::internal

#endif // ICEBERG_HAS_SPDLOG
Loading
Loading