-
Notifications
You must be signed in to change notification settings - Fork 5.5k
admin: support HTML UI for admin post params #22424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 94 commits
c4101e1
29cf776
94c97c0
aa00670
34c7607
4e821b4
8cbcf36
ff2aaf9
1e4d606
256b6c9
49212f6
ba5a1dc
c78b6a4
33b2f22
efe142e
78091d1
119270c
68a89d2
70ce924
71c1647
d04a05c
85d33f7
d482473
d42f01a
2c4422a
594b618
102d95c
a5706e1
a4de093
246af3a
987e764
5f5ae3c
c9041e6
afbcf1a
3fc02f0
fc5ac30
7bb3a00
6dcac05
81f2763
329281b
c537b80
25c871e
685e307
182c26d
fecece3
f187c65
d16f773
2ec28de
e23c884
4f59d62
c2350a2
c9a0467
e8abc92
bfb734a
752ae63
e8d16fd
abe2049
ab028cf
d9d03c8
b28dd55
1b95d3b
048d239
93fe3f3
5c0fd6c
85cde04
b626917
ddda5d7
2a62570
03be332
ce6e904
26f200d
50dbe5a
0859243
603f0f8
1cc67ca
ee21f74
379b316
a7b3565
c456954
8c3af8b
58c8b81
36888b8
1738fc7
35595ac
c820090
da2db71
4f2025f
bcc798b
8c9d80c
b17ad84
855d881
1c3e458
832ea51
4d7b7e5
f76377b
327ce0f
e6c81a0
8ce26f4
32ce495
4ec9bad
e17e9b4
09ca850
c77f7cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,6 +76,17 @@ void AdminImpl::startHttpListener(const std::list<AccessLog::InstanceSharedPtr>& | |
| } | ||
| } | ||
|
|
||
| namespace { | ||
| std::vector<absl::string_view> prepend(const absl::string_view first, | ||
| const std::vector<absl::string_view>& strings) { | ||
| std::vector<absl::string_view> v; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. drive by question: is this more efficient than https://cplusplus.com/reference/vector/vector/insert/ ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably not; will clean up.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh wait -- what did you mean? It was using vector::insert already. I think this is would be slightly more efficient than copying the vector as is and then inserting a new beginning, as it right-size allocs it first.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My hunch is insert will be faster, as it will result in moves v/s the explicit copying within prepend. Can this function not just take an r-value
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry -- I'm confused. I am using insert. BTW this code doesn't need to be fast at all; it just happens once on startup. I think the main argument here is not whether to use insert, but whether it's worth doing the reserve. I don't think move vs copy makes much difference: this is a string-view. But I see Pradeep's point: this is a local helper so I could just make it modify the strings in-place not not bother doing the copy, which makes this non-perf-critical code have fewer lines :) |
||
| v.reserve(strings.size() + 1); | ||
| v.push_back(first); | ||
| v.insert(v.end(), strings.begin(), strings.end()); | ||
| return v; | ||
| } | ||
| } // namespace | ||
|
|
||
| AdminImpl::AdminImpl(const std::string& profile_path, Server::Instance& server, | ||
| bool ignore_global_conn_limit) | ||
| : server_(server), | ||
|
|
@@ -122,7 +133,11 @@ AdminImpl::AdminImpl(const std::string& profile_path, Server::Instance& server, | |
| makeHandler("/contention", "dump current Envoy mutex contention stats (if enabled)", | ||
| MAKE_ADMIN_HANDLER(stats_handler_.handlerContention), false, false), | ||
| makeHandler("/cpuprofiler", "enable/disable the CPU profiler", | ||
| MAKE_ADMIN_HANDLER(profiling_handler_.handlerCpuProfiler), false, true), | ||
| MAKE_ADMIN_HANDLER(profiling_handler_.handlerCpuProfiler), false, true, | ||
| {{Admin::ParamDescriptor::Type::Enum, | ||
| "enable", | ||
| "enables the CPU profiler", | ||
| {"y", "n"}}}), | ||
| makeHandler("/heapprofiler", "enable/disable the heap profiler", | ||
| MAKE_ADMIN_HANDLER(profiling_handler_.handlerHeapProfiler), false, true), | ||
| makeHandler("/healthcheck/fail", "cause the server to fail health checks", | ||
|
|
@@ -139,16 +154,29 @@ AdminImpl::AdminImpl(const std::string& profile_path, Server::Instance& server, | |
| // those params as the post-body rather than query-params and that requires some | ||
| // re-plumbing through the admin callback API. See also drain_listeners. | ||
| makeHandler("/logging", "query/change logging levels", | ||
| MAKE_ADMIN_HANDLER(logs_handler_.handlerLogging), false, true), | ||
|
|
||
| MAKE_ADMIN_HANDLER(logs_handler_.handlerLogging), false, true, | ||
| {{Admin::ParamDescriptor::Type::String, "paths", | ||
| "Change multiple logging levels by setting to " | ||
| "<logger_name1>:<desired_level1>,<logger_name2>:<desired_level2>."}, | ||
| {Admin::ParamDescriptor::Type::Enum, "level", "desired logging level", | ||
| prepend("", LogsHandler::levelStrings())}}), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what does empty level do? I could not find this info here https://www.envoyproxy.io/docs/envoy/latest/operations/admin#post--logging
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this particular command, if you specify one of the logging levels, it will change all the logging paths to use that level. If you leave the level blank then it will perform some other operation -- either just listing out the current levels for each path, or setting particular path/level combos. I'll add a comment. |
||
| makeHandler("/memory", "print current allocation/heap usage", | ||
| MAKE_ADMIN_HANDLER(server_info_handler_.handlerMemory), false, false), | ||
| makeHandler("/quitquitquit", "exit the server", | ||
| MAKE_ADMIN_HANDLER(server_cmd_handler_.handlerQuitQuitQuit), false, true), | ||
| makeHandler("/reset_counters", "reset all counters to zero", | ||
| MAKE_ADMIN_HANDLER(stats_handler_.handlerResetCounters), false, true), | ||
| makeHandler("/drain_listeners", "drain listeners", | ||
| MAKE_ADMIN_HANDLER(listeners_handler_.handlerDrainListeners), false, true), | ||
| makeHandler( | ||
| "/drain_listeners", "drain listeners", | ||
| MAKE_ADMIN_HANDLER(listeners_handler_.handlerDrainListeners), false, true, | ||
| {{ParamDescriptor::Type::Boolean, "graceful", | ||
| "When draining listeners, enter a graceful drain period prior to closing " | ||
| "listeners. This behaviour and duration is configurable via server options " | ||
| "or CLI"}, | ||
| {ParamDescriptor::Type::Boolean, "inboundonly", | ||
| "Drains all inbound listeners. traffic_direction field in " | ||
| "envoy_v3_api_msg_config.listener.v3.Listener is used to determine whether a " | ||
| "listener is inbound or outbound."}}), | ||
| makeHandler("/server_info", "print server version/status information", | ||
| MAKE_ADMIN_HANDLER(server_info_handler_.handlerServerInfo), false, false), | ||
| makeHandler("/ready", "print server state, return 200 if LIVE, otherwise return 503", | ||
|
|
@@ -332,6 +360,7 @@ Admin::RequestPtr AdminImpl::makeRequest(absl::string_view path_and_query, | |
| } | ||
| } | ||
|
|
||
| ASSERT(admin_stream.getRequestHeaders().getPathValue() == path_and_query); | ||
| return handler.handler_(path_and_query, admin_stream); | ||
| } | ||
| } | ||
|
|
@@ -437,6 +466,7 @@ Http::Code AdminImpl::request(absl::string_view path_and_query, absl::string_vie | |
|
|
||
| auto request_headers = Http::RequestHeaderMapImpl::create(); | ||
| request_headers->setMethod(method); | ||
| request_headers->setPath(path_and_query); | ||
| filter.decodeHeaders(*request_headers, false); | ||
| Buffer::OwnedImpl response; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,10 +16,9 @@ namespace { | |
| absl::flat_hash_map<absl::string_view, spdlog::level::level_enum> buildLevelMap() { | ||
| absl::flat_hash_map<absl::string_view, spdlog::level::level_enum> levels; | ||
|
|
||
| for (size_t i = 0; i < ARRAY_SIZE(spdlog::level::level_string_views); i++) { | ||
| spdlog::string_view_t spd_level_string{spdlog::level::level_string_views[i]}; | ||
| absl::string_view level_string{spd_level_string.data(), spd_level_string.size()}; | ||
| levels[level_string] = static_cast<spdlog::level::level_enum>(i); | ||
| uint32_t i = 0; | ||
| for (absl::string_view level_string : LogsHandler::levelStrings()) { | ||
| levels[level_string] = static_cast<spdlog::level::level_enum>(i++); | ||
| } | ||
|
|
||
| return levels; | ||
|
|
@@ -29,28 +28,34 @@ absl::flat_hash_map<absl::string_view, spdlog::level::level_enum> buildLevelMap( | |
| LogsHandler::LogsHandler(Server::Instance& server) | ||
| : HandlerContextBase(server), log_levels_(buildLevelMap()) {} | ||
|
|
||
| Http::Code LogsHandler::handlerLogging(absl::string_view url, Http::ResponseHeaderMap&, | ||
| Buffer::Instance& response, AdminStream&) { | ||
| Http::Utility::QueryParams query_params = Http::Utility::parseQueryString(url); | ||
| std::vector<absl::string_view> LogsHandler::levelStrings() { | ||
| std::vector<absl::string_view> strings; | ||
| strings.reserve(ARRAY_SIZE(spdlog::level::level_string_views)); | ||
| for (spdlog::string_view_t level : spdlog::level::level_string_views) { | ||
| strings.emplace_back(absl::string_view{level.data(), level.size()}); | ||
| } | ||
| return strings; | ||
| } | ||
|
|
||
| Http::Code rc = Http::Code::OK; | ||
| if (!query_params.empty()) { | ||
| auto status = changeLogLevel(query_params); | ||
| if (!status.ok()) { | ||
| rc = Http::Code::BadRequest; | ||
| response.add(fmt::format("error: {}\n\n", status.message())); | ||
|
|
||
| response.add("usage: /logging?<name>=<level> (change single level)\n"); | ||
| response.add( | ||
| "usage: /logging?paths=name1:level1,name2:level2,... (change multiple levels)\n"); | ||
| response.add("usage: /logging?level=<level> (change all levels)\n"); | ||
| response.add("levels: "); | ||
| for (auto level_string_view : spdlog::level::level_string_views) { | ||
| response.add(fmt::format("{} ", level_string_view)); | ||
| } | ||
| Http::Code LogsHandler::handlerLogging(absl::string_view, Http::ResponseHeaderMap&, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the first parameter used for any handler, since it's now available on the AdminStream? Is this no longer needed on
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would require AdminFilter to store the query params once parsed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will remove the first param to MAKE_ADMIN_HANDLER as a follow-up -- it's a pretty noisy change so I'd like to make that in a separate PR that has no functional impact. I'm not sure what you mean by 'store the query params' -- the API I provided is that it generates the query-params map and returns it. The source for that is the request body and request headers which contains the "path" as a synthetic header.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done as a pending follow-up PR: #22571 -- it's not reviewable now but once this merges, that one will be non-functional so that its sprawl is easier to reason about. |
||
| Buffer::Instance& response, AdminStream& admin_stream) { | ||
| Http::Utility::QueryParams query_params = admin_stream.queryParams(); | ||
|
|
||
| response.add("\n"); | ||
| Http::Code rc = Http::Code::OK; | ||
| const absl::Status status = changeLogLevel(query_params); | ||
| if (!status.ok()) { | ||
| rc = Http::Code::BadRequest; | ||
| response.add(fmt::format("error: {}\n\n", status.message())); | ||
|
|
||
| response.add("usage: /logging?<name>=<level> (change single level)\n"); | ||
| response.add("usage: /logging?paths=name1:level1,name2:level2,... (change multiple levels)\n"); | ||
| response.add("usage: /logging?level=<level> (change all levels)\n"); | ||
| response.add("levels: "); | ||
| for (auto level_string_view : spdlog::level::level_string_views) { | ||
| response.add(fmt::format("{} ", level_string_view)); | ||
| } | ||
|
|
||
| response.add("\n"); | ||
| } | ||
|
|
||
| if (!Logger::Context::useFineGrainLogger()) { | ||
|
|
@@ -77,18 +82,31 @@ Http::Code LogsHandler::handlerReopenLogs(absl::string_view, Http::ResponseHeade | |
| return Http::Code::OK; | ||
| } | ||
|
|
||
| absl::Status LogsHandler::changeLogLevel(const Http::Utility::QueryParams& params) { | ||
| absl::Status LogsHandler::changeLogLevel(Http::Utility::QueryParams& params) { | ||
| // "level" and "paths" will be set to the empty string when this is invoked | ||
| // from HTML without setting them, so clean out empty values. | ||
| auto level = params.find("level"); | ||
| if (level != params.end() && level->second.empty()) { | ||
| params.erase(level); | ||
| level = params.end(); | ||
| } | ||
| auto paths = params.find("paths"); | ||
| if (paths != params.end() && paths->second.empty()) { | ||
| params.erase(paths); | ||
| paths = params.end(); | ||
| } | ||
|
|
||
| if (params.empty()) { | ||
| return absl::OkStatus(); | ||
| } | ||
|
|
||
| if (params.size() != 1) { | ||
| return absl::InvalidArgumentError("invalid number of parameters"); | ||
| } | ||
|
|
||
| const auto it = params.begin(); | ||
| absl::string_view key(it->first); | ||
| absl::string_view value(it->second); | ||
|
|
||
| if (key == "level") { | ||
| if (level != params.end()) { | ||
| // Change all log levels. | ||
| auto level_to_use = parseLogLevel(value); | ||
| const absl::StatusOr<spdlog::level::level_enum> level_to_use = parseLogLevel(level->second); | ||
| if (!level_to_use.ok()) { | ||
| return level_to_use.status(); | ||
| } | ||
|
|
@@ -101,27 +119,40 @@ absl::Status LogsHandler::changeLogLevel(const Http::Utility::QueryParams& param | |
| // not common to call this function at a high rate. | ||
| absl::flat_hash_map<absl::string_view, spdlog::level::level_enum> name_levels; | ||
|
|
||
| if (key == "paths") { | ||
| if (paths != params.end()) { | ||
| // Bulk change log level by name:level pairs, separated by comma. | ||
| std::vector<absl::string_view> pairs = absl::StrSplit(value, ',', absl::SkipWhitespace()); | ||
| std::vector<absl::string_view> pairs = | ||
| absl::StrSplit(paths->second, ',', absl::SkipWhitespace()); | ||
| for (const auto& name_level : pairs) { | ||
| std::pair<absl::string_view, absl::string_view> name_level_pair = | ||
| const std::pair<absl::string_view, absl::string_view> name_level_pair = | ||
| absl::StrSplit(name_level, absl::MaxSplits(':', 1), absl::SkipWhitespace()); | ||
| auto [name, level] = name_level_pair; | ||
| if (name.empty() || level.empty()) { | ||
| return absl::InvalidArgumentError("empty logger name or empty logger level"); | ||
| } | ||
|
|
||
| auto level_to_use = parseLogLevel(level); | ||
| const absl::StatusOr<spdlog::level::level_enum> level_to_use = parseLogLevel(level); | ||
| if (!level_to_use.ok()) { | ||
| return level_to_use.status(); | ||
| } | ||
|
|
||
| name_levels[name] = *level_to_use; | ||
| } | ||
| } else { | ||
| // The HTML admin interface will always populate "level" and "paths" though | ||
| // they may be empty. There's a legacy non-HTML-accessible mechanism to | ||
| // set a single logger to a level, which we'll handle now. In this scenario, | ||
| // "level" and "paths" will not be populated. | ||
| if (params.size() != 1) { | ||
| return absl::InvalidArgumentError("invalid number of parameters"); | ||
| } | ||
|
|
||
| // Change particular log level by name. | ||
| auto level_to_use = parseLogLevel(value); | ||
| const auto it = params.begin(); | ||
| const std::string& key = it->first; | ||
| const std::string& value = it->second; | ||
|
|
||
| const absl::StatusOr<spdlog::level::level_enum> level_to_use = parseLogLevel(value); | ||
| if (!level_to_use.ok()) { | ||
| return level_to_use.status(); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.