| /* |
| * 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 |
| |
| /// \file iceberg/logging/log_macros.h |
| /// \brief Iceberg-prefixed logging macros (ICEBERG_LOG_*). |
| /// |
| /// Kept out of logger.h so consumers of the C++ logging API (Logger, Log(), |
| /// ScopedLogger) are not forced to pull in these macros or the conforming |
| /// preprocessor they require. Include this header to use the macros; include |
| /// short_log_macros.h for the bare LOG_* aliases. |
| |
| #include <cstdlib> |
| #include <format> |
| #include <memory> |
| #include <source_location> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include "iceberg/logging/log_level.h" |
| #include "iceberg/logging/logger.h" |
| |
| namespace iceberg::internal { |
| |
| /// \brief Runtime (non-literal) format-string helper for ICEBERG_LOG_RUNTIME_FMT. |
| /// |
| /// std::format requires a compile-time format string; this routes a runtime |
| /// string through std::vformat. Args are bound as named lvalues and the |
| /// arg-store is held in a named variable so it outlives the vformat call |
| /// (C++23 make_format_args rejects rvalues -- P2905 / LWG3631). |
| template <typename... Args> |
| std::string VFormat(std::string_view fmt, Args&&... args) { |
| auto store = std::make_format_args(args...); |
| return std::vformat(fmt, store); |
| } |
| |
| /// \brief Gate on \p logger.ShouldLog, then format (via \p make_message) and emit. |
| /// |
| /// \p make_message is a callable returning the formatted std::string; it is |
| /// invoked only after the level passes ShouldLog, so a disabled log never |
| /// evaluates its format arguments. Never throws: a std::formatter that throws |
| /// (any type) routes to EmitFormatError, so the noexcept logging contract holds. |
| template <typename MakeMessage> |
| void EmitIfEnabled(Logger& logger, LogLevel level, const std::source_location& location, |
| MakeMessage&& make_message) noexcept { |
| if (!logger.ShouldLog(level)) return; |
| try { |
| Emit(logger, level, location, std::forward<MakeMessage>(make_message)()); |
| } catch (...) { |
| EmitFormatError(logger, level, location); |
| } |
| } |
| |
| /// \brief Emit to the current (scoped-or-default) logger if enabled. |
| template <typename MakeMessage> |
| void LogToCurrent(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)); |
| } |
| } |
| |
| /// \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)); |
| } |
| } |
| |
| template <typename MakeMessage> |
| void LogToExplicitRuntime(Logger& logger, LogLevel level, |
| const std::source_location& location, |
| MakeMessage&& make_message) noexcept { |
| if (level == LogLevel::kFatal) { |
| DispatchFatal(&logger, location, std::forward<MakeMessage>(make_message)); |
| } |
| EmitIfEnabled(logger, level, location, std::forward<MakeMessage>(make_message)); |
| } |
| |
| template <typename MakeMessage> |
| [[noreturn]] void LogFatal(const std::source_location& location, |
| MakeMessage&& make_message) noexcept { |
| auto logger = GetCurrentLogger(); |
| DispatchFatal(logger.get(), location, std::forward<MakeMessage>(make_message)); |
| } |
| |
| } // namespace iceberg::internal |
| |
| // --------------------------------------------------------------------------- |
| // Logging macros. |
| // |
| // Every macro takes a std::format string followed by its arguments. The |
| // rendered line depends on the active backend (see cerr_logger.h for the |
| // std::cerr layout, or the spdlog pattern); the examples below show the call |
| // site and, for the default CerrLogger, the line it produces. |
| // |
| // ICEBERG_LOG_TRACE("entering scan for {}", table); |
| // 2026-06-16T10:59:41.186Z trace [12345] [table_scan.cc:88] entering scan for db.t |
| // ICEBERG_LOG_DEBUG("cache miss key={}", key); |
| // 2026-06-16T10:59:41.186Z debug [12345] [cache.cc:42] cache miss key=manifest-7 |
| // ICEBERG_LOG_INFO("loaded {} manifests in {} ms", n, ms); |
| // 2026-06-16T10:59:41.186Z info [12345] [table_scan.cc:91] loaded 5 manifests in 12 |
| // ms |
| // ICEBERG_LOG_WARN("retry {} after {}", attempt, err); |
| // 2026-06-16T10:59:41.186Z warn [12345] [io.cc:51] retry 2 after timeout |
| // ICEBERG_LOG_ERROR("commit failed: {}", status); |
| // 2026-06-16T10:59:41.186Z error [12345] [txn.cc:77] commit failed: conflict |
| // ICEBERG_LOG_CRITICAL("metadata unreadable at {}", path); |
| // 2026-06-16T10:59:41.186Z critical [12345] [meta.cc:30] metadata unreadable at |
| // s3://b/m.json |
| // ICEBERG_LOG_FATAL("unrecoverable: {}", reason); // emits, flushes, then |
| // std::abort() |
| // 2026-06-16T10:59:41.186Z fatal [12345] [boot.cc:19] unrecoverable: bad config |
| // |
| // Less common forms: |
| // ICEBERG_LOG(level, "level chosen at runtime: {}", x); // runtime severity |
| // ICEBERG_LOG_TO(logger, level, "to an explicit logger {}", y); |
| // ICEBERG_LOG_RUNTIME_FMT(level, fmt_string, args...); // non-literal format |
| // |
| // Include short_log_macros.h for bare aliases (LOG_INFO, ...). A format string is |
| // mandatory; zero extra args is fine (ICEBERG_LOG_INFO("done")). |
| // --------------------------------------------------------------------------- |
| |
| /// \brief Compile-time severity floor: statements below this level are discarded |
| /// via `if constexpr`, so no emit code runs and no format call / source_location |
| /// is generated for them (the compiler is free to optimize the dead branch away). |
| /// The statement must still be well-formed -- a bad format string or a |
| /// non-formattable argument is a compile error even when the branch is discarded. |
| /// Defaults to keeping everything. ICEBERG_LOG_FATAL is never gated by this floor |
| /// -- its abort is always compiled in. |
| #ifndef ICEBERG_LOG_ACTIVE_LEVEL |
| # define ICEBERG_LOG_ACTIVE_LEVEL ::iceberg::LogLevel::kTrace |
| #endif |
| |
| // A message-builder lambda that formats lazily (only invoked past ShouldLog by the |
| // EmitIfEnabled helpers), so disabled logs never evaluate their arguments. |
| #define ICEBERG_INTERNAL_LOG_MESSAGE(FMT_, ...) \ |
| [&]() -> ::std::string { return ::std::format((FMT_)__VA_OPT__(, ) __VA_ARGS__); } |
| |
| // Fixed-severity emit with a compile-time floor (`if constexpr`) then the shared |
| // current-logger path. Formatting happens only on the taken path and never throws. |
| #define ICEBERG_INTERNAL_LOG(level_, FMT_, ...) \ |
| do { \ |
| if constexpr ((level_) >= ICEBERG_LOG_ACTIVE_LEVEL) { \ |
| ::iceberg::internal::LogToCurrent( \ |
| (level_), ::std::source_location::current(), \ |
| ICEBERG_INTERNAL_LOG_MESSAGE(FMT_ __VA_OPT__(, ) __VA_ARGS__)); \ |
| } \ |
| } while (0) |
| |
| #define ICEBERG_LOG_TRACE(...) \ |
| ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kTrace, __VA_ARGS__) |
| #define ICEBERG_LOG_DEBUG(...) \ |
| ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kDebug, __VA_ARGS__) |
| #define ICEBERG_LOG_INFO(...) \ |
| ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kInfo, __VA_ARGS__) |
| #define ICEBERG_LOG_WARN(...) \ |
| ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kWarn, __VA_ARGS__) |
| #define ICEBERG_LOG_ERROR(...) \ |
| ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kError, __VA_ARGS__) |
| #define ICEBERG_LOG_CRITICAL(...) \ |
| ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kCritical, __VA_ARGS__) |
| |
| // FATAL: emit if enabled (never compile-stripped), then ALWAYS flush + abort. |
| // Acquires the effective (scoped-or-default) logger ONCE so a concurrent |
| // SetDefaultLogger cannot flush a different logger than it emitted to. |
| #define ICEBERG_LOG_FATAL(FMT_, ...) \ |
| ::iceberg::internal::LogFatal( \ |
| ::std::source_location::current(), \ |
| ICEBERG_INTERNAL_LOG_MESSAGE(FMT_ __VA_OPT__(, ) __VA_ARGS__)) |
| |
| // Generic, runtime-level form against the default logger. No compile-time floor |
| // (the level is not a constant). Aborts when level == kFatal. |
| #define ICEBERG_LOG(level_, FMT_, ...) \ |
| ::iceberg::internal::LogToCurrentRuntime( \ |
| (level_), ::std::source_location::current(), \ |
| ICEBERG_INTERNAL_LOG_MESSAGE(FMT_ __VA_OPT__(, ) __VA_ARGS__)) |
| |
| // Generic form targeting an EXPLICIT logger (must be an lvalue Logger&). Honors |
| // only that logger's ShouldLog. Aborts when level == kFatal. |
| #define ICEBERG_LOG_TO(logger_, level_, FMT_, ...) \ |
| ::iceberg::internal::LogToExplicitRuntime( \ |
| (logger_), (level_), ::std::source_location::current(), \ |
| ICEBERG_INTERNAL_LOG_MESSAGE(FMT_ __VA_OPT__(, ) __VA_ARGS__)) |
| |
| // Runtime (non-literal) format string against the default logger. Aborts when |
| // level == kFatal. |
| #define ICEBERG_LOG_RUNTIME_FMT(level_, FMT_, ...) \ |
| ::iceberg::internal::LogToCurrentRuntime( \ |
| (level_), ::std::source_location::current(), [&]() -> ::std::string { \ |
| return ::iceberg::internal::VFormat((FMT_)__VA_OPT__(, ) __VA_ARGS__); \ |
| }) |