Coverage Report

Created: 2024-09-19 09:45

/proc/self/cwd/source/server/admin/logs_handler.cc
Line
Count
Source (jump to first uncovered line)
1
#include "source/server/admin/logs_handler.h"
2
3
#include <string>
4
5
#include "source/common/common/fine_grain_logger.h"
6
#include "source/common/common/logger.h"
7
#include "source/server/admin/utils.h"
8
9
#include "absl/strings/str_split.h"
10
11
namespace Envoy {
12
namespace Server {
13
14
namespace {
15
// Build the level string to level enum map.
16
4.47k
absl::flat_hash_map<absl::string_view, spdlog::level::level_enum> buildLevelMap() {
17
4.47k
  absl::flat_hash_map<absl::string_view, spdlog::level::level_enum> levels;
18
19
4.47k
  uint32_t i = 0;
20
31.3k
  for (absl::string_view level_string : LogsHandler::levelStrings()) {
21
31.3k
    levels[level_string] = static_cast<spdlog::level::level_enum>(i++);
22
31.3k
  }
23
24
4.47k
  return levels;
25
4.47k
}
26
} // namespace
27
28
LogsHandler::LogsHandler(Server::Instance& server)
29
4.47k
    : HandlerContextBase(server), log_levels_(buildLevelMap()) {}
30
31
8.94k
std::vector<absl::string_view> LogsHandler::levelStrings() {
32
8.94k
  std::vector<absl::string_view> strings;
33
8.94k
  strings.reserve(ARRAY_SIZE(spdlog::level::level_string_views));
34
62.6k
  for (spdlog::string_view_t level : spdlog::level::level_string_views) {
35
62.6k
    strings.emplace_back(absl::string_view{level.data(), level.size()});
36
62.6k
  }
37
8.94k
  return strings;
38
8.94k
}
39
40
Http::Code LogsHandler::handlerLogging(Http::ResponseHeaderMap&, Buffer::Instance& response,
41
0
                                       AdminStream& admin_stream) {
42
0
  Http::Utility::QueryParamsMulti query_params = admin_stream.queryParams();
43
44
0
  Http::Code rc = Http::Code::OK;
45
0
  const absl::Status status = changeLogLevel(query_params);
46
0
  if (!status.ok()) {
47
0
    rc = Http::Code::BadRequest;
48
0
    response.add(fmt::format("error: {}\n\n", status.message()));
49
50
0
    response.add("usage: /logging?<name>=<level> (change single level)\n");
51
0
    response.add("usage: /logging?paths=name1:level1,name2:level2,... (change multiple levels)\n");
52
0
    response.add("usage: /logging?level=<level> (change all levels)\n");
53
0
    response.add("levels: ");
54
0
    for (auto level_string_view : spdlog::level::level_string_views) {
55
0
      response.add(fmt::format("{} ", level_string_view));
56
0
    }
57
58
0
    response.add("\n");
59
0
  }
60
61
0
  if (!Logger::Context::useFineGrainLogger()) {
62
0
    response.add("active loggers:\n");
63
0
    for (const Logger::Logger& logger : Logger::Registry::loggers()) {
64
0
      response.add(fmt::format("  {}: {}\n", logger.name(), logger.levelString()));
65
0
    }
66
67
0
    response.add("\n");
68
0
  } else {
69
0
    response.add("active loggers:\n");
70
0
    std::string logger_info = getFineGrainLogContext().listFineGrainLoggers();
71
0
    response.add(logger_info);
72
0
    response.add("\n");
73
0
  }
74
75
0
  return rc;
76
0
}
77
78
Http::Code LogsHandler::handlerReopenLogs(Http::ResponseHeaderMap&, Buffer::Instance& response,
79
0
                                          AdminStream&) {
80
0
  server_.accessLogManager().reopen();
81
0
  response.add("OK\n");
82
0
  return Http::Code::OK;
83
0
}
84
85
0
absl::Status LogsHandler::changeLogLevel(Http::Utility::QueryParamsMulti& params) {
86
  // "level" and "paths" will be set to the empty string when this is invoked
87
  // from HTML without setting them, so clean out empty values.
88
0
  auto level = params.getFirstValue("level");
89
0
  if (level.has_value() && level.value().empty()) {
90
0
    params.remove("level");
91
0
    level = std::nullopt;
92
0
  }
93
0
  auto paths = params.getFirstValue("paths");
94
0
  if (paths.has_value() && paths.value().empty()) {
95
0
    params.remove("paths");
96
0
    paths = std::nullopt;
97
0
  }
98
99
0
  if (params.data().empty()) {
100
0
    return absl::OkStatus();
101
0
  }
102
103
0
  if (params.data().size() != 1) {
104
0
    return absl::InvalidArgumentError("invalid number of parameters");
105
0
  }
106
107
0
  if (level.has_value()) {
108
    // Change all log levels.
109
0
    const absl::StatusOr<spdlog::level::level_enum> level_to_use = parseLogLevel(level.value());
110
0
    if (!level_to_use.ok()) {
111
0
      return level_to_use.status();
112
0
    }
113
114
0
    Logger::Context::changeAllLogLevels(*level_to_use);
115
0
    return absl::OkStatus();
116
0
  }
117
118
  // Build a map of name:level pairs, a few allocations is ok here since it's
119
  // not common to call this function at a high rate.
120
0
  absl::flat_hash_map<absl::string_view, spdlog::level::level_enum> name_levels;
121
0
  std::vector<std::pair<absl::string_view, int>> glob_levels;
122
0
  const bool use_fine_grain_logger = Logger::Context::useFineGrainLogger();
123
124
0
  if (paths.has_value()) {
125
    // Bulk change log level by name:level pairs, separated by comma.
126
0
    std::vector<absl::string_view> pairs =
127
0
        absl::StrSplit(paths.value(), ',', absl::SkipWhitespace());
128
0
    for (const auto& name_level : pairs) {
129
0
      const std::pair<absl::string_view, absl::string_view> name_level_pair =
130
0
          absl::StrSplit(name_level, absl::MaxSplits(':', 1), absl::SkipWhitespace());
131
0
      auto [name, level] = name_level_pair;
132
0
      if (name.empty() || level.empty()) {
133
0
        return absl::InvalidArgumentError("empty logger name or empty logger level");
134
0
      }
135
136
0
      const absl::StatusOr<spdlog::level::level_enum> level_to_use = parseLogLevel(level);
137
0
      if (!level_to_use.ok()) {
138
0
        return level_to_use.status();
139
0
      }
140
141
0
      if (use_fine_grain_logger) {
142
0
        ENVOY_LOG(info, "adding fine-grain log update, glob='{}' level='{}'", name,
143
0
                  spdlog::level::level_string_views[*level_to_use]);
144
0
        glob_levels.emplace_back(name, *level_to_use);
145
0
      } else {
146
0
        name_levels[name] = *level_to_use;
147
0
      }
148
0
    }
149
0
  } else {
150
    // The HTML admin interface will always populate "level" and "paths" though
151
    // they may be empty. There's a legacy non-HTML-accessible mechanism to
152
    // set a single logger to a level, which we'll handle now. In this scenario,
153
    // "level" and "paths" will not be populated.
154
0
    if (params.data().size() != 1) {
155
0
      return absl::InvalidArgumentError("invalid number of parameters");
156
0
    }
157
158
    // Change particular log level by name.
159
0
    const auto it = params.data().begin();
160
0
    const std::string& key = it->first;
161
0
    const std::string& value = it->second[0];
162
163
0
    const absl::StatusOr<spdlog::level::level_enum> level_to_use = parseLogLevel(value);
164
0
    if (!level_to_use.ok()) {
165
0
      return level_to_use.status();
166
0
    }
167
168
0
    if (use_fine_grain_logger) {
169
0
      ENVOY_LOG(info, "adding fine-grain log update, glob='{}' level='{}'", key,
170
0
                spdlog::level::level_string_views[*level_to_use]);
171
0
      glob_levels.emplace_back(key, *level_to_use);
172
0
    } else {
173
0
      name_levels[key] = *level_to_use;
174
0
    }
175
0
  }
176
177
0
  if (!use_fine_grain_logger) {
178
0
    return changeLogLevelsForComponentLoggers(name_levels);
179
0
  }
180
0
  getFineGrainLogContext().updateVerbositySetting(glob_levels);
181
182
0
  return absl::OkStatus();
183
0
}
184
185
absl::Status LogsHandler::changeLogLevelsForComponentLoggers(
186
0
    const absl::flat_hash_map<absl::string_view, spdlog::level::level_enum>& changes) {
187
0
  std::vector<std::pair<Logger::Logger*, spdlog::level::level_enum>> loggers_to_change;
188
0
  for (Logger::Logger& logger : Logger::Registry::loggers()) {
189
0
    auto name_level_itr = changes.find(logger.name());
190
0
    if (name_level_itr == changes.end()) {
191
0
      continue;
192
0
    }
193
194
0
    loggers_to_change.emplace_back(std::make_pair(&logger, name_level_itr->second));
195
0
  }
196
197
  // Check if we have any invalid logger in changes.
198
0
  if (loggers_to_change.size() != changes.size()) {
199
0
    return absl::InvalidArgumentError("unknown logger name");
200
0
  }
201
202
0
  for (auto& it : loggers_to_change) {
203
0
    Logger::Logger* logger = it.first;
204
0
    spdlog::level::level_enum level = it.second;
205
206
0
    ENVOY_LOG(info, "change log level: name='{}' level='{}'", logger->name(),
207
0
              spdlog::level::level_string_views[level]);
208
0
    logger->setLevel(level);
209
0
  }
210
211
0
  return absl::OkStatus();
212
0
}
213
214
} // namespace Server
215
} // namespace Envoy