From 0083bbd676fbc1f7a4775829fec93611f30101b7 Mon Sep 17 00:00:00 2001 From: Willem Kaufmann Date: Wed, 22 Apr 2026 18:48:39 -0400 Subject: [PATCH 1/3] `util`: factor `is_enabled` gate into templated helpers Extract the body of `logger::log(level, fmt, args...)` and `logger::log(level, rate_limit&, fmt, args...)` into private do_log_checked / do_log_checked_rl helpers that take an explicit bypass flag. No observable behavior change; this is preparation for a force_tag overload set that will request bypass=true. --- include/seastar/util/log.hh | 73 +++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/include/seastar/util/log.hh b/include/seastar/util/log.hh index 7599def12bc..0955b543b41 100644 --- a/include/seastar/util/log.hh +++ b/include/seastar/util/log.hh @@ -71,6 +71,14 @@ class logger { #endif public: + /// Tag used to request that a log call bypass the configured level + /// gate. Passed as the first argument to any force-suffixed overload + /// or to the per-level helpers. A distinct type (not bool) avoids + /// ambiguity with the format_info-first overloads: a string literal + /// converts to format_info but not to force_tag. + struct force_tag {}; + static constexpr force_tag force{}; + class log_writer { public: virtual ~log_writer() = default; @@ -251,20 +259,7 @@ public: /// template void log(log_level level, format_info_t fmt, Args&&... args) noexcept { - if (is_enabled(level)) { - try { - lambda_log_writer writer([&] (internal::log_buf::inserter_iterator it) { -#ifdef SEASTAR_LOGGER_COMPILE_TIME_FMT - return fmt::format_to(it, fmt.format, std::forward(args)...); -#else - return fmt::format_to(it, fmt::runtime(fmt.format), std::forward(args)...); -#endif - }); - do_log(level, writer); - } catch (...) { - failed_to_log(std::current_exception(), fmt::string_view(fmt.format), fmt.loc); - } - } + do_log_checked(level, false, std::move(fmt), std::forward(args)...); } /// logs with a rate limit to desired level if enabled, otherwise we ignore the log line @@ -283,19 +278,7 @@ public: /// template void log(log_level level, rate_limit& rl, format_info_t fmt, Args&&... args) noexcept { - if (is_enabled(level) && rl.check()) { - try { - lambda_log_writer writer([&] (internal::log_buf::inserter_iterator it) { - if (rl.has_dropped_messages()) { - it = fmt::format_to(it, "(rate limiting dropped {} similar messages) ", rl.get_and_reset_dropped_messages()); - } - return fmt::format_to(it, fmt::runtime(fmt.format), std::forward(args)...); - }); - do_log(level, writer); - } catch (...) { - failed_to_log(std::current_exception(), fmt::string_view(fmt.format), fmt.loc); - } - } + do_log_checked_rl(level, false, rl, std::move(fmt), std::forward(args)...); } /// \cond internal @@ -457,6 +440,42 @@ public: /// /// \note this is a noop if fmtlib's version is less than 6.0 static void set_with_color(bool enabled) noexcept; + +private: + template + void do_log_checked(log_level level, bool bypass, format_info_t fmt, Args&&... args) noexcept { + if (bypass || is_enabled(level)) { + try { + lambda_log_writer writer([&] (internal::log_buf::inserter_iterator it) { +#ifdef SEASTAR_LOGGER_COMPILE_TIME_FMT + return fmt::format_to(it, fmt.format, std::forward(args)...); +#else + return fmt::format_to(it, fmt::runtime(fmt.format), std::forward(args)...); +#endif + }); + do_log(level, writer); + } catch (...) { + failed_to_log(std::current_exception(), fmt::string_view(fmt.format), fmt.loc); + } + } + } + + template + void do_log_checked_rl(log_level level, bool bypass, rate_limit& rl, format_info_t fmt, Args&&... args) noexcept { + if ((bypass || is_enabled(level)) && rl.check()) { + try { + lambda_log_writer writer([&] (internal::log_buf::inserter_iterator it) { + if (rl.has_dropped_messages()) { + it = fmt::format_to(it, "(rate limiting dropped {} similar messages) ", rl.get_and_reset_dropped_messages()); + } + return fmt::format_to(it, fmt::runtime(fmt.format), std::forward(args)...); + }); + do_log(level, writer); + } catch (...) { + failed_to_log(std::current_exception(), fmt::string_view(fmt.format), fmt.loc); + } + } + } }; /// \brief used to keep a static registry of loggers From 1965c80807e8cc0bdede605b9acac7f2dfb4de5b Mon Sep 17 00:00:00 2001 From: Willem Kaufmann Date: Wed, 22 Apr 2026 18:53:34 -0400 Subject: [PATCH 2/3] `util`: add `force_tag` overloads of `logger::log` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two new templated overloads of `logger::log` accept a `force_tag` as their second positional argument and route to do_log_checked / do_log_checked_rl with bypass=true, emitting regardless of the configured logger level. Distinct tag type (not bool) avoids ambiguity with existing format_info-first callers — string literals convert to format_info but not to force_tag. --- include/seastar/util/log.hh | 13 ++++++++++ tests/unit/CMakeLists.txt | 3 +++ tests/unit/logger_force_test.cc | 42 +++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 tests/unit/logger_force_test.cc diff --git a/include/seastar/util/log.hh b/include/seastar/util/log.hh index 0955b543b41..91c564a91b2 100644 --- a/include/seastar/util/log.hh +++ b/include/seastar/util/log.hh @@ -262,6 +262,12 @@ public: do_log_checked(level, false, std::move(fmt), std::forward(args)...); } + /// Force-emit variant: bypass the level gate. See \ref force_tag. + template + void log(log_level level, force_tag, format_info_t fmt, Args&&... args) noexcept { + do_log_checked(level, true, std::move(fmt), std::forward(args)...); + } + /// logs with a rate limit to desired level if enabled, otherwise we ignore the log line /// /// If there were messages dropped due to rate-limiting the following snippet @@ -281,6 +287,13 @@ public: do_log_checked_rl(level, false, rl, std::move(fmt), std::forward(args)...); } + /// Force-emit rate-limited variant: bypass the level gate (the rate + /// limit itself still applies). + template + void log(log_level level, force_tag, rate_limit& rl, format_info_t fmt, Args&&... args) noexcept { + do_log_checked_rl(level, true, rl, std::move(fmt), std::forward(args)...); + } + /// \cond internal /// logs to desired level if enabled, otherwise we ignore the log line /// diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 413ca0b0c5d..28db9238158 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -769,6 +769,9 @@ seastar_add_test (weak_ptr seastar_add_test (log_buf SOURCES log_buf_test.cc) +seastar_add_test (logger_force + SOURCES logger_force_test.cc) + seastar_add_test (exception_logging KIND BOOST SOURCES exception_logging_test.cc) diff --git a/tests/unit/logger_force_test.cc b/tests/unit/logger_force_test.cc new file mode 100644 index 00000000000..7af05db5bff --- /dev/null +++ b/tests/unit/logger_force_test.cc @@ -0,0 +1,42 @@ +/* + * This file is open source software, licensed to you under the terms + * of the Apache License, Version 2.0 (the "License"). See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. 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 +#include + +#include + +namespace { +seastar::logger test_log("test"); +} + +SEASTAR_THREAD_TEST_CASE(force_bypasses_level_gate_log_overload) { + std::ostringstream captured; + seastar::logger::set_ostream(captured); + seastar::logger::set_ostream_enabled(true); + + test_log.set_level(seastar::log_level::warn); + + // Without force: dropped. + test_log.log(seastar::log_level::info, "dropped_line"); + BOOST_REQUIRE(captured.str().find("dropped_line") == std::string::npos); + + // With force: emitted. + test_log.log(seastar::log_level::info, seastar::logger::force, "forced_line"); + BOOST_REQUIRE(captured.str().find("forced_line") != std::string::npos); +} From 8948822a046413306b8da155ab85674545ab7f04 Mon Sep 17 00:00:00 2001 From: Willem Kaufmann Date: Wed, 22 Apr 2026 18:58:59 -0400 Subject: [PATCH 3/3] `util`: add force_tag overloads of per-level convenience methods error/warn/info/info0/debug/trace gain a force_tag overload that forwards to logger::log(level, force, fmt, args...). info0 retains its shard-zero guard. Gives callers a symmetric API surface for bypassing the level gate from any of the standard helpers. --- include/seastar/util/log.hh | 32 ++++++++++++++++++++++++++++++++ tests/unit/logger_force_test.cc | 26 ++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/include/seastar/util/log.hh b/include/seastar/util/log.hh index 91c564a91b2..bebad1a2476 100644 --- a/include/seastar/util/log.hh +++ b/include/seastar/util/log.hh @@ -351,6 +351,11 @@ public: void error(format_info_t fmt, Args&&... args) noexcept { log(log_level::error, std::move(fmt), std::forward(args)...); } + /// Force-emit variant: bypass the level gate. See \ref force_tag. + template + void error(force_tag, format_info_t fmt, Args&&... args) noexcept { + log(log_level::error, force, std::move(fmt), std::forward(args)...); + } /// Log with warning tag: /// WARN %Y-%m-%d %T,%03d [shard 0] - "your msg" \n /// @@ -362,6 +367,11 @@ public: void warn(format_info_t fmt, Args&&... args) noexcept { log(log_level::warn, std::move(fmt), std::forward(args)...); } + /// Force-emit variant: bypass the level gate. See \ref force_tag. + template + void warn(force_tag, format_info_t fmt, Args&&... args) noexcept { + log(log_level::warn, force, std::move(fmt), std::forward(args)...); + } /// Log with info tag: /// INFO %Y-%m-%d %T,%03d [shard 0] - "your msg" \n /// @@ -373,6 +383,11 @@ public: void info(format_info_t fmt, Args&&... args) noexcept { log(log_level::info, std::move(fmt), std::forward(args)...); } + /// Force-emit variant: bypass the level gate. See \ref force_tag. + template + void info(force_tag, format_info_t fmt, Args&&... args) noexcept { + log(log_level::info, force, std::move(fmt), std::forward(args)...); + } /// Log with info tag on shard zero only: /// INFO %Y-%m-%d %T,%03d [shard 0] - "your msg" \n /// @@ -386,6 +401,13 @@ public: log(log_level::info, std::move(fmt), std::forward(args)...); } } + /// Force-emit variant: bypass the level gate. See \ref force_tag. + template + void info0(force_tag, format_info_t fmt, Args&&... args) noexcept { + if (is_shard_zero()) { + log(log_level::info, force, std::move(fmt), std::forward(args)...); + } + } /// Log with debug tag: /// DEBUG %Y-%m-%d %T,%03d [shard 0] - "your msg" \n /// @@ -397,6 +419,11 @@ public: void debug(format_info_t fmt, Args&&... args) noexcept { log(log_level::debug, std::move(fmt), std::forward(args)...); } + /// Force-emit variant: bypass the level gate. See \ref force_tag. + template + void debug(force_tag, format_info_t fmt, Args&&... args) noexcept { + log(log_level::debug, force, std::move(fmt), std::forward(args)...); + } /// Log with trace tag: /// TRACE %Y-%m-%d %T,%03d [shard 0] - "your msg" \n /// @@ -408,6 +435,11 @@ public: void trace(format_info_t fmt, Args&&... args) noexcept { log(log_level::trace, std::move(fmt), std::forward(args)...); } + /// Force-emit variant: bypass the level gate. See \ref force_tag. + template + void trace(force_tag, format_info_t fmt, Args&&... args) noexcept { + log(log_level::trace, force, std::move(fmt), std::forward(args)...); + } /// \return name of the logger. Usually one logger per module /// diff --git a/tests/unit/logger_force_test.cc b/tests/unit/logger_force_test.cc index 7af05db5bff..980daba67e2 100644 --- a/tests/unit/logger_force_test.cc +++ b/tests/unit/logger_force_test.cc @@ -40,3 +40,29 @@ SEASTAR_THREAD_TEST_CASE(force_bypasses_level_gate_log_overload) { test_log.log(seastar::log_level::info, seastar::logger::force, "forced_line"); BOOST_REQUIRE(captured.str().find("forced_line") != std::string::npos); } + +SEASTAR_THREAD_TEST_CASE(force_bypasses_level_gate_convenience_methods) { + std::ostringstream captured; + seastar::logger::set_ostream(captured); + seastar::logger::set_ostream_enabled(true); + + test_log.set_level(seastar::log_level::warn); + + test_log.info(seastar::logger::force, "info_forced"); + test_log.debug(seastar::logger::force, "debug_forced"); + test_log.trace(seastar::logger::force, "trace_forced"); + test_log.warn(seastar::logger::force, "warn_forced"); + test_log.error(seastar::logger::force, "error_forced"); + + const auto out = captured.str(); + BOOST_REQUIRE(out.find("info_forced") != std::string::npos); + BOOST_REQUIRE(out.find("debug_forced") != std::string::npos); + BOOST_REQUIRE(out.find("trace_forced") != std::string::npos); + BOOST_REQUIRE(out.find("warn_forced") != std::string::npos); + BOOST_REQUIRE(out.find("error_forced") != std::string::npos); + + // Without force, info/debug/trace dropped under warn level. + captured.str(""); + test_log.info("info_dropped"); + BOOST_REQUIRE(captured.str().find("info_dropped") == std::string::npos); +}