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
10735
absl::flat_hash_map<absl::string_view, spdlog::level::level_enum> buildLevelMap() {
17
10735
  absl::flat_hash_map<absl::string_view, spdlog::level::level_enum> levels;
18

            
19
10735
  uint32_t i = 0;
20
75145
  for (absl::string_view level_string : LogsHandler::levelStrings()) {
21
75145
    levels[level_string] = static_cast<spdlog::level::level_enum>(i++);
22
75145
  }
23

            
24
10735
  return levels;
25
10735
}
26
} // namespace
27

            
28
LogsHandler::LogsHandler(Server::Instance& server)
29
10735
    : HandlerContextBase(server), log_levels_(buildLevelMap()) {}
30

            
31
21470
std::vector<absl::string_view> LogsHandler::levelStrings() {
32
21470
  std::vector<absl::string_view> strings;
33
21470
  strings.reserve(ARRAY_SIZE(spdlog::level::level_string_views));
34
150290
  for (spdlog::string_view_t level : spdlog::level::level_string_views) {
35
150290
    strings.emplace_back(absl::string_view{level.data(), level.size()});
36
150290
  }
37
21470
  return strings;
38
21470
}
39

            
40
Http::Code LogsHandler::handlerLogging(Http::ResponseHeaderMap&, Buffer::Instance& response,
41
59
                                       AdminStream& admin_stream) {
42
59
  Http::Utility::QueryParamsMulti query_params = admin_stream.queryParams();
43

            
44
59
  Http::Code rc = Http::Code::OK;
45
59
  const absl::Status status = changeLogLevel(query_params);
46
59
  if (!status.ok()) {
47
25
    rc = Http::Code::BadRequest;
48
25
    response.add(fmt::format("error: {}\n\n", status.message()));
49

            
50
25
    response.add("usage: /logging?<name>=<level> (change single level)\n");
51
25
    response.add("usage: /logging?paths=name1:level1,name2:level2,... (change multiple levels)\n");
52
25
    response.add("usage: /logging?level=<level> (change all levels)\n");
53
25
    response.add("levels: ");
54
175
    for (auto level_string_view : spdlog::level::level_string_views) {
55
175
      response.add(fmt::format("{} ", level_string_view));
56
175
    }
57

            
58
25
    response.add("\n");
59
25
  }
60

            
61
59
  if (!Logger::Context::useFineGrainLogger()) {
62
39
    response.add("active loggers:\n");
63
2769
    for (const Logger::Logger& logger : Logger::Registry::loggers()) {
64
2769
      response.add(fmt::format("  {}: {}\n", logger.name(), logger.levelString()));
65
2769
    }
66

            
67
39
    response.add("\n");
68
59
  } else {
69
20
    response.add("active loggers:\n");
70
20
    std::string logger_info = getFineGrainLogContext().listFineGrainLoggers();
71
20
    response.add(logger_info);
72
20
    response.add("\n");
73
20
  }
74

            
75
59
  return rc;
76
59
}
77

            
78
Http::Code LogsHandler::handlerReopenLogs(Http::ResponseHeaderMap&, Buffer::Instance& response,
79
1
                                          AdminStream&) {
80
1
  server_.accessLogManager().reopen();
81
1
  response.add("OK\n");
82
1
  return Http::Code::OK;
83
1
}
84

            
85
59
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
59
  auto level = params.getFirstValue("level");
89
59
  if (level.has_value() && level.value().empty()) {
90
1
    params.remove("level");
91
1
    level = std::nullopt;
92
1
  }
93
59
  auto paths = params.getFirstValue("paths");
94
59
  if (paths.has_value() && paths.value().empty()) {
95
1
    params.remove("paths");
96
1
    paths = std::nullopt;
97
1
  }
98

            
99
59
  if (params.data().empty()) {
100
6
    return absl::OkStatus();
101
6
  }
102

            
103
53
  if (params.data().size() != 1) {
104
4
    return absl::InvalidArgumentError("invalid number of parameters");
105
4
  }
106

            
107
49
  if (level.has_value()) {
108
    // Change all log levels.
109
13
    const absl::StatusOr<spdlog::level::level_enum> level_to_use = parseLogLevel(level.value());
110
13
    if (!level_to_use.ok()) {
111
3
      return level_to_use.status();
112
3
    }
113

            
114
10
    Logger::Context::changeAllLogLevels(*level_to_use);
115
10
    return absl::OkStatus();
116
13
  }
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
36
  absl::flat_hash_map<absl::string_view, spdlog::level::level_enum> name_levels;
121
36
  std::vector<std::pair<absl::string_view, int>> glob_levels;
122
36
  const bool use_fine_grain_logger = Logger::Context::useFineGrainLogger();
123

            
124
36
  if (paths.has_value()) {
125
    // Bulk change log level by name:level pairs, separated by comma.
126
24
    std::vector<absl::string_view> pairs =
127
24
        absl::StrSplit(paths.value(), ',', absl::SkipWhitespace());
128
62
    for (const auto& name_level : pairs) {
129
62
      const std::pair<absl::string_view, absl::string_view> name_level_pair =
130
62
          absl::StrSplit(name_level, absl::MaxSplits(':', 1), absl::SkipWhitespace());
131
62
      auto [name, level] = name_level_pair;
132
62
      if (name.empty() || level.empty()) {
133
6
        return absl::InvalidArgumentError("empty logger name or empty logger level");
134
6
      }
135

            
136
56
      const absl::StatusOr<spdlog::level::level_enum> level_to_use = parseLogLevel(level);
137
56
      if (!level_to_use.ok()) {
138
3
        return level_to_use.status();
139
3
      }
140

            
141
53
      if (use_fine_grain_logger) {
142
14
        ENVOY_LOG(info, "adding fine-grain log update, glob='{}' level='{}'", name,
143
14
                  spdlog::level::level_string_views[*level_to_use]);
144
14
        glob_levels.emplace_back(name, *level_to_use);
145
53
      } else {
146
39
        name_levels[name] = *level_to_use;
147
39
      }
148
53
    }
149
24
  } 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
12
    if (params.data().size() != 1) {
155
      return absl::InvalidArgumentError("invalid number of parameters");
156
    }
157

            
158
    // Change particular log level by name.
159
12
    const auto it = params.data().begin();
160
12
    const std::string& key = it->first;
161
12
    const std::string& value = it->second[0];
162

            
163
12
    const absl::StatusOr<spdlog::level::level_enum> level_to_use = parseLogLevel(value);
164
12
    if (!level_to_use.ok()) {
165
3
      return level_to_use.status();
166
3
    }
167

            
168
9
    if (use_fine_grain_logger) {
169
3
      ENVOY_LOG(info, "adding fine-grain log update, glob='{}' level='{}'", key,
170
3
                spdlog::level::level_string_views[*level_to_use]);
171
3
      glob_levels.emplace_back(key, *level_to_use);
172
9
    } else {
173
6
      name_levels[key] = *level_to_use;
174
6
    }
175
9
  }
176

            
177
24
  if (!use_fine_grain_logger) {
178
12
    return changeLogLevelsForComponentLoggers(name_levels);
179
12
  }
180
12
  getFineGrainLogContext().updateVerbositySetting(glob_levels);
181

            
182
12
  return absl::OkStatus();
183
24
}
184

            
185
absl::Status LogsHandler::changeLogLevelsForComponentLoggers(
186
12
    const absl::flat_hash_map<absl::string_view, spdlog::level::level_enum>& changes) {
187
12
  std::vector<std::pair<Logger::Logger*, spdlog::level::level_enum>> loggers_to_change;
188
852
  for (Logger::Logger& logger : Logger::Registry::loggers()) {
189
852
    auto name_level_itr = changes.find(logger.name());
190
852
    if (name_level_itr == changes.end()) {
191
831
      continue;
192
831
    }
193

            
194
21
    loggers_to_change.emplace_back(std::make_pair(&logger, name_level_itr->second));
195
21
  }
196

            
197
  // Check if we have any invalid logger in changes.
198
12
  if (loggers_to_change.size() != changes.size()) {
199
6
    return absl::InvalidArgumentError("unknown logger name");
200
6
  }
201

            
202
12
  for (auto& it : loggers_to_change) {
203
12
    Logger::Logger* logger = it.first;
204
12
    spdlog::level::level_enum level = it.second;
205

            
206
12
    ENVOY_LOG(info, "change log level: name='{}' level='{}'", logger->name(),
207
12
              spdlog::level::level_string_views[level]);
208
12
    logger->setLevel(level);
209
12
  }
210

            
211
6
  return absl::OkStatus();
212
12
}
213

            
214
} // namespace Server
215
} // namespace Envoy