unioil-loyalty-rn-app/ios/Pods/Flipper-Folly/folly/logging/LogConfigParser.cpp

602 lines
19 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/logging/LogConfigParser.h>
#include <folly/Conv.h>
#include <folly/String.h>
#include <folly/dynamic.h>
#include <folly/json.h>
#include <folly/lang/SafeAssert.h>
#include <folly/logging/LogName.h>
using std::string;
namespace folly {
namespace {
/**
* Get the type of a folly::dynamic object as a string, for inclusion in
* exception messages.
*/
std::string dynamicTypename(const dynamic& value) {
switch (value.type()) {
case dynamic::NULLT:
return "null";
case dynamic::ARRAY:
return "array";
case dynamic::BOOL:
return "boolean";
case dynamic::DOUBLE:
return "double";
case dynamic::INT64:
return "integer";
case dynamic::OBJECT:
return "object";
case dynamic::STRING:
return "string";
}
return "unknown type";
}
/**
* Parse a LogLevel from a JSON value.
*
* This accepts the log level either as an integer value or a string that can
* be parsed with stringToLogLevel()
*
* On success updates the result parameter and returns true.
* Returns false if the input is not a string or integer.
* Throws a LogConfigParseError on other errors.
*/
bool parseJsonLevel(
const dynamic& value, StringPiece categoryName, LogLevel& result) {
if (value.isString()) {
auto levelString = value.asString();
try {
result = stringToLogLevel(levelString);
return true;
} catch (const std::exception&) {
throw LogConfigParseError{to<string>(
"invalid log level \"",
levelString,
"\" for category \"",
categoryName,
"\"")};
}
} else if (value.isInt()) {
auto level = static_cast<LogLevel>(value.asInt());
if (level < LogLevel::MIN_LEVEL || level > LogLevel::MAX_LEVEL) {
throw LogConfigParseError{to<string>(
"invalid log level ",
value.asInt(),
" for category \"",
categoryName,
"\": outside of valid range")};
}
result = level;
return true;
}
return false;
}
LogCategoryConfig parseJsonCategoryConfig(
const dynamic& value, StringPiece categoryName) {
LogCategoryConfig config;
// If the input is not an object, allow it to be
// just a plain level specification
if (!value.isObject()) {
if (!parseJsonLevel(value, categoryName, config.level)) {
throw LogConfigParseError{to<string>(
"unexpected data type for configuration of category \"",
categoryName,
"\": got ",
dynamicTypename(value),
", expected an object, string, or integer")};
}
return config;
}
auto* level = value.get_ptr("level");
if (!level) {
// Require that level information be present for each log category.
throw LogConfigParseError{to<string>(
"no log level specified for category \"", categoryName, "\"")};
}
if (!parseJsonLevel(*level, categoryName, config.level)) {
throw LogConfigParseError{to<string>(
"unexpected data type for level field of category \"",
categoryName,
"\": got ",
dynamicTypename(*level),
", expected a string or integer")};
}
auto* inherit = value.get_ptr("inherit");
if (inherit) {
if (!inherit->isBool()) {
throw LogConfigParseError{to<string>(
"unexpected data type for inherit field of category \"",
categoryName,
"\": got ",
dynamicTypename(*inherit),
", expected a boolean")};
}
config.inheritParentLevel = inherit->asBool();
}
auto* propagate = value.get_ptr("propagate");
if (propagate) {
if (!parseJsonLevel(
*propagate, categoryName, config.propagateLevelMessagesToParent)) {
throw LogConfigParseError{to<string>(
"unexpected data type for propagate field of category \"",
categoryName,
"\": got ",
dynamicTypename(*propagate),
", expected a string or integer")};
}
}
auto* handlers = value.get_ptr("handlers");
if (handlers) {
if (!handlers->isArray()) {
throw LogConfigParseError{to<string>(
"the \"handlers\" field for category ",
categoryName,
" must be a list")};
}
config.handlers = std::vector<std::string>{};
for (const auto& item : *handlers) {
if (!item.isString()) {
throw LogConfigParseError{to<string>(
"the \"handlers\" list for category ",
categoryName,
" must be contain only strings")};
}
config.handlers->push_back(item.asString());
}
}
return config;
}
LogHandlerConfig parseJsonHandlerConfig(
const dynamic& value, StringPiece handlerName) {
if (!value.isObject()) {
throw LogConfigParseError{to<string>(
"unexpected data type for configuration of handler \"",
handlerName,
"\": got ",
dynamicTypename(value),
", expected an object")};
}
// Parse the handler type
auto* type = value.get_ptr("type");
if (!type) {
throw LogConfigParseError{to<string>(
"no handler type specified for log handler \"", handlerName, "\"")};
}
if (!type->isString()) {
throw LogConfigParseError{to<string>(
"unexpected data type for \"type\" field of handler \"",
handlerName,
"\": got ",
dynamicTypename(*type),
", expected a string")};
}
LogHandlerConfig config{type->asString()};
// Parse the handler options
auto* options = value.get_ptr("options");
if (options) {
if (!options->isObject()) {
throw LogConfigParseError{to<string>(
"unexpected data type for \"options\" field of handler \"",
handlerName,
"\": got ",
dynamicTypename(*options),
", expected an object")};
}
for (const auto& item : options->items()) {
if (!item.first.isString()) {
// This shouldn't really ever happen.
// We deserialize the json with allow_non_string_keys set to False.
throw LogConfigParseError{to<string>(
"unexpected data type for option of handler \"",
handlerName,
"\": got ",
dynamicTypename(item.first),
", expected string")};
}
if (!item.second.isString()) {
throw LogConfigParseError{to<string>(
"unexpected data type for option \"",
item.first.asString(),
"\" of handler \"",
handlerName,
"\": got ",
dynamicTypename(item.second),
", expected a string")};
}
config.options[item.first.asString()] = item.second.asString();
}
}
return config;
}
LogConfig::CategoryConfigMap parseCategoryConfigs(StringPiece value) {
LogConfig::CategoryConfigMap categoryConfigs;
// Allow empty (or all whitespace) input
value = trimWhitespace(value);
if (value.empty()) {
return categoryConfigs;
}
std::unordered_map<string, string> seenCategories;
std::vector<StringPiece> pieces;
folly::split(",", value, pieces);
for (const auto& piece : pieces) {
LogCategoryConfig categoryConfig;
StringPiece categoryName;
StringPiece configString;
auto equalIndex = piece.find('=');
if (equalIndex == StringPiece::npos) {
// If level information is supplied without a category name,
// apply it to the root log category.
categoryName = StringPiece{"."};
configString = trimWhitespace(piece);
} else {
categoryName = piece.subpiece(0, equalIndex);
configString = piece.subpiece(equalIndex + 1);
// If ":=" is used instead of just "=", disable inheriting the parent's
// effective level if it is lower than this category's level.
if (categoryName.endsWith(':')) {
categoryConfig.inheritParentLevel = false;
categoryName.subtract(1);
}
// Remove whitespace from the category name
categoryName = trimWhitespace(categoryName);
}
// Split the configString into level and handler information.
std::vector<StringPiece> handlerPieces;
folly::split(":", configString, handlerPieces);
FOLLY_SAFE_DCHECK(
!handlerPieces.empty(),
"folly::split() always returns a list of length 1");
auto levelString = trimWhitespace(handlerPieces[0]);
bool hasHandlerConfig = handlerPieces.size() > 1;
if (handlerPieces.size() == 2 && trimWhitespace(handlerPieces[1]).empty()) {
// This is an explicitly empty handler list.
// This requests LoggerDB::updateConfig() to clear all existing log
// handlers from this category.
categoryConfig.handlers = std::vector<std::string>{};
} else if (hasHandlerConfig) {
categoryConfig.handlers = std::vector<std::string>{};
for (size_t n = 1; n < handlerPieces.size(); ++n) {
auto handlerName = trimWhitespace(handlerPieces[n]);
if (handlerName.empty()) {
throw LogConfigParseError{to<string>(
"error parsing configuration for log category \"",
categoryName,
"\": log handler name cannot be empty")};
}
categoryConfig.handlers->push_back(handlerName.str());
}
}
// Parse the levelString into a LogLevel
levelString = trimWhitespace(levelString);
try {
categoryConfig.level = stringToLogLevel(levelString);
} catch (const std::exception&) {
throw LogConfigParseError{to<string>(
"invalid log level \"",
levelString,
"\" for category \"",
categoryName,
"\"")};
}
// Check for multiple entries for the same category with different but
// equivalent names.
auto canonicalName = LogName::canonicalize(categoryName);
auto ret = seenCategories.emplace(canonicalName, categoryName.str());
if (!ret.second) {
throw LogConfigParseError{to<string>(
"category \"",
canonicalName,
"\" listed multiple times under different names: \"",
ret.first->second,
"\" and \"",
categoryName,
"\"")};
}
auto emplaceResult =
categoryConfigs.emplace(canonicalName, std::move(categoryConfig));
FOLLY_SAFE_DCHECK(
emplaceResult.second,
"category name must be new since it was not in seenCategories");
}
return categoryConfigs;
}
bool splitNameValue(
StringPiece input, StringPiece* outName, StringPiece* outValue) {
size_t equalIndex = input.find('=');
if (equalIndex == StringPiece::npos) {
return false;
}
StringPiece name{input.begin(), input.begin() + equalIndex};
StringPiece value{input.begin() + equalIndex + 1, input.end()};
*outName = trimWhitespace(name);
*outValue = trimWhitespace(value);
return true;
}
std::pair<std::string, LogHandlerConfig> parseHandlerConfig(StringPiece value) {
// Parse the handler name and optional type
auto colonIndex = value.find(':');
StringPiece namePortion;
StringPiece optionsStr;
if (colonIndex == StringPiece::npos) {
namePortion = value;
} else {
namePortion = StringPiece{value.begin(), value.begin() + colonIndex};
optionsStr = StringPiece{value.begin() + colonIndex + 1, value.end()};
}
StringPiece handlerName;
Optional<StringPiece> handlerType(in_place);
if (!splitNameValue(namePortion, &handlerName, &handlerType.value())) {
handlerName = trimWhitespace(namePortion);
handlerType = folly::none;
}
// Make sure the handler name and type are not empty.
// Also disallow commas in the name: this helps catch accidental errors where
// the user left out the ':' and intended to be specifying options instead of
// part of the name or type.
if (handlerName.empty()) {
throw LogConfigParseError{
"error parsing log handler configuration: empty log handler name"};
}
if (handlerName.contains(',')) {
throw LogConfigParseError{to<string>(
"error parsing configuration for log handler \"",
handlerName,
"\": name cannot contain a comma when using the basic config format")};
}
if (handlerType.has_value()) {
if (handlerType->empty()) {
throw LogConfigParseError{to<string>(
"error parsing configuration for log handler \"",
handlerName,
"\": empty log handler type")};
}
if (handlerType->contains(',')) {
throw LogConfigParseError{to<string>(
"error parsing configuration for log handler \"",
handlerName,
"\": invalid type \"",
handlerType.value(),
"\": type name cannot contain a comma when using "
"the basic config format")};
}
}
// Parse the options
LogHandlerConfig config{handlerType};
optionsStr = trimWhitespace(optionsStr);
if (!optionsStr.empty()) {
std::vector<StringPiece> pieces;
folly::split(",", optionsStr, pieces);
FOLLY_SAFE_DCHECK(
!pieces.empty(), "folly::split() always returns a list of length 1");
for (const auto& piece : pieces) {
StringPiece optionName;
StringPiece optionValue;
if (!splitNameValue(piece, &optionName, &optionValue)) {
throw LogConfigParseError{to<string>(
"error parsing configuration for log handler \"",
handlerName,
"\": options must be of the form NAME=VALUE")};
}
auto ret = config.options.emplace(optionName.str(), optionValue.str());
if (!ret.second) {
throw LogConfigParseError{to<string>(
"error parsing configuration for log handler \"",
handlerName,
"\": duplicate configuration for option \"",
optionName,
"\"")};
}
}
}
return std::make_pair(handlerName.str(), std::move(config));
}
} // namespace
LogConfig parseLogConfig(StringPiece value) {
value = trimWhitespace(value);
if (value.startsWith('{')) {
return parseLogConfigJson(value);
}
// Split the input string on semicolons.
// Everything up to the first semicolon specifies log category configs.
// From then on each section specifies a single LogHandler config.
std::vector<StringPiece> pieces;
folly::split(";", value, pieces);
FOLLY_SAFE_DCHECK(
!pieces.empty(), "folly::split() always returns a list of length 1");
auto categoryConfigs = parseCategoryConfigs(pieces[0]);
LogConfig::HandlerConfigMap handlerConfigs;
for (size_t n = 1; n < pieces.size(); ++n) {
auto handlerInfo = parseHandlerConfig(pieces[n]);
auto ret = handlerConfigs.emplace(
handlerInfo.first, std::move(handlerInfo.second));
if (!ret.second) {
throw LogConfigParseError{to<string>(
"configuration for log category \"",
handlerInfo.first,
"\" specified multiple times")};
}
}
return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
}
LogConfig parseLogConfigJson(StringPiece value) {
json::serialization_opts opts;
opts.allow_trailing_comma = true;
auto jsonData = folly::parseJson(json::stripComments(value), opts);
return parseLogConfigDynamic(jsonData);
}
LogConfig parseLogConfigDynamic(const dynamic& value) {
if (!value.isObject()) {
throw LogConfigParseError{"JSON config input must be an object"};
}
std::unordered_map<string, string> seenCategories;
LogConfig::CategoryConfigMap categoryConfigs;
auto* categories = value.get_ptr("categories");
if (categories) {
if (!categories->isObject()) {
throw LogConfigParseError{to<string>(
"unexpected data type for log categories config: got ",
dynamicTypename(*categories),
", expected an object")};
}
for (const auto& entry : categories->items()) {
if (!entry.first.isString()) {
// This shouldn't really ever happen.
// We deserialize the json with allow_non_string_keys set to False.
throw LogConfigParseError{"category name must be a string"};
}
auto categoryName = entry.first.asString();
auto categoryConfig = parseJsonCategoryConfig(entry.second, categoryName);
// Check for multiple entries for the same category with different but
// equivalent names.
auto canonicalName = LogName::canonicalize(categoryName);
auto ret = seenCategories.emplace(canonicalName, categoryName);
if (!ret.second) {
throw LogConfigParseError{to<string>(
"category \"",
canonicalName,
"\" listed multiple times under different names: \"",
ret.first->second,
"\" and \"",
categoryName,
"\"")};
}
categoryConfigs[canonicalName] = std::move(categoryConfig);
}
}
LogConfig::HandlerConfigMap handlerConfigs;
auto* handlers = value.get_ptr("handlers");
if (handlers) {
if (!handlers->isObject()) {
throw LogConfigParseError{to<string>(
"unexpected data type for log handlers config: got ",
dynamicTypename(*handlers),
", expected an object")};
}
for (const auto& entry : handlers->items()) {
if (!entry.first.isString()) {
// This shouldn't really ever happen.
// We deserialize the json with allow_non_string_keys set to False.
throw LogConfigParseError{"handler name must be a string"};
}
auto handlerName = entry.first.asString();
handlerConfigs.emplace(
handlerName, parseJsonHandlerConfig(entry.second, handlerName));
}
}
return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
}
dynamic logConfigToDynamic(const LogConfig& config) {
dynamic categories = dynamic::object;
for (const auto& entry : config.getCategoryConfigs()) {
categories.insert(entry.first, logConfigToDynamic(entry.second));
}
dynamic handlers = dynamic::object;
for (const auto& entry : config.getHandlerConfigs()) {
handlers.insert(entry.first, logConfigToDynamic(entry.second));
}
return dynamic::object("categories", std::move(categories))(
"handlers", std::move(handlers));
}
dynamic logConfigToDynamic(const LogHandlerConfig& config) {
dynamic options = dynamic::object;
for (const auto& opt : config.options) {
options.insert(opt.first, opt.second);
}
auto result = dynamic::object("options", options);
if (config.type.has_value()) {
result("type", config.type.value());
}
return result;
}
dynamic logConfigToDynamic(const LogCategoryConfig& config) {
auto value = dynamic::object("level", logLevelToString(config.level))(
"inherit", config.inheritParentLevel)(
"propagate", logLevelToString(config.propagateLevelMessagesToParent));
if (config.handlers.has_value()) {
auto handlers = dynamic::array();
for (const auto& handlerName : config.handlers.value()) {
handlers.push_back(handlerName);
}
value("handlers", std::move(handlers));
}
return value;
}
} // namespace folly