LogConfigParser.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /*
  2. * Copyright 2017-present Facebook, Inc.
  3. *
  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
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  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.
  15. */
  16. #include <folly/logging/LogConfigParser.h>
  17. #include <folly/Conv.h>
  18. #include <folly/String.h>
  19. #include <folly/dynamic.h>
  20. #include <folly/json.h>
  21. #include <folly/lang/SafeAssert.h>
  22. #include <folly/logging/LogName.h>
  23. using std::shared_ptr;
  24. using std::string;
  25. namespace folly {
  26. namespace {
  27. /**
  28. * Get the type of a folly::dynamic object as a string, for inclusion in
  29. * exception messages.
  30. */
  31. std::string dynamicTypename(const dynamic& value) {
  32. switch (value.type()) {
  33. case dynamic::NULLT:
  34. return "null";
  35. case dynamic::ARRAY:
  36. return "array";
  37. case dynamic::BOOL:
  38. return "boolean";
  39. case dynamic::DOUBLE:
  40. return "double";
  41. case dynamic::INT64:
  42. return "integer";
  43. case dynamic::OBJECT:
  44. return "object";
  45. case dynamic::STRING:
  46. return "string";
  47. }
  48. return "unknown type";
  49. }
  50. /**
  51. * Parse a LogLevel from a JSON value.
  52. *
  53. * This accepts the log level either as an integer value or a string that can
  54. * be parsed with stringToLogLevel()
  55. *
  56. * On success updates the result parameter and returns true.
  57. * Returns false if the input is not a string or integer.
  58. * Throws a LogConfigParseError on other errors.
  59. */
  60. bool parseJsonLevel(
  61. const dynamic& value,
  62. StringPiece categoryName,
  63. LogLevel& result) {
  64. if (value.isString()) {
  65. auto levelString = value.asString();
  66. try {
  67. result = stringToLogLevel(levelString);
  68. return true;
  69. } catch (const std::exception&) {
  70. throw LogConfigParseError{to<string>(
  71. "invalid log level \"",
  72. levelString,
  73. "\" for category \"",
  74. categoryName,
  75. "\"")};
  76. }
  77. } else if (value.isInt()) {
  78. auto level = static_cast<LogLevel>(value.asInt());
  79. if (level < LogLevel::MIN_LEVEL || level > LogLevel::MAX_LEVEL) {
  80. throw LogConfigParseError{to<string>(
  81. "invalid log level ",
  82. value.asInt(),
  83. " for category \"",
  84. categoryName,
  85. "\": outside of valid range")};
  86. }
  87. result = level;
  88. return true;
  89. }
  90. return false;
  91. }
  92. LogCategoryConfig parseJsonCategoryConfig(
  93. const dynamic& value,
  94. StringPiece categoryName) {
  95. LogCategoryConfig config;
  96. // If the input is not an object, allow it to be
  97. // just a plain level specification
  98. if (!value.isObject()) {
  99. if (!parseJsonLevel(value, categoryName, config.level)) {
  100. throw LogConfigParseError{to<string>(
  101. "unexpected data type for configuration of category \"",
  102. categoryName,
  103. "\": got ",
  104. dynamicTypename(value),
  105. ", expected an object, string, or integer")};
  106. }
  107. return config;
  108. }
  109. auto* level = value.get_ptr("level");
  110. if (!level) {
  111. // Require that level information be present for each log category.
  112. throw LogConfigParseError{to<string>(
  113. "no log level specified for category \"", categoryName, "\"")};
  114. }
  115. if (!parseJsonLevel(*level, categoryName, config.level)) {
  116. throw LogConfigParseError{to<string>(
  117. "unexpected data type for level field of category \"",
  118. categoryName,
  119. "\": got ",
  120. dynamicTypename(*level),
  121. ", expected a string or integer")};
  122. }
  123. auto* inherit = value.get_ptr("inherit");
  124. if (inherit) {
  125. if (!inherit->isBool()) {
  126. throw LogConfigParseError{to<string>(
  127. "unexpected data type for inherit field of category \"",
  128. categoryName,
  129. "\": got ",
  130. dynamicTypename(*inherit),
  131. ", expected a boolean")};
  132. }
  133. config.inheritParentLevel = inherit->asBool();
  134. }
  135. auto* handlers = value.get_ptr("handlers");
  136. if (handlers) {
  137. if (!handlers->isArray()) {
  138. throw LogConfigParseError{to<string>(
  139. "the \"handlers\" field for category ",
  140. categoryName,
  141. " must be a list")};
  142. }
  143. config.handlers = std::vector<std::string>{};
  144. for (const auto& item : *handlers) {
  145. if (!item.isString()) {
  146. throw LogConfigParseError{to<string>(
  147. "the \"handlers\" list for category ",
  148. categoryName,
  149. " must be contain only strings")};
  150. }
  151. config.handlers->push_back(item.asString());
  152. }
  153. }
  154. return config;
  155. }
  156. LogHandlerConfig parseJsonHandlerConfig(
  157. const dynamic& value,
  158. StringPiece handlerName) {
  159. if (!value.isObject()) {
  160. throw LogConfigParseError{to<string>(
  161. "unexpected data type for configuration of handler \"",
  162. handlerName,
  163. "\": got ",
  164. dynamicTypename(value),
  165. ", expected an object")};
  166. }
  167. // Parse the handler type
  168. auto* type = value.get_ptr("type");
  169. if (!type) {
  170. throw LogConfigParseError{to<string>(
  171. "no handler type specified for log handler \"", handlerName, "\"")};
  172. }
  173. if (!type->isString()) {
  174. throw LogConfigParseError{to<string>(
  175. "unexpected data type for \"type\" field of handler \"",
  176. handlerName,
  177. "\": got ",
  178. dynamicTypename(*type),
  179. ", expected a string")};
  180. }
  181. LogHandlerConfig config{type->asString()};
  182. // Parse the handler options
  183. auto* options = value.get_ptr("options");
  184. if (options) {
  185. if (!options->isObject()) {
  186. throw LogConfigParseError{to<string>(
  187. "unexpected data type for \"options\" field of handler \"",
  188. handlerName,
  189. "\": got ",
  190. dynamicTypename(*options),
  191. ", expected an object")};
  192. }
  193. for (const auto& item : options->items()) {
  194. if (!item.first.isString()) {
  195. // This shouldn't really ever happen.
  196. // We deserialize the json with allow_non_string_keys set to False.
  197. throw LogConfigParseError{to<string>(
  198. "unexpected data type for option of handler \"",
  199. handlerName,
  200. "\": got ",
  201. dynamicTypename(item.first),
  202. ", expected string")};
  203. }
  204. if (!item.second.isString()) {
  205. throw LogConfigParseError{to<string>(
  206. "unexpected data type for option \"",
  207. item.first.asString(),
  208. "\" of handler \"",
  209. handlerName,
  210. "\": got ",
  211. dynamicTypename(item.second),
  212. ", expected a string")};
  213. }
  214. config.options[item.first.asString()] = item.second.asString();
  215. }
  216. }
  217. return config;
  218. }
  219. LogConfig::CategoryConfigMap parseCategoryConfigs(StringPiece value) {
  220. LogConfig::CategoryConfigMap categoryConfigs;
  221. // Allow empty (or all whitespace) input
  222. value = trimWhitespace(value);
  223. if (value.empty()) {
  224. return categoryConfigs;
  225. }
  226. std::unordered_map<string, string> seenCategories;
  227. std::vector<StringPiece> pieces;
  228. folly::split(",", value, pieces);
  229. for (const auto& piece : pieces) {
  230. LogCategoryConfig categoryConfig;
  231. StringPiece categoryName;
  232. StringPiece configString;
  233. auto equalIndex = piece.find('=');
  234. if (equalIndex == StringPiece::npos) {
  235. // If level information is supplied without a category name,
  236. // apply it to the root log category.
  237. categoryName = StringPiece{"."};
  238. configString = trimWhitespace(piece);
  239. } else {
  240. categoryName = piece.subpiece(0, equalIndex);
  241. configString = piece.subpiece(equalIndex + 1);
  242. // If ":=" is used instead of just "=", disable inheriting the parent's
  243. // effective level if it is lower than this category's level.
  244. if (categoryName.endsWith(':')) {
  245. categoryConfig.inheritParentLevel = false;
  246. categoryName.subtract(1);
  247. }
  248. // Remove whitespace from the category name
  249. categoryName = trimWhitespace(categoryName);
  250. }
  251. // Split the configString into level and handler information.
  252. std::vector<StringPiece> handlerPieces;
  253. folly::split(":", configString, handlerPieces);
  254. FOLLY_SAFE_DCHECK(
  255. handlerPieces.size() >= 1,
  256. "folly::split() always returns a list of length 1");
  257. auto levelString = trimWhitespace(handlerPieces[0]);
  258. bool hasHandlerConfig = handlerPieces.size() > 1;
  259. if (handlerPieces.size() == 2 && trimWhitespace(handlerPieces[1]).empty()) {
  260. // This is an explicitly empty handler list.
  261. // This requests LoggerDB::updateConfig() to clear all existing log
  262. // handlers from this category.
  263. categoryConfig.handlers = std::vector<std::string>{};
  264. } else if (hasHandlerConfig) {
  265. categoryConfig.handlers = std::vector<std::string>{};
  266. for (size_t n = 1; n < handlerPieces.size(); ++n) {
  267. auto handlerName = trimWhitespace(handlerPieces[n]);
  268. if (handlerName.empty()) {
  269. throw LogConfigParseError{to<string>(
  270. "error parsing configuration for log category \"",
  271. categoryName,
  272. "\": log handler name cannot be empty")};
  273. }
  274. categoryConfig.handlers->push_back(handlerName.str());
  275. }
  276. }
  277. // Parse the levelString into a LogLevel
  278. levelString = trimWhitespace(levelString);
  279. try {
  280. categoryConfig.level = stringToLogLevel(levelString);
  281. } catch (const std::exception&) {
  282. throw LogConfigParseError{to<string>(
  283. "invalid log level \"",
  284. levelString,
  285. "\" for category \"",
  286. categoryName,
  287. "\"")};
  288. }
  289. // Check for multiple entries for the same category with different but
  290. // equivalent names.
  291. auto canonicalName = LogName::canonicalize(categoryName);
  292. auto ret = seenCategories.emplace(canonicalName, categoryName.str());
  293. if (!ret.second) {
  294. throw LogConfigParseError{to<string>(
  295. "category \"",
  296. canonicalName,
  297. "\" listed multiple times under different names: \"",
  298. ret.first->second,
  299. "\" and \"",
  300. categoryName,
  301. "\"")};
  302. }
  303. auto emplaceResult =
  304. categoryConfigs.emplace(canonicalName, std::move(categoryConfig));
  305. FOLLY_SAFE_DCHECK(
  306. emplaceResult.second,
  307. "category name must be new since it was not in seenCategories");
  308. }
  309. return categoryConfigs;
  310. }
  311. bool splitNameValue(
  312. StringPiece input,
  313. StringPiece* outName,
  314. StringPiece* outValue) {
  315. size_t equalIndex = input.find('=');
  316. if (equalIndex == StringPiece::npos) {
  317. return false;
  318. }
  319. StringPiece name{input.begin(), input.begin() + equalIndex};
  320. StringPiece value{input.begin() + equalIndex + 1, input.end()};
  321. *outName = trimWhitespace(name);
  322. *outValue = trimWhitespace(value);
  323. return true;
  324. }
  325. std::pair<std::string, LogHandlerConfig> parseHandlerConfig(StringPiece value) {
  326. // Parse the handler name and optional type
  327. auto colonIndex = value.find(':');
  328. StringPiece namePortion;
  329. StringPiece optionsStr;
  330. if (colonIndex == StringPiece::npos) {
  331. namePortion = value;
  332. } else {
  333. namePortion = StringPiece{value.begin(), value.begin() + colonIndex};
  334. optionsStr = StringPiece{value.begin() + colonIndex + 1, value.end()};
  335. }
  336. StringPiece handlerName;
  337. Optional<StringPiece> handlerType(in_place);
  338. if (!splitNameValue(namePortion, &handlerName, &handlerType.value())) {
  339. handlerName = trimWhitespace(namePortion);
  340. handlerType = folly::none;
  341. }
  342. // Make sure the handler name and type are not empty.
  343. // Also disallow commas in the name: this helps catch accidental errors where
  344. // the user left out the ':' and intended to be specifying options instead of
  345. // part of the name or type.
  346. if (handlerName.empty()) {
  347. throw LogConfigParseError{
  348. "error parsing log handler configuration: empty log handler name"};
  349. }
  350. if (handlerName.contains(',')) {
  351. throw LogConfigParseError{to<string>(
  352. "error parsing configuration for log handler \"",
  353. handlerName,
  354. "\": name cannot contain a comma when using the basic config format")};
  355. }
  356. if (handlerType.hasValue()) {
  357. if (handlerType->empty()) {
  358. throw LogConfigParseError{to<string>(
  359. "error parsing configuration for log handler \"",
  360. handlerName,
  361. "\": empty log handler type")};
  362. }
  363. if (handlerType->contains(',')) {
  364. throw LogConfigParseError{to<string>(
  365. "error parsing configuration for log handler \"",
  366. handlerName,
  367. "\": invalid type \"",
  368. handlerType.value(),
  369. "\": type name cannot contain a comma when using "
  370. "the basic config format")};
  371. }
  372. }
  373. // Parse the options
  374. LogHandlerConfig config{handlerType};
  375. optionsStr = trimWhitespace(optionsStr);
  376. if (!optionsStr.empty()) {
  377. std::vector<StringPiece> pieces;
  378. folly::split(",", optionsStr, pieces);
  379. FOLLY_SAFE_DCHECK(
  380. pieces.size() >= 1, "folly::split() always returns a list of length 1");
  381. for (const auto& piece : pieces) {
  382. StringPiece optionName;
  383. StringPiece optionValue;
  384. if (!splitNameValue(piece, &optionName, &optionValue)) {
  385. throw LogConfigParseError{to<string>(
  386. "error parsing configuration for log handler \"",
  387. handlerName,
  388. "\": options must be of the form NAME=VALUE")};
  389. }
  390. auto ret = config.options.emplace(optionName.str(), optionValue.str());
  391. if (!ret.second) {
  392. throw LogConfigParseError{to<string>(
  393. "error parsing configuration for log handler \"",
  394. handlerName,
  395. "\": duplicate configuration for option \"",
  396. optionName,
  397. "\"")};
  398. }
  399. }
  400. }
  401. return std::make_pair(handlerName.str(), std::move(config));
  402. }
  403. } // namespace
  404. LogConfig parseLogConfig(StringPiece value) {
  405. value = trimWhitespace(value);
  406. if (value.startsWith('{')) {
  407. return parseLogConfigJson(value);
  408. }
  409. // Split the input string on semicolons.
  410. // Everything up to the first semicolon specifies log category configs.
  411. // From then on each section specifies a single LogHandler config.
  412. std::vector<StringPiece> pieces;
  413. folly::split(";", value, pieces);
  414. FOLLY_SAFE_DCHECK(
  415. pieces.size() >= 1, "folly::split() always returns a list of length 1");
  416. auto categoryConfigs = parseCategoryConfigs(pieces[0]);
  417. LogConfig::HandlerConfigMap handlerConfigs;
  418. for (size_t n = 1; n < pieces.size(); ++n) {
  419. auto handlerInfo = parseHandlerConfig(pieces[n]);
  420. auto ret = handlerConfigs.emplace(
  421. handlerInfo.first, std::move(handlerInfo.second));
  422. if (!ret.second) {
  423. throw LogConfigParseError{to<string>(
  424. "configuration for log category \"",
  425. handlerInfo.first,
  426. "\" specified multiple times")};
  427. }
  428. }
  429. return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
  430. }
  431. LogConfig parseLogConfigJson(StringPiece value) {
  432. json::serialization_opts opts;
  433. opts.allow_trailing_comma = true;
  434. auto jsonData = folly::parseJson(json::stripComments(value), opts);
  435. return parseLogConfigDynamic(jsonData);
  436. }
  437. LogConfig parseLogConfigDynamic(const dynamic& value) {
  438. if (!value.isObject()) {
  439. throw LogConfigParseError{"JSON config input must be an object"};
  440. }
  441. std::unordered_map<string, string> seenCategories;
  442. LogConfig::CategoryConfigMap categoryConfigs;
  443. auto* categories = value.get_ptr("categories");
  444. if (categories) {
  445. if (!categories->isObject()) {
  446. throw LogConfigParseError{to<string>(
  447. "unexpected data type for log categories config: got ",
  448. dynamicTypename(*categories),
  449. ", expected an object")};
  450. }
  451. for (const auto& entry : categories->items()) {
  452. if (!entry.first.isString()) {
  453. // This shouldn't really ever happen.
  454. // We deserialize the json with allow_non_string_keys set to False.
  455. throw LogConfigParseError{"category name must be a string"};
  456. }
  457. auto categoryName = entry.first.asString();
  458. auto categoryConfig = parseJsonCategoryConfig(entry.second, categoryName);
  459. // Check for multiple entries for the same category with different but
  460. // equivalent names.
  461. auto canonicalName = LogName::canonicalize(categoryName);
  462. auto ret = seenCategories.emplace(canonicalName, categoryName);
  463. if (!ret.second) {
  464. throw LogConfigParseError{to<string>(
  465. "category \"",
  466. canonicalName,
  467. "\" listed multiple times under different names: \"",
  468. ret.first->second,
  469. "\" and \"",
  470. categoryName,
  471. "\"")};
  472. }
  473. categoryConfigs[canonicalName] = std::move(categoryConfig);
  474. }
  475. }
  476. LogConfig::HandlerConfigMap handlerConfigs;
  477. auto* handlers = value.get_ptr("handlers");
  478. if (handlers) {
  479. if (!handlers->isObject()) {
  480. throw LogConfigParseError{to<string>(
  481. "unexpected data type for log handlers config: got ",
  482. dynamicTypename(*handlers),
  483. ", expected an object")};
  484. }
  485. for (const auto& entry : handlers->items()) {
  486. if (!entry.first.isString()) {
  487. // This shouldn't really ever happen.
  488. // We deserialize the json with allow_non_string_keys set to False.
  489. throw LogConfigParseError{"handler name must be a string"};
  490. }
  491. auto handlerName = entry.first.asString();
  492. handlerConfigs.emplace(
  493. handlerName, parseJsonHandlerConfig(entry.second, handlerName));
  494. }
  495. }
  496. return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
  497. }
  498. dynamic logConfigToDynamic(const LogConfig& config) {
  499. dynamic categories = dynamic::object;
  500. for (const auto& entry : config.getCategoryConfigs()) {
  501. categories.insert(entry.first, logConfigToDynamic(entry.second));
  502. }
  503. dynamic handlers = dynamic::object;
  504. for (const auto& entry : config.getHandlerConfigs()) {
  505. handlers.insert(entry.first, logConfigToDynamic(entry.second));
  506. }
  507. return dynamic::object("categories", std::move(categories))(
  508. "handlers", std::move(handlers));
  509. }
  510. dynamic logConfigToDynamic(const LogHandlerConfig& config) {
  511. dynamic options = dynamic::object;
  512. for (const auto& opt : config.options) {
  513. options.insert(opt.first, opt.second);
  514. }
  515. auto result = dynamic::object("options", options);
  516. if (config.type.hasValue()) {
  517. result("type", config.type.value());
  518. }
  519. return std::move(result);
  520. }
  521. dynamic logConfigToDynamic(const LogCategoryConfig& config) {
  522. auto value = dynamic::object("level", logLevelToString(config.level))(
  523. "inherit", config.inheritParentLevel);
  524. if (config.handlers.hasValue()) {
  525. auto handlers = dynamic::array();
  526. for (const auto& handlerName : config.handlers.value()) {
  527. handlers.push_back(handlerName);
  528. }
  529. value("handlers", std::move(handlers));
  530. }
  531. return std::move(value);
  532. }
  533. } // namespace folly