2 * Copyright 2004-present Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 #include <folly/experimental/logging/LogConfigParser.h>
18 #include <folly/Conv.h>
19 #include <folly/String.h>
20 #include <folly/dynamic.h>
21 #include <folly/experimental/logging/LogName.h>
22 #include <folly/json.h>
25 using std::shared_ptr;
33 * Get the type of a folly::dynamic object as a string, for inclusion in
36 std::string dynamicTypename(const dynamic& value) {
37 switch (value.type()) {
53 return "unknown type";
57 * Parse a LogLevel from a JSON value.
59 * This accepts the log level either as an integer value or a string that can
60 * be parsed with stringToLogLevel()
62 * On success updates the result parameter and returns true.
63 * Returns false if the input is not a string or integer.
64 * Throws a LogConfigParseError on other errors.
68 StringPiece categoryName,
70 if (value.isString()) {
71 auto levelString = value.asString();
73 result = stringToLogLevel(levelString);
75 } catch (const std::exception& ex) {
76 throw LogConfigParseError{to<string>(
77 "invalid log level \"",
83 } else if (value.isInt()) {
84 auto level = static_cast<LogLevel>(value.asInt());
85 if (level < LogLevel::MIN_LEVEL || level > LogLevel::MAX_LEVEL) {
86 throw LogConfigParseError{to<string>(
91 "\": outside of valid range")};
100 LogCategoryConfig parseJsonCategoryConfig(
101 const dynamic& value,
102 StringPiece categoryName) {
103 LogCategoryConfig config;
105 // If the input is not an object, allow it to be
106 // just a plain level specification
107 if (!value.isObject()) {
108 if (!parseJsonLevel(value, categoryName, config.level)) {
109 throw LogConfigParseError{to<string>(
110 "unexpected data type for configuration of category \"",
113 dynamicTypename(value),
114 ", expected an object, string, or integer")};
119 auto* level = value.get_ptr("level");
121 // Require that level information be present for each log category.
122 throw LogConfigParseError{to<string>(
123 "no log level specified for category \"", categoryName, "\"")};
125 if (!parseJsonLevel(*level, categoryName, config.level)) {
126 throw LogConfigParseError{to<string>(
127 "unexpected data type for level field of category \"",
130 dynamicTypename(*level),
131 ", expected a string or integer")};
134 auto* inherit = value.get_ptr("inherit");
136 if (!inherit->isBool()) {
137 throw LogConfigParseError{to<string>(
138 "unexpected data type for inherit field of category \"",
141 dynamicTypename(*inherit),
142 ", expected a boolean")};
144 config.inheritParentLevel = inherit->asBool();
147 auto* handlers = value.get_ptr("handlers");
149 if (!handlers->isArray()) {
150 throw LogConfigParseError{to<string>(
151 "the \"handlers\" field for category ",
155 config.handlers = std::vector<std::string>{};
156 for (const auto& item : *handlers) {
157 if (!item.isString()) {
158 throw LogConfigParseError{to<string>(
159 "the \"handlers\" list for category ",
161 " must be contain only strings")};
163 config.handlers->push_back(item.asString());
170 LogHandlerConfig parseJsonHandlerConfig(
171 const dynamic& value,
172 StringPiece handlerName) {
173 if (!value.isObject()) {
174 throw LogConfigParseError{to<string>(
175 "unexpected data type for configuration of handler \"",
178 dynamicTypename(value),
179 ", expected an object")};
182 // Parse the handler type
183 auto* type = value.get_ptr("type");
185 throw LogConfigParseError{to<string>(
186 "no handler type specified for log handler \"", handlerName, "\"")};
188 if (!type->isString()) {
189 throw LogConfigParseError{to<string>(
190 "unexpected data type for \"type\" field of handler \"",
193 dynamicTypename(*type),
194 ", expected a string")};
196 LogHandlerConfig config{type->asString()};
198 // Parse the handler options
199 auto* options = value.get_ptr("options");
201 if (!options->isObject()) {
202 throw LogConfigParseError{to<string>(
203 "unexpected data type for \"options\" field of handler \"",
206 dynamicTypename(*options),
207 ", expected an object")};
210 for (const auto& item : options->items()) {
211 if (!item.first.isString()) {
212 // This shouldn't really ever happen.
213 // We deserialize the json with allow_non_string_keys set to False.
214 throw LogConfigParseError{to<string>(
215 "unexpected data type for option of handler \"",
218 dynamicTypename(item.first),
219 ", expected string")};
221 if (!item.second.isString()) {
222 throw LogConfigParseError{to<string>(
223 "unexpected data type for option \"",
224 item.first.asString(),
228 dynamicTypename(item.second),
229 ", expected a string")};
231 config.options[item.first.asString()] = item.second.asString();
238 LogConfig::CategoryConfigMap parseCategoryConfigs(StringPiece value) {
239 LogConfig::CategoryConfigMap categoryConfigs;
241 // Allow empty (or all whitespace) input
242 value = trimWhitespace(value);
244 return categoryConfigs;
247 std::unordered_map<string, string> seenCategories;
248 std::vector<StringPiece> pieces;
249 folly::split(",", value, pieces);
250 for (const auto& piece : pieces) {
251 LogCategoryConfig categoryConfig;
252 StringPiece categoryName;
253 StringPiece configString;
255 auto equalIndex = piece.find('=');
256 if (equalIndex == StringPiece::npos) {
257 // If level information is supplied without a category name,
258 // apply it to the root log category.
259 categoryName = StringPiece{"."};
260 configString = trimWhitespace(piece);
262 categoryName = piece.subpiece(0, equalIndex);
263 configString = piece.subpiece(equalIndex + 1);
265 // If ":=" is used instead of just "=", disable inheriting the parent's
266 // effective level if it is lower than this category's level.
267 if (categoryName.endsWith(':')) {
268 categoryConfig.inheritParentLevel = false;
269 categoryName.subtract(1);
272 // Remove whitespace from the category name
273 categoryName = trimWhitespace(categoryName);
276 // Split the configString into level and handler information.
277 std::vector<StringPiece> handlerPieces;
278 folly::split(":", configString, handlerPieces);
279 // folly::split() always returns a list of length 1
280 assert(handlerPieces.size() >= 1);
281 auto levelString = trimWhitespace(handlerPieces[0]);
283 bool hasHandlerConfig = handlerPieces.size() > 1;
284 if (handlerPieces.size() == 2 && trimWhitespace(handlerPieces[1]).empty()) {
285 // This is an explicitly empty handler list.
286 // This requests LoggerDB::updateConfig() to clear all existing log
287 // handlers from this category.
288 categoryConfig.handlers = std::vector<std::string>{};
289 } else if (hasHandlerConfig) {
290 categoryConfig.handlers = std::vector<std::string>{};
291 for (size_t n = 1; n < handlerPieces.size(); ++n) {
292 auto handlerName = trimWhitespace(handlerPieces[n]);
293 if (handlerName.empty()) {
294 throw LogConfigParseError{to<string>(
295 "error parsing configuration for log category \"",
297 "\": log handler name cannot be empty")};
299 categoryConfig.handlers->push_back(handlerName.str());
303 // Parse the levelString into a LogLevel
304 levelString = trimWhitespace(levelString);
306 categoryConfig.level = stringToLogLevel(levelString);
307 } catch (const std::exception&) {
308 throw LogConfigParseError{to<string>(
309 "invalid log level \"",
311 "\" for category \"",
316 // Check for multiple entries for the same category with different but
318 auto canonicalName = LogName::canonicalize(categoryName);
319 auto ret = seenCategories.emplace(canonicalName, categoryName.str());
321 throw LogConfigParseError{to<string>(
324 "\" listed multiple times under different names: \"",
332 categoryConfigs.emplace(canonicalName, std::move(categoryConfig));
333 assert(emplaceResult.second);
336 return categoryConfigs;
341 StringPiece* outName,
342 StringPiece* outValue) {
343 size_t equalIndex = input.find('=');
344 if (equalIndex == StringPiece::npos) {
348 StringPiece name{input.begin(), input.begin() + equalIndex};
349 StringPiece value{input.begin() + equalIndex + 1, input.end()};
351 *outName = trimWhitespace(name);
352 *outValue = trimWhitespace(value);
356 std::pair<std::string, LogHandlerConfig> parseHandlerConfig(StringPiece value) {
357 std::vector<StringPiece> pieces;
358 folly::split(",", value, pieces);
359 // "folly::split() always returns a list of length 1";
360 assert(pieces.size() >= 1);
362 StringPiece handlerName;
363 StringPiece handlerType;
364 if (!splitNameValue(pieces[0], &handlerName, &handlerType)) {
365 throw LogConfigParseError{to<string>(
366 "error parsing log handler configuration \"",
368 "\": expected data in the form NAME=TYPE")};
370 if (handlerName.empty()) {
371 throw LogConfigParseError{
372 "error parsing log handler configuration: empty log handler name"};
374 if (handlerType.empty()) {
375 throw LogConfigParseError{to<string>(
376 "error parsing configuration for log handler \"",
378 "\": empty log handler type")};
381 LogHandlerConfig config{handlerType};
382 for (size_t n = 1; n < pieces.size(); ++n) {
383 StringPiece optionName;
384 StringPiece optionValue;
385 if (!splitNameValue(pieces[n], &optionName, &optionValue)) {
386 throw LogConfigParseError{to<string>(
387 "error parsing configuration for log handler \"",
389 "\": options must be of the form NAME=VALUE")};
392 auto ret = config.options.emplace(optionName.str(), optionValue.str());
394 throw LogConfigParseError{to<string>(
395 "error parsing configuration for log handler \"",
397 "\": duplicate configuration for option \"",
403 return std::make_pair(handlerName.str(), std::move(config));
408 LogConfig parseLogConfig(StringPiece value) {
409 value = trimWhitespace(value);
410 if (value.startsWith('{')) {
411 return parseLogConfigJson(value);
414 // Split the input string on semicolons.
415 // Everything up to the first semicolon specifies log category configs.
416 // From then on each section specifies a single LogHandler config.
417 std::vector<StringPiece> pieces;
418 folly::split(";", value, pieces);
419 // "folly::split() always returns a list of length 1";
420 assert(pieces.size() >= 1);
422 auto categoryConfigs = parseCategoryConfigs(pieces[0]);
423 LogConfig::HandlerConfigMap handlerConfigs;
424 for (size_t n = 1; n < pieces.size(); ++n) {
425 auto handlerInfo = parseHandlerConfig(pieces[n]);
426 auto ret = handlerConfigs.emplace(
427 handlerInfo.first, std::move(handlerInfo.second));
429 throw LogConfigParseError{to<string>(
430 "configuration for log category \"",
432 "\" specified multiple times")};
436 return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
439 LogConfig parseLogConfigJson(StringPiece value) {
440 json::serialization_opts opts;
441 opts.allow_trailing_comma = true;
442 auto jsonData = folly::parseJson(json::stripComments(value), opts);
443 return parseLogConfigDynamic(jsonData);
446 LogConfig parseLogConfigDynamic(const dynamic& value) {
447 if (!value.isObject()) {
448 throw LogConfigParseError{"JSON config input must be an object"};
451 std::unordered_map<string, string> seenCategories;
452 LogConfig::CategoryConfigMap categoryConfigs;
453 auto* categories = value.get_ptr("categories");
455 if (!categories->isObject()) {
456 throw LogConfigParseError{to<string>(
457 "unexpected data type for log categories config: got ",
458 dynamicTypename(*categories),
459 ", expected an object")};
462 for (const auto& entry : categories->items()) {
463 if (!entry.first.isString()) {
464 // This shouldn't really ever happen.
465 // We deserialize the json with allow_non_string_keys set to False.
466 throw LogConfigParseError{"category name must be a string"};
468 auto categoryName = entry.first.asString();
469 auto categoryConfig = parseJsonCategoryConfig(entry.second, categoryName);
471 // Check for multiple entries for the same category with different but
473 auto canonicalName = LogName::canonicalize(categoryName);
474 auto ret = seenCategories.emplace(canonicalName, categoryName);
476 throw LogConfigParseError{to<string>(
479 "\" listed multiple times under different names: \"",
486 categoryConfigs[canonicalName] = std::move(categoryConfig);
490 LogConfig::HandlerConfigMap handlerConfigs;
491 auto* handlers = value.get_ptr("handlers");
493 if (!handlers->isObject()) {
494 throw LogConfigParseError{to<string>(
495 "unexpected data type for log handlers config: got ",
496 dynamicTypename(*handlers),
497 ", expected an object")};
500 for (const auto& entry : handlers->items()) {
501 if (!entry.first.isString()) {
502 // This shouldn't really ever happen.
503 // We deserialize the json with allow_non_string_keys set to False.
504 throw LogConfigParseError{"handler name must be a string"};
506 auto handlerName = entry.first.asString();
507 handlerConfigs.emplace(
508 handlerName, parseJsonHandlerConfig(entry.second, handlerName));
512 return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
515 dynamic logConfigToDynamic(const LogConfig& config) {
516 dynamic categories = dynamic::object;
517 for (const auto& entry : config.getCategoryConfigs()) {
518 categories.insert(entry.first, logConfigToDynamic(entry.second));
521 dynamic handlers = dynamic::object;
522 for (const auto& entry : config.getHandlerConfigs()) {
523 handlers.insert(entry.first, logConfigToDynamic(entry.second));
526 return dynamic::object("categories", std::move(categories))(
527 "handlers", std::move(handlers));
530 dynamic logConfigToDynamic(const LogHandlerConfig& config) {
531 dynamic options = dynamic::object;
532 for (const auto& opt : config.options) {
533 options.insert(opt.first, opt.second);
535 return dynamic::object("type", config.type)("options", options);
538 dynamic logConfigToDynamic(const LogCategoryConfig& config) {
539 auto value = dynamic::object("level", logLevelToString(config.level))(
540 "inherit", config.inheritParentLevel);
541 if (config.handlers.hasValue()) {
542 auto handlers = dynamic::array();
543 for (const auto& handlerName : config.handlers.value()) {
544 handlers.push_back(handlerName);
546 value("handlers", std::move(handlers));