From 20992b532593bc52948617c42782ca4cc816680a Mon Sep 17 00:00:00 2001 From: Retr0-XD <88275564+Retr0-XD@users.noreply.github.com> Date: Sat, 21 Mar 2026 15:46:02 +0530 Subject: [PATCH] server: add --fine-grain-log-levels CLI argument Allow configuring per-file log levels (glob:level pairs) at startup when --enable-fine-grain-logging is set, similar to --component-log-level. Fixes #41028 Signed-off-by: Retr0-XD --- api/envoy/admin/v3/server_info.proto | 3 ++ envoy/server/options.h | 7 +++++ source/exe/BUILD | 1 + source/exe/main_common.cc | 9 ++++++ source/server/BUILD | 1 + source/server/options_impl.cc | 42 ++++++++++++++++++++++++++++ source/server/options_impl.h | 1 + source/server/options_impl_base.h | 5 ++++ test/mocks/server/options.h | 2 ++ test/server/options_impl_test.cc | 31 ++++++++++++++++++++ 10 files changed, 102 insertions(+) diff --git a/api/envoy/admin/v3/server_info.proto b/api/envoy/admin/v3/server_info.proto index 3e6d32f8ae8fc..b284f6e0ab50d 100644 --- a/api/envoy/admin/v3/server_info.proto +++ b/api/envoy/admin/v3/server_info.proto @@ -197,6 +197,9 @@ message CommandLineOptions { // See :option:`--enable-fine-grain-logging` for details. bool enable_fine_grain_logging = 34; + // See :option:`--fine-grain-log-levels` for details. + string fine_grain_log_levels = 43; + // See :option:`--socket-path` for details. string socket_path = 35; diff --git a/envoy/server/options.h b/envoy/server/options.h index 170838bacf903..fdad8f2c0dd2b 100644 --- a/envoy/server/options.h +++ b/envoy/server/options.h @@ -201,6 +201,13 @@ class Options { */ virtual bool enableFineGrainLogging() const PURE; + /** + * @return const std::vector>& pair of + * glob-pattern,log-level for all configured fine-grain log overrides. + */ + virtual const std::vector>& + fineGrainLogLevels() const PURE; + /** * @return const std::string& the log file path. */ diff --git a/source/exe/BUILD b/source/exe/BUILD index 16a51c89b39b1..e82ad4d1448be 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -115,6 +115,7 @@ envoy_cc_library( ":stripped_main_base_lib", "//source/common/api:os_sys_calls_lib", "//source/common/common:compiler_requirements_lib", + "//source/common/common:minimal_logger_lib", "//source/common/common:perf_annotation_lib", "//source/common/grpc:google_grpc_context_lib", "//source/server:hot_restart_lib", diff --git a/source/exe/main_common.cc b/source/exe/main_common.cc index 75099c6ee0c03..5bcd1280a3b29 100644 --- a/source/exe/main_common.cc +++ b/source/exe/main_common.cc @@ -8,6 +8,7 @@ #include "envoy/config/listener/v3/listener.pb.h" #include "source/common/common/compiler_requirements.h" +#include "source/common/common/fine_grain_logger.h" #include "source/common/common/logger.h" #include "source/common/common/perf_annotation.h" #include "source/common/common/thread.h" @@ -70,6 +71,14 @@ MainCommonBase::MainCommonBase(const Server::Options& options, Event::TimeSystem logging_context_ = std::make_unique( options_.logLevel(), options_.logFormat(), restarter_->logLock(), options_.logFormatEscaped(), options_.mode() == Server::Mode::Validate ? false : options_.enableFineGrainLogging()); + if (options_.enableFineGrainLogging() && !options_.fineGrainLogLevels().empty()) { + std::vector> glob_levels; + glob_levels.reserve(options_.fineGrainLogLevels().size()); + for (const auto& [glob, level] : options_.fineGrainLogLevels()) { + glob_levels.emplace_back(glob, static_cast(level)); + } + getFineGrainLogContext().updateVerbositySetting(glob_levels); + } init(time_system, listener_hooks, std::move(random_generator), std::move(process_context), createFunction()); } diff --git a/source/server/BUILD b/source/server/BUILD index 662cdb5123a42..8fae5d56ca5b4 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -301,6 +301,7 @@ envoy_cc_library( "//source/common/stats:stats_lib", "//source/common/stats:tag_utility_lib", "//source/common/version:version_lib", + "@abseil-cpp//absl/strings", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@tclap", diff --git a/source/server/options_impl.cc b/source/server/options_impl.cc index d5e73471e7786..eed3ae4569b7b 100644 --- a/source/server/options_impl.cc +++ b/source/server/options_impl.cc @@ -16,6 +16,7 @@ #include "source/server/options_impl_platform.h" #include "absl/strings/str_replace.h" +#include "absl/strings/str_join.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "spdlog/spdlog.h" @@ -49,6 +50,11 @@ OptionsImpl::OptionsImpl(std::vector args, const std::string component_log_level_string = "Comma-separated list of component log levels. For example upstream:debug,config:trace"; + const std::string fine_grain_log_levels_string = + "Comma-separated list of file-level log overrides for fine-grain logging. " + "Glob patterns are matched against source file names. For example " + "\"*filter*:debug,source/common/common/*:trace\". " + "Requires --enable-fine-grain-logging."; const std::string log_format_string = fmt::format("Log message format in spdlog syntax " "(see https://github.com/gabime/spdlog/wiki/3.-Custom-formatting)" @@ -123,6 +129,8 @@ OptionsImpl::OptionsImpl(std::vector args, TCLAP::SwitchArg enable_fine_grain_logging( "", "enable-fine-grain-logging", "Logger mode: enable file level log control (Fine-Grain Logger) or not", cmd, false); + TCLAP::ValueArg fine_grain_log_levels( + "", "fine-grain-log-levels", fine_grain_log_levels_string, false, "", "string", cmd); TCLAP::ValueArg log_path("", "log-path", "Path to logfile", false, "", "string", cmd); TCLAP::ValueArg restart_epoch("", "restart-epoch", "Hot restart epoch #", false, 0, @@ -228,7 +236,13 @@ OptionsImpl::OptionsImpl(std::vector args, "error: --component-log-level will not work with --enable-fine-grain-logging"); } + if (!fine_grain_log_levels.getValue().empty() && !enable_fine_grain_logging_) { + throw MalformedArgvException( + "error: --fine-grain-log-levels requires --enable-fine-grain-logging"); + } + parseComponentLogLevels(component_log_level.getValue()); + parseFineGrainLogLevels(fine_grain_log_levels.getValue()); if (mode.getValue() == "serve") { mode_ = Server::Mode::Serve; @@ -397,6 +411,26 @@ void OptionsImpl::parseComponentLogLevels(const std::string& component_log_level void OptionsImpl::logError(const std::string& error) { throw MalformedArgvException(error); } +void OptionsImpl::parseFineGrainLogLevels(const std::string& fine_grain_log_levels) { + if (fine_grain_log_levels.empty()) { + return; + } + std::vector log_levels = absl::StrSplit(fine_grain_log_levels, ','); + for (auto& level : log_levels) { + std::vector glob_level = absl::StrSplit(level, absl::MaxSplits(':', 1)); + if (glob_level.size() != 2) { + logError( + fmt::format("error: fine-grain log level not correctly specified '{}'", level)); + } + const std::string glob = glob_level[0]; + auto status_or_error = parseAndValidateLogLevel(glob_level[1]); + if (!status_or_error.status().ok()) { + logError(std::string(status_or_error.status().message())); + } + fine_grain_log_levels_.push_back(std::make_pair(glob, status_or_error.value())); + } +} + Server::CommandLineOptionsPtr OptionsImpl::toCommandLineOptions() const { Server::CommandLineOptionsPtr command_line_options = std::make_unique(); @@ -419,6 +453,14 @@ Server::CommandLineOptionsPtr OptionsImpl::toCommandLineOptions() const { command_line_options->set_log_format(logFormat()); command_line_options->set_log_format_escaped(logFormatEscaped()); command_line_options->set_enable_fine_grain_logging(enableFineGrainLogging()); + if (!fine_grain_log_levels_.empty()) { + std::vector parts; + parts.reserve(fine_grain_log_levels_.size()); + for (const auto& [glob, level] : fine_grain_log_levels_) { + parts.push_back(fmt::format("{}:{}", glob, spdlog::level::to_string_view(level))); + } + command_line_options->set_fine_grain_log_levels(absl::StrJoin(parts, ",")); + } command_line_options->set_log_path(logPath()); command_line_options->set_service_cluster(serviceClusterName()); command_line_options->set_service_node(serviceNodeName()); diff --git a/source/server/options_impl.h b/source/server/options_impl.h index 7f59fc9f83639..c8ef3425c2ce5 100644 --- a/source/server/options_impl.h +++ b/source/server/options_impl.h @@ -54,6 +54,7 @@ class OptionsImpl : public OptionsImplBase { Server::CommandLineOptionsPtr toCommandLineOptions() const override; void parseComponentLogLevels(const std::string& component_log_levels); + void parseFineGrainLogLevels(const std::string& fine_grain_log_levels); static void logError(const std::string& error); static std::string allowedLogLevels(); }; diff --git a/source/server/options_impl_base.h b/source/server/options_impl_base.h index 1da468b45ec36..a7052c272421d 100644 --- a/source/server/options_impl_base.h +++ b/source/server/options_impl_base.h @@ -137,6 +137,10 @@ class OptionsImplBase : public Server::Options, protected Logger::Loggable>& + fineGrainLogLevels() const override { + return fine_grain_log_levels_; + } const std::string& logPath() const override { return log_path_; } uint64_t restartEpoch() const override { return restart_epoch_; } Server::Mode mode() const override { return mode_; } @@ -199,6 +203,7 @@ class OptionsImplBase : public Server::Options, protected Logger::Loggable> component_log_levels_; std::string component_log_level_str_; + std::vector> fine_grain_log_levels_; std::string log_format_{Logger::Logger::DEFAULT_LOG_FORMAT}; bool log_format_set_{false}; bool log_format_escaped_{false}; diff --git a/test/mocks/server/options.h b/test/mocks/server/options.h index b5a7bcc3a6e21..ab7592465bc67 100644 --- a/test/mocks/server/options.h +++ b/test/mocks/server/options.h @@ -40,6 +40,8 @@ class MockOptions : public Options { MOCK_METHOD(bool, logFormatSet, (), (const)); MOCK_METHOD(bool, logFormatEscaped, (), (const)); MOCK_METHOD(bool, enableFineGrainLogging, (), (const)); + MOCK_METHOD((const std::vector>&), + fineGrainLogLevels, (), (const)); MOCK_METHOD(const std::string&, logPath, (), (const)); MOCK_METHOD(uint64_t, restartEpoch, (), (const)); MOCK_METHOD(std::chrono::milliseconds, fileFlushIntervalMsec, (), (const)); diff --git a/test/server/options_impl_test.cc b/test/server/options_impl_test.cc index e90d669f635d3..2e7cc43ff7d63 100644 --- a/test/server/options_impl_test.cc +++ b/test/server/options_impl_test.cc @@ -347,6 +347,37 @@ TEST_F(OptionsImplTest, SetEnableFineGrainLogging) { EXPECT_TRUE(options->enableFineGrainLogging()); } +TEST_F(OptionsImplTest, FineGrainLogLevelsCLI) { + std::unique_ptr options = createOptionsImpl( + "envoy --enable-fine-grain-logging --fine-grain-log-levels *filter*:debug,*common*:trace"); + EXPECT_TRUE(options->enableFineGrainLogging()); + const auto& levels = options->fineGrainLogLevels(); + ASSERT_EQ(2, levels.size()); + EXPECT_EQ("*filter*", levels[0].first); + EXPECT_EQ(spdlog::level::debug, levels[0].second); + EXPECT_EQ("*common*", levels[1].first); + EXPECT_EQ(spdlog::level::trace, levels[1].second); +} + +TEST_F(OptionsImplTest, FineGrainLogLevelsRequiresFineGrainLogging) { + EXPECT_THROW_WITH_REGEX( + createOptionsImpl("envoy --fine-grain-log-levels *filter*:debug"), MalformedArgvException, + "error: --fine-grain-log-levels requires --enable-fine-grain-logging"); +} + +TEST_F(OptionsImplTest, InvalidFineGrainLogLevel) { + std::unique_ptr options = createOptionsImpl("envoy --mode init_only"); + EXPECT_THROW_WITH_REGEX(options->parseFineGrainLogLevels("*filter*:blah"), + MalformedArgvException, "error: invalid log level specified 'blah'"); +} + +TEST_F(OptionsImplTest, InvalidFineGrainLogLevelStructure) { + std::unique_ptr options = createOptionsImpl("envoy --mode init_only"); + EXPECT_THROW_WITH_REGEX(options->parseFineGrainLogLevels("*filter*"), + MalformedArgvException, + "error: fine-grain log level not correctly specified '\\*filter\\*'"); +} + // Validates that the server_info proto is in sync with the options. TEST_F(OptionsImplTest, OptionsAreInSyncWithProto) { std::unique_ptr options = createOptionsImpl("envoy -c hello");