Skip to content

Commit 812817d

Browse files
committed
Prototype logging macro split
1 parent f4f6359 commit 812817d

11 files changed

Lines changed: 577 additions & 31 deletions

‎meson.build‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ project(
3131
)
3232

3333
cpp = meson.get_compiler('cpp')
34-
# Hide two noisy MSVC warnings (C4251/C4275) about standard-library types used in
35-
# our public classes. They're harmless because the whole library ships as one DLL.
36-
args = cpp.get_supported_arguments(['/bigobj', '/wd4251', '/wd4275'])
34+
# MSVC-specific options: /bigobj avoids object section limits, /wd4251 and
35+
# /wd4275 silence harmless DLL-interface warnings for standard-library members,
36+
# and /Zc:preprocessor is required by public headers that use __VA_OPT__.
37+
args = cpp.get_supported_arguments(
38+
['/bigobj', '/wd4251', '/wd4275', '/Zc:preprocessor'],
39+
)
3740
add_project_arguments(args, language: 'cpp')
3841

3942
subdir('src')

‎src/iceberg/CMakeLists.txt‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ add_iceberg_lib(iceberg
188188
OUTPUTS
189189
ICEBERG_LIBRARIES)
190190

191+
if(MSVC)
192+
foreach(ICEBERG_LOGGING_TARGET iceberg_shared iceberg_static)
193+
if(TARGET ${ICEBERG_LOGGING_TARGET})
194+
target_compile_options(${ICEBERG_LOGGING_TARGET} PUBLIC /Zc:preprocessor)
195+
endif()
196+
endforeach()
197+
endif()
198+
191199
set(ICEBERG_DATA_SOURCES
192200
data/data_writer.cc
193201
data/delete_filter.cc

‎src/iceberg/logging/log_macros.h‎

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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.
24+
25+
#include <cstdlib>
26+
#include <format>
27+
#include <memory>
28+
#include <source_location>
29+
#include <string>
30+
#include <string_view>
31+
#include <utility>
32+
33+
#include "iceberg/logging/logger.h"
34+
35+
#define ICEBERG_LOG_LEVEL_TRACE 0
36+
#define ICEBERG_LOG_LEVEL_DEBUG 1
37+
#define ICEBERG_LOG_LEVEL_INFO 2
38+
#define ICEBERG_LOG_LEVEL_WARN 3
39+
#define ICEBERG_LOG_LEVEL_ERROR 4
40+
#define ICEBERG_LOG_LEVEL_CRITICAL 5
41+
#define ICEBERG_LOG_LEVEL_FATAL 6
42+
#define ICEBERG_LOG_LEVEL_OFF 7
43+
44+
#ifndef ICEBERG_LOG_ACTIVE_LEVEL
45+
# define ICEBERG_LOG_ACTIVE_LEVEL ICEBERG_LOG_LEVEL_TRACE
46+
#endif
47+
48+
static_assert(static_cast<int>(::iceberg::LogLevel::kTrace) == ICEBERG_LOG_LEVEL_TRACE);
49+
static_assert(static_cast<int>(::iceberg::LogLevel::kDebug) == ICEBERG_LOG_LEVEL_DEBUG);
50+
static_assert(static_cast<int>(::iceberg::LogLevel::kInfo) == ICEBERG_LOG_LEVEL_INFO);
51+
static_assert(static_cast<int>(::iceberg::LogLevel::kWarn) == ICEBERG_LOG_LEVEL_WARN);
52+
static_assert(static_cast<int>(::iceberg::LogLevel::kError) == ICEBERG_LOG_LEVEL_ERROR);
53+
static_assert(static_cast<int>(::iceberg::LogLevel::kCritical) ==
54+
ICEBERG_LOG_LEVEL_CRITICAL);
55+
static_assert(static_cast<int>(::iceberg::LogLevel::kFatal) == ICEBERG_LOG_LEVEL_FATAL);
56+
static_assert(static_cast<int>(::iceberg::LogLevel::kOff) == ICEBERG_LOG_LEVEL_OFF);
57+
58+
namespace iceberg::logging::detail {
59+
60+
template <typename... Args>
61+
std::string VFormat(std::string_view fmt, Args&&... args) {
62+
auto store = std::make_format_args(args...);
63+
return std::vformat(fmt, store);
64+
}
65+
66+
template <typename FormatMessage>
67+
void EmitIfEnabled(Logger& logger, LogLevel level, const std::source_location& location,
68+
FormatMessage&& format_message) noexcept {
69+
if (level == LogLevel::kOff || !logger.ShouldLog(level)) return;
70+
71+
try {
72+
::iceberg::internal::Emit(logger, level, location,
73+
std::forward<FormatMessage>(format_message)());
74+
} catch (...) {
75+
::iceberg::internal::EmitFormatError(logger, level, location);
76+
}
77+
}
78+
79+
template <typename FormatMessage>
80+
void LogCurrentIfEnabled(LogLevel level, const std::source_location& location,
81+
FormatMessage&& format_message) noexcept {
82+
const std::shared_ptr<Logger>& logger = ::iceberg::internal::CurrentLogger();
83+
if (logger) {
84+
EmitIfEnabled(*logger, level, location, std::forward<FormatMessage>(format_message));
85+
}
86+
}
87+
88+
template <typename FormatMessage>
89+
void LogCurrentRuntime(LogLevel level, const std::source_location& location,
90+
FormatMessage&& format_message) noexcept {
91+
const std::shared_ptr<Logger>& logger = ::iceberg::internal::CurrentLogger();
92+
if (logger) {
93+
EmitIfEnabled(*logger, level, location, std::forward<FormatMessage>(format_message));
94+
}
95+
if (level == LogLevel::kFatal) {
96+
if (logger) logger->Flush();
97+
std::abort();
98+
}
99+
}
100+
101+
template <typename FormatMessage>
102+
void LogToRuntime(Logger& logger, LogLevel level, const std::source_location& location,
103+
FormatMessage&& format_message) noexcept {
104+
EmitIfEnabled(logger, level, location, std::forward<FormatMessage>(format_message));
105+
if (level == LogLevel::kFatal) {
106+
logger.Flush();
107+
std::abort();
108+
}
109+
}
110+
111+
template <typename FormatMessage>
112+
[[noreturn]] void LogCurrentFatal(const std::source_location& location,
113+
FormatMessage&& format_message) noexcept {
114+
auto logger = ::iceberg::GetCurrentLogger();
115+
if (logger) {
116+
EmitIfEnabled(*logger, LogLevel::kFatal, location,
117+
std::forward<FormatMessage>(format_message));
118+
logger->Flush();
119+
}
120+
std::abort();
121+
}
122+
123+
} // namespace iceberg::logging::detail
124+
125+
#define ICEBERG_INTERNAL_LOG_CURRENT(level_, FMT_, ...) \
126+
do { \
127+
::iceberg::logging::detail::LogCurrentIfEnabled( \
128+
(level_), ::std::source_location::current(), [&]() -> ::std::string { \
129+
return ::std::format((FMT_)__VA_OPT__(, ) __VA_ARGS__); \
130+
}); \
131+
} while (false)
132+
133+
#define ICEBERG_INTERNAL_LOG_RUNTIME(level_, FMT_, ...) \
134+
do { \
135+
::iceberg::logging::detail::LogCurrentRuntime( \
136+
(level_), ::std::source_location::current(), [&]() -> ::std::string { \
137+
return ::std::format((FMT_)__VA_OPT__(, ) __VA_ARGS__); \
138+
}); \
139+
} while (false)
140+
141+
#define ICEBERG_INTERNAL_LOG_TO(logger_, level_, FMT_, ...) \
142+
do { \
143+
::iceberg::logging::detail::LogToRuntime( \
144+
(logger_), (level_), ::std::source_location::current(), [&]() -> ::std::string { \
145+
return ::std::format((FMT_)__VA_OPT__(, ) __VA_ARGS__); \
146+
}); \
147+
} while (false)
148+
149+
#define ICEBERG_INTERNAL_LOG_RUNTIME_FMT(level_, FMT_, ...) \
150+
do { \
151+
::iceberg::logging::detail::LogCurrentRuntime( \
152+
(level_), ::std::source_location::current(), [&]() -> ::std::string { \
153+
return ::iceberg::logging::detail::VFormat((FMT_)__VA_OPT__(, ) __VA_ARGS__); \
154+
}); \
155+
} while (false)
156+
157+
#define ICEBERG_INTERNAL_LOG_FATAL(FMT_, ...) \
158+
do { \
159+
::iceberg::logging::detail::LogCurrentFatal( \
160+
::std::source_location::current(), [&]() -> ::std::string { \
161+
return ::std::format((FMT_)__VA_OPT__(, ) __VA_ARGS__); \
162+
}); \
163+
} while (false)
164+
165+
#if ICEBERG_LOG_ACTIVE_LEVEL <= ICEBERG_LOG_LEVEL_TRACE
166+
# define ICEBERG_LOG_TRACE(...) \
167+
ICEBERG_INTERNAL_LOG_CURRENT(::iceberg::LogLevel::kTrace, __VA_ARGS__)
168+
#else
169+
# define ICEBERG_LOG_TRACE(...) ((void)0)
170+
#endif
171+
172+
#if ICEBERG_LOG_ACTIVE_LEVEL <= ICEBERG_LOG_LEVEL_DEBUG
173+
# define ICEBERG_LOG_DEBUG(...) \
174+
ICEBERG_INTERNAL_LOG_CURRENT(::iceberg::LogLevel::kDebug, __VA_ARGS__)
175+
#else
176+
# define ICEBERG_LOG_DEBUG(...) ((void)0)
177+
#endif
178+
179+
#if ICEBERG_LOG_ACTIVE_LEVEL <= ICEBERG_LOG_LEVEL_INFO
180+
# define ICEBERG_LOG_INFO(...) \
181+
ICEBERG_INTERNAL_LOG_CURRENT(::iceberg::LogLevel::kInfo, __VA_ARGS__)
182+
#else
183+
# define ICEBERG_LOG_INFO(...) ((void)0)
184+
#endif
185+
186+
#if ICEBERG_LOG_ACTIVE_LEVEL <= ICEBERG_LOG_LEVEL_WARN
187+
# define ICEBERG_LOG_WARN(...) \
188+
ICEBERG_INTERNAL_LOG_CURRENT(::iceberg::LogLevel::kWarn, __VA_ARGS__)
189+
#else
190+
# define ICEBERG_LOG_WARN(...) ((void)0)
191+
#endif
192+
193+
#if ICEBERG_LOG_ACTIVE_LEVEL <= ICEBERG_LOG_LEVEL_ERROR
194+
# define ICEBERG_LOG_ERROR(...) \
195+
ICEBERG_INTERNAL_LOG_CURRENT(::iceberg::LogLevel::kError, __VA_ARGS__)
196+
#else
197+
# define ICEBERG_LOG_ERROR(...) ((void)0)
198+
#endif
199+
200+
#if ICEBERG_LOG_ACTIVE_LEVEL <= ICEBERG_LOG_LEVEL_CRITICAL
201+
# define ICEBERG_LOG_CRITICAL(...) \
202+
ICEBERG_INTERNAL_LOG_CURRENT(::iceberg::LogLevel::kCritical, __VA_ARGS__)
203+
#else
204+
# define ICEBERG_LOG_CRITICAL(...) ((void)0)
205+
#endif
206+
207+
#define ICEBERG_LOG_FATAL(...) ICEBERG_INTERNAL_LOG_FATAL(__VA_ARGS__)
208+
209+
#define ICEBERG_LOG(...) ICEBERG_INTERNAL_LOG_RUNTIME(__VA_ARGS__)
210+
211+
#define ICEBERG_LOG_TO(...) ICEBERG_INTERNAL_LOG_TO(__VA_ARGS__)
212+
213+
#define ICEBERG_LOG_RUNTIME_FMT(...) ICEBERG_INTERNAL_LOG_RUNTIME_FMT(__VA_ARGS__)

‎src/iceberg/logging/logger.h‎

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ struct ICEBERG_EXPORT LogAttribute {
5656

5757
/// \brief A single log record handed to a Logger.
5858
///
59-
/// The formatted message is owned (moved in by the logging macros), so a sink
59+
/// The formatted message is owned, so a sink
6060
/// may safely retain the record beyond the Log() call. The member set must not
6161
/// depend on the build's logging backend (the spdlog backend never appears here).
6262
/// Use LogMessage::Builder for a readable way to assemble one, especially with
@@ -134,11 +134,11 @@ inline constexpr std::string_view kPatternProperty = "pattern";
134134

135135
/// \brief Pluggable logging sink.
136136
///
137-
/// ShouldLog() is the single authority for runtime filtering -- the macros call
138-
/// it on every (compile-time-enabled) statement, so level changes by any path
139-
/// take effect immediately. Implementations must be thread-safe and must not
140-
/// throw. They must also obey:
141-
/// - No reentrancy: Log()/Flush() must not call the logging macros or
137+
/// ShouldLog() is the single authority for runtime filtering -- public logging
138+
/// helpers call it on every enabled statement, so level changes by any path take
139+
/// effect immediately. Implementations must be thread-safe and must not throw.
140+
/// They must also obey:
141+
/// - No reentrancy: Log()/Flush() must not call public logging helpers or
142142
/// GetDefaultLogger() (UB -- deadlock with mutex-based sinks).
143143
/// - level() is an accessor consistent with ShouldLog (used by SetDefaultLevel
144144
/// and introspection); ShouldLog may implement finer logic than a level compare.
@@ -187,7 +187,7 @@ class ICEBERG_EXPORT Logger {
187187
/// \brief Return the process-global default logger (never null).
188188
///
189189
/// Off the hot path -- acquires the slot lock and returns an owning copy. The
190-
/// logging macros use the cheaper internal hot-path accessor instead.
190+
/// public logging helpers use the cheaper internal hot-path accessor instead.
191191
ICEBERG_EXPORT std::shared_ptr<Logger> GetDefaultLogger();
192192

193193
/// \brief Return the effective logger for this thread (never null): the active
@@ -214,7 +214,7 @@ ICEBERG_EXPORT void SetDefaultLevel(LogLevel level);
214214
/// \brief Bind a logger for the current thread until this object leaves scope.
215215
///
216216
/// The default logging path on this thread -- CurrentLogger(), Log(level, ...),
217-
/// and the LOG_* macros -- routes to \p logger instead of the global default;
217+
/// and the ICEBERG_LOG_* macros -- routes to \p logger instead of the global default;
218218
/// explicit Log(logger, ...) is unaffected. Bindings nest and restore on exit, and
219219
/// nullptr masks any enclosing binding back to the global default. Lets an engine
220220
/// route Iceberg's own logs into a per catalog/session/query/task context with no
@@ -247,8 +247,8 @@ class ICEBERG_EXPORT ScopedLogger {
247247
};
248248

249249
// ---------------------------------------------------------------------------
250-
// Using the API directly (the LOG_* macros that wrap this are added later in
251-
// the stack). Example: a custom sink, installed as the process default.
250+
// Using the API directly. Example: a custom sink, installed as the process
251+
// default.
252252
//
253253
// class MySink : public Logger {
254254
// public:
@@ -285,31 +285,19 @@ ICEBERG_EXPORT const std::shared_ptr<Logger>& CurrentLogger() noexcept;
285285

286286
/// \brief Build a LogMessage from the already-formatted text and dispatch it.
287287
///
288-
/// Declared ICEBERG_EXPORT because the logging macros expand into this call in
289-
/// consumer translation units.
288+
/// Declared ICEBERG_EXPORT because public inline logging helpers can expand into
289+
/// this call in consumer translation units.
290290
ICEBERG_EXPORT void Emit(Logger& logger, LogLevel level,
291291
const std::source_location& location, std::string&& message);
292292

293293
/// \brief Emit a fixed fallback record when formatting threw.
294294
///
295295
/// noexcept, allocation-light (small/SSO literal), performs no std::format, and
296-
/// does not recurse -- so the macro's "logging never throws" guarantee holds
297-
/// even when a format argument throws.
296+
/// does not recurse -- so the public logging API's "logging never throws"
297+
/// guarantee holds even when a format argument throws.
298298
ICEBERG_EXPORT void EmitFormatError(Logger& logger, LogLevel level,
299299
const std::source_location& location) noexcept;
300300

301-
/// \brief Runtime (non-literal) format-string helper.
302-
///
303-
/// std::format requires a compile-time format string; this routes a runtime
304-
/// string through std::vformat. Args are bound as named lvalues and the
305-
/// arg-store is held in a named variable so it outlives the vformat call
306-
/// (C++23 make_format_args rejects rvalues -- P2905 / LWG3631).
307-
template <typename... Args>
308-
std::string VFormat(std::string_view fmt, Args&&... args) {
309-
auto store = std::make_format_args(args...);
310-
return std::vformat(fmt, store);
311-
}
312-
313301
/// \brief A checked format string bundled with the caller's source_location.
314302
///
315303
/// The consteval constructor preserves std::format's compile-time format-string
@@ -332,7 +320,7 @@ struct FmtWithLoc {
332320
/// \brief Shared gate -> format -> emit body for the function-style Log() API.
333321
///
334322
/// Formats only when the logger is enabled for \p level, and never throws (a
335-
/// formatting failure routes to EmitFormatError, matching the macros).
323+
/// formatting failure routes to EmitFormatError, matching the macro helpers).
336324
template <typename... Args>
337325
void FormatAndEmit(Logger& logger, LogLevel level, const std::source_location& loc,
338326
std::format_string<Args...> fmt, Args&&... args) noexcept {

‎src/iceberg/logging/meson.build‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
# under the License.
1717

1818
install_headers(
19-
['cerr_logger.h', 'log_level.h', 'logger.h'],
19+
[
20+
'cerr_logger.h',
21+
'log_level.h',
22+
'log_macros.h',
23+
'logger.h',
24+
'short_log_macros.h',
25+
],
2026
subdir: 'iceberg/logging',
2127
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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/short_log_macros.h
23+
/// \brief Opt-in short aliases for Iceberg logging macros.
24+
25+
#include "iceberg/logging/log_macros.h"
26+
27+
#define LOG_TRACE(...) ICEBERG_LOG_TRACE(__VA_ARGS__)
28+
#define LOG_DEBUG(...) ICEBERG_LOG_DEBUG(__VA_ARGS__)
29+
#define LOG_INFO(...) ICEBERG_LOG_INFO(__VA_ARGS__)
30+
#define LOG_WARN(...) ICEBERG_LOG_WARN(__VA_ARGS__)
31+
#define LOG_ERROR(...) ICEBERG_LOG_ERROR(__VA_ARGS__)
32+
#define LOG_CRITICAL(...) ICEBERG_LOG_CRITICAL(__VA_ARGS__)
33+
#define LOG_FATAL(...) ICEBERG_LOG_FATAL(__VA_ARGS__)

0 commit comments

Comments
 (0)