|
| 1 | +/* |
| 2 | + * Licensed to the Apache Software Foundation (ASF) under one |
| 3 | + * or more contributor license agreements. See the NOTICE file |
| 4 | + * distributed with this work for additional information |
| 5 | + * regarding copyright ownership. The ASF licenses this file |
| 6 | + * to you under the Apache License, Version 2.0 (the |
| 7 | + * "License"); you may not use this file except in compliance |
| 8 | + * with the License. You may obtain a copy of the License at |
| 9 | + * |
| 10 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | + * |
| 12 | + * Unless required by applicable law or agreed to in writing, |
| 13 | + * software distributed under the License is distributed on an |
| 14 | + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | + * KIND, either express or implied. See the License for the |
| 16 | + * specific language governing permissions and limitations |
| 17 | + * under the License. |
| 18 | + */ |
| 19 | + |
| 20 | +#pragma once |
| 21 | + |
| 22 | +/// \file iceberg/logging/log_macros.h |
| 23 | +/// \brief Iceberg-prefixed logging macros (ICEBERG_LOG_*). |
| 24 | +/// |
| 25 | +/// Kept out of logger.h so consumers of the C++ logging API (Logger, Log(), |
| 26 | +/// ScopedLogger) are not forced to pull in these macros or the conforming |
| 27 | +/// preprocessor they require. Include this header to use the macros; include |
| 28 | +/// short_log_macros.h for the bare LOG_* aliases. |
| 29 | + |
| 30 | +#include <cstdlib> |
| 31 | +#include <format> |
| 32 | +#include <memory> |
| 33 | +#include <source_location> |
| 34 | +#include <string> |
| 35 | +#include <string_view> |
| 36 | +#include <utility> |
| 37 | + |
| 38 | +#include "iceberg/logging/log_level.h" |
| 39 | +#include "iceberg/logging/logger.h" |
| 40 | + |
| 41 | +namespace iceberg::internal { |
| 42 | + |
| 43 | +/// \brief Runtime (non-literal) format-string helper for ICEBERG_LOG_RUNTIME_FMT. |
| 44 | +/// |
| 45 | +/// std::format requires a compile-time format string; this routes a runtime |
| 46 | +/// string through std::vformat. Args are bound as named lvalues and the |
| 47 | +/// arg-store is held in a named variable so it outlives the vformat call |
| 48 | +/// (C++23 make_format_args rejects rvalues -- P2905 / LWG3631). |
| 49 | +template <typename... Args> |
| 50 | +std::string VFormat(std::string_view fmt, Args&&... args) { |
| 51 | + auto store = std::make_format_args(args...); |
| 52 | + return std::vformat(fmt, store); |
| 53 | +} |
| 54 | + |
| 55 | +/// \brief Gate on \p logger.ShouldLog, then format (via \p make_message) and emit. |
| 56 | +/// |
| 57 | +/// \p make_message is a callable returning the formatted std::string; it is |
| 58 | +/// invoked only after the level passes ShouldLog, so a disabled log never |
| 59 | +/// evaluates its format arguments. Never throws: a std::formatter that throws |
| 60 | +/// (any type) routes to EmitFormatError, so the noexcept logging contract holds. |
| 61 | +template <typename MakeMessage> |
| 62 | +void EmitIfEnabled(Logger& logger, LogLevel level, const std::source_location& location, |
| 63 | + MakeMessage&& make_message) noexcept { |
| 64 | + if (!logger.ShouldLog(level)) return; |
| 65 | + try { |
| 66 | + Emit(logger, level, location, std::forward<MakeMessage>(make_message)()); |
| 67 | + } catch (...) { |
| 68 | + EmitFormatError(logger, level, location); |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +/// \brief Emit to the current (scoped-or-default) logger if enabled. |
| 73 | +template <typename MakeMessage> |
| 74 | +void LogToCurrent(LogLevel level, const std::source_location& location, |
| 75 | + MakeMessage&& make_message) noexcept { |
| 76 | + const std::shared_ptr<Logger>& logger = CurrentLogger(); |
| 77 | + if (logger) { |
| 78 | + EmitIfEnabled(*logger, level, location, std::forward<MakeMessage>(make_message)); |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +/// \brief Runtime-level variant against the current logger: emit if enabled, then |
| 83 | +/// flush + abort when level == kFatal (using the same acquired logger). |
| 84 | +template <typename MakeMessage> |
| 85 | +void LogToCurrentRuntime(LogLevel level, const std::source_location& location, |
| 86 | + MakeMessage&& make_message) noexcept { |
| 87 | + const std::shared_ptr<Logger>& logger = CurrentLogger(); |
| 88 | + if (logger) { |
| 89 | + EmitIfEnabled(*logger, level, location, std::forward<MakeMessage>(make_message)); |
| 90 | + } |
| 91 | + if (level == LogLevel::kFatal) { |
| 92 | + if (logger) logger->Flush(); |
| 93 | + std::abort(); |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +/// \brief Runtime-level variant against an explicit logger: emit if enabled, then |
| 98 | +/// flush + abort when level == kFatal. |
| 99 | +template <typename MakeMessage> |
| 100 | +void LogToExplicitRuntime(Logger& logger, LogLevel level, |
| 101 | + const std::source_location& location, |
| 102 | + MakeMessage&& make_message) noexcept { |
| 103 | + EmitIfEnabled(logger, level, location, std::forward<MakeMessage>(make_message)); |
| 104 | + if (level == LogLevel::kFatal) { |
| 105 | + logger.Flush(); |
| 106 | + std::abort(); |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +/// \brief Fatal path: acquire the effective (scoped-or-default) logger ONCE, emit |
| 111 | +/// if enabled, flush that same logger, then abort. Never returns. |
| 112 | +template <typename MakeMessage> |
| 113 | +[[noreturn]] void LogFatal(const std::source_location& location, |
| 114 | + MakeMessage&& make_message) noexcept { |
| 115 | + auto logger = GetCurrentLogger(); |
| 116 | + if (logger) { |
| 117 | + EmitIfEnabled(*logger, LogLevel::kFatal, location, |
| 118 | + std::forward<MakeMessage>(make_message)); |
| 119 | + logger->Flush(); |
| 120 | + } |
| 121 | + std::abort(); |
| 122 | +} |
| 123 | + |
| 124 | +} // namespace iceberg::internal |
| 125 | + |
| 126 | +// --------------------------------------------------------------------------- |
| 127 | +// Logging macros. |
| 128 | +// |
| 129 | +// Every macro takes a std::format string followed by its arguments. The |
| 130 | +// rendered line depends on the active backend (see cerr_logger.h for the |
| 131 | +// std::cerr layout, or the spdlog pattern); the examples below show the call |
| 132 | +// site and, for the default CerrLogger, the line it produces. |
| 133 | +// |
| 134 | +// ICEBERG_LOG_TRACE("entering scan for {}", table); |
| 135 | +// 2026-06-16T10:59:41.186Z trace [12345] [table_scan.cc:88] entering scan for db.t |
| 136 | +// ICEBERG_LOG_DEBUG("cache miss key={}", key); |
| 137 | +// 2026-06-16T10:59:41.186Z debug [12345] [cache.cc:42] cache miss key=manifest-7 |
| 138 | +// ICEBERG_LOG_INFO("loaded {} manifests in {} ms", n, ms); |
| 139 | +// 2026-06-16T10:59:41.186Z info [12345] [table_scan.cc:91] loaded 5 manifests in 12 |
| 140 | +// ms |
| 141 | +// ICEBERG_LOG_WARN("retry {} after {}", attempt, err); |
| 142 | +// 2026-06-16T10:59:41.186Z warn [12345] [io.cc:51] retry 2 after timeout |
| 143 | +// ICEBERG_LOG_ERROR("commit failed: {}", status); |
| 144 | +// 2026-06-16T10:59:41.186Z error [12345] [txn.cc:77] commit failed: conflict |
| 145 | +// ICEBERG_LOG_CRITICAL("metadata unreadable at {}", path); |
| 146 | +// 2026-06-16T10:59:41.186Z critical [12345] [meta.cc:30] metadata unreadable at |
| 147 | +// s3://b/m.json |
| 148 | +// ICEBERG_LOG_FATAL("unrecoverable: {}", reason); // emits, flushes, then |
| 149 | +// std::abort() |
| 150 | +// 2026-06-16T10:59:41.186Z fatal [12345] [boot.cc:19] unrecoverable: bad config |
| 151 | +// |
| 152 | +// Less common forms: |
| 153 | +// ICEBERG_LOG(level, "level chosen at runtime: {}", x); // runtime severity |
| 154 | +// ICEBERG_LOG_TO(logger, level, "to an explicit logger {}", y); |
| 155 | +// ICEBERG_LOG_RUNTIME_FMT(level, fmt_string, args...); // non-literal format |
| 156 | +// |
| 157 | +// Include short_log_macros.h for bare aliases (LOG_INFO, ...). A format string is |
| 158 | +// mandatory; zero extra args is fine (ICEBERG_LOG_INFO("done")). |
| 159 | +// --------------------------------------------------------------------------- |
| 160 | + |
| 161 | +/// \brief Compile-time severity floor: statements below this level are discarded |
| 162 | +/// via `if constexpr`, so no emit code runs and no format call / source_location |
| 163 | +/// is generated for them (the compiler is free to optimize the dead branch away). |
| 164 | +/// The statement must still be well-formed -- a bad format string or a |
| 165 | +/// non-formattable argument is a compile error even when the branch is discarded. |
| 166 | +/// Defaults to keeping everything. ICEBERG_LOG_FATAL is never gated by this floor |
| 167 | +/// -- its abort is always compiled in. |
| 168 | +#ifndef ICEBERG_LOG_ACTIVE_LEVEL |
| 169 | +# define ICEBERG_LOG_ACTIVE_LEVEL ::iceberg::LogLevel::kTrace |
| 170 | +#endif |
| 171 | + |
| 172 | +// A message-builder lambda that formats lazily (only invoked past ShouldLog by the |
| 173 | +// EmitIfEnabled helpers), so disabled logs never evaluate their arguments. |
| 174 | +#define ICEBERG_INTERNAL_LOG_MESSAGE(FMT_, ...) \ |
| 175 | + [&]() -> ::std::string { return ::std::format((FMT_)__VA_OPT__(, ) __VA_ARGS__); } |
| 176 | + |
| 177 | +// Fixed-severity emit with a compile-time floor (`if constexpr`) then the shared |
| 178 | +// current-logger path. Formatting happens only on the taken path and never throws. |
| 179 | +#define ICEBERG_INTERNAL_LOG(level_, FMT_, ...) \ |
| 180 | + do { \ |
| 181 | + if constexpr ((level_) >= ICEBERG_LOG_ACTIVE_LEVEL) { \ |
| 182 | + ::iceberg::internal::LogToCurrent( \ |
| 183 | + (level_), ::std::source_location::current(), \ |
| 184 | + ICEBERG_INTERNAL_LOG_MESSAGE(FMT_ __VA_OPT__(, ) __VA_ARGS__)); \ |
| 185 | + } \ |
| 186 | + } while (0) |
| 187 | + |
| 188 | +#define ICEBERG_LOG_TRACE(...) \ |
| 189 | + ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kTrace, __VA_ARGS__) |
| 190 | +#define ICEBERG_LOG_DEBUG(...) \ |
| 191 | + ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kDebug, __VA_ARGS__) |
| 192 | +#define ICEBERG_LOG_INFO(...) \ |
| 193 | + ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kInfo, __VA_ARGS__) |
| 194 | +#define ICEBERG_LOG_WARN(...) \ |
| 195 | + ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kWarn, __VA_ARGS__) |
| 196 | +#define ICEBERG_LOG_ERROR(...) \ |
| 197 | + ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kError, __VA_ARGS__) |
| 198 | +#define ICEBERG_LOG_CRITICAL(...) \ |
| 199 | + ICEBERG_INTERNAL_LOG(::iceberg::LogLevel::kCritical, __VA_ARGS__) |
| 200 | + |
| 201 | +// FATAL: emit if enabled (never compile-stripped), then ALWAYS flush + abort. |
| 202 | +// Acquires the effective (scoped-or-default) logger ONCE so a concurrent |
| 203 | +// SetDefaultLogger cannot flush a different logger than it emitted to. |
| 204 | +#define ICEBERG_LOG_FATAL(FMT_, ...) \ |
| 205 | + ::iceberg::internal::LogFatal( \ |
| 206 | + ::std::source_location::current(), \ |
| 207 | + ICEBERG_INTERNAL_LOG_MESSAGE(FMT_ __VA_OPT__(, ) __VA_ARGS__)) |
| 208 | + |
| 209 | +// Generic, runtime-level form against the default logger. No compile-time floor |
| 210 | +// (the level is not a constant). Aborts when level == kFatal. |
| 211 | +#define ICEBERG_LOG(level_, FMT_, ...) \ |
| 212 | + ::iceberg::internal::LogToCurrentRuntime( \ |
| 213 | + (level_), ::std::source_location::current(), \ |
| 214 | + ICEBERG_INTERNAL_LOG_MESSAGE(FMT_ __VA_OPT__(, ) __VA_ARGS__)) |
| 215 | + |
| 216 | +// Generic form targeting an EXPLICIT logger (must be an lvalue Logger&). Honors |
| 217 | +// only that logger's ShouldLog. Aborts when level == kFatal. |
| 218 | +#define ICEBERG_LOG_TO(logger_, level_, FMT_, ...) \ |
| 219 | + ::iceberg::internal::LogToExplicitRuntime( \ |
| 220 | + (logger_), (level_), ::std::source_location::current(), \ |
| 221 | + ICEBERG_INTERNAL_LOG_MESSAGE(FMT_ __VA_OPT__(, ) __VA_ARGS__)) |
| 222 | + |
| 223 | +// Runtime (non-literal) format string against the default logger. Aborts when |
| 224 | +// level == kFatal. |
| 225 | +#define ICEBERG_LOG_RUNTIME_FMT(level_, FMT_, ...) \ |
| 226 | + ::iceberg::internal::LogToCurrentRuntime( \ |
| 227 | + (level_), ::std::source_location::current(), [&]() -> ::std::string { \ |
| 228 | + return ::iceberg::internal::VFormat((FMT_)__VA_OPT__(, ) __VA_ARGS__); \ |
| 229 | + }) |
0 commit comments