blob: 0dc510f0f103028d5ef556e76505861281053334 [file]
/*
* 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__); \
})