proxygen
LogConfigParser.cpp
Go to the documentation of this file.
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  */
17 
18 #include <folly/Conv.h>
19 #include <folly/String.h>
20 #include <folly/dynamic.h>
21 #include <folly/json.h>
22 #include <folly/lang/SafeAssert.h>
23 #include <folly/logging/LogName.h>
24 
25 using std::shared_ptr;
26 using std::string;
27 
28 namespace folly {
29 
30 namespace {
31 
36 std::string dynamicTypename(const dynamic& value) {
37  switch (value.type()) {
38  case dynamic::NULLT:
39  return "null";
40  case dynamic::ARRAY:
41  return "array";
42  case dynamic::BOOL:
43  return "boolean";
44  case dynamic::DOUBLE:
45  return "double";
46  case dynamic::INT64:
47  return "integer";
48  case dynamic::OBJECT:
49  return "object";
50  case dynamic::STRING:
51  return "string";
52  }
53  return "unknown type";
54 }
55 
66 bool parseJsonLevel(
67  const dynamic& value,
68  StringPiece categoryName,
69  LogLevel& result) {
70  if (value.isString()) {
71  auto levelString = value.asString();
72  try {
73  result = stringToLogLevel(levelString);
74  return true;
75  } catch (const std::exception&) {
76  throw LogConfigParseError{to<string>(
77  "invalid log level \"",
78  levelString,
79  "\" for category \"",
80  categoryName,
81  "\"")};
82  }
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>(
87  "invalid log level ",
88  value.asInt(),
89  " for category \"",
90  categoryName,
91  "\": outside of valid range")};
92  }
93  result = level;
94  return true;
95  }
96 
97  return false;
98 }
99 
100 LogCategoryConfig parseJsonCategoryConfig(
101  const dynamic& value,
102  StringPiece categoryName) {
103  LogCategoryConfig config;
104 
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 \"",
111  categoryName,
112  "\": got ",
113  dynamicTypename(value),
114  ", expected an object, string, or integer")};
115  }
116  return config;
117  }
118 
119  auto* level = value.get_ptr("level");
120  if (!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, "\"")};
124  }
125  if (!parseJsonLevel(*level, categoryName, config.level)) {
126  throw LogConfigParseError{to<string>(
127  "unexpected data type for level field of category \"",
128  categoryName,
129  "\": got ",
130  dynamicTypename(*level),
131  ", expected a string or integer")};
132  }
133 
134  auto* inherit = value.get_ptr("inherit");
135  if (inherit) {
136  if (!inherit->isBool()) {
137  throw LogConfigParseError{to<string>(
138  "unexpected data type for inherit field of category \"",
139  categoryName,
140  "\": got ",
141  dynamicTypename(*inherit),
142  ", expected a boolean")};
143  }
144  config.inheritParentLevel = inherit->asBool();
145  }
146 
147  auto* handlers = value.get_ptr("handlers");
148  if (handlers) {
149  if (!handlers->isArray()) {
150  throw LogConfigParseError{to<string>(
151  "the \"handlers\" field for category ",
152  categoryName,
153  " must be a list")};
154  }
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 ",
160  categoryName,
161  " must be contain only strings")};
162  }
163  config.handlers->push_back(item.asString());
164  }
165  }
166 
167  return config;
168 }
169 
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 \"",
176  handlerName,
177  "\": got ",
178  dynamicTypename(value),
179  ", expected an object")};
180  }
181 
182  // Parse the handler type
183  auto* type = value.get_ptr("type");
184  if (!type) {
185  throw LogConfigParseError{to<string>(
186  "no handler type specified for log handler \"", handlerName, "\"")};
187  }
188  if (!type->isString()) {
189  throw LogConfigParseError{to<string>(
190  "unexpected data type for \"type\" field of handler \"",
191  handlerName,
192  "\": got ",
193  dynamicTypename(*type),
194  ", expected a string")};
195  }
196  LogHandlerConfig config{type->asString()};
197 
198  // Parse the handler options
199  auto* options = value.get_ptr("options");
200  if (options) {
201  if (!options->isObject()) {
202  throw LogConfigParseError{to<string>(
203  "unexpected data type for \"options\" field of handler \"",
204  handlerName,
205  "\": got ",
206  dynamicTypename(*options),
207  ", expected an object")};
208  }
209 
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 \"",
216  handlerName,
217  "\": got ",
218  dynamicTypename(item.first),
219  ", expected string")};
220  }
221  if (!item.second.isString()) {
222  throw LogConfigParseError{to<string>(
223  "unexpected data type for option \"",
224  item.first.asString(),
225  "\" of handler \"",
226  handlerName,
227  "\": got ",
228  dynamicTypename(item.second),
229  ", expected a string")};
230  }
231  config.options[item.first.asString()] = item.second.asString();
232  }
233  }
234 
235  return config;
236 }
237 
238 LogConfig::CategoryConfigMap parseCategoryConfigs(StringPiece value) {
239  LogConfig::CategoryConfigMap categoryConfigs;
240 
241  // Allow empty (or all whitespace) input
242  value = trimWhitespace(value);
243  if (value.empty()) {
244  return categoryConfigs;
245  }
246 
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;
254 
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);
261  } else {
262  categoryName = piece.subpiece(0, equalIndex);
263  configString = piece.subpiece(equalIndex + 1);
264 
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);
270  }
271 
272  // Remove whitespace from the category name
273  categoryName = trimWhitespace(categoryName);
274  }
275 
276  // Split the configString into level and handler information.
277  std::vector<StringPiece> handlerPieces;
278  folly::split(":", configString, handlerPieces);
280  handlerPieces.size() >= 1,
281  "folly::split() always returns a list of length 1");
282  auto levelString = trimWhitespace(handlerPieces[0]);
283 
284  bool hasHandlerConfig = handlerPieces.size() > 1;
285  if (handlerPieces.size() == 2 && trimWhitespace(handlerPieces[1]).empty()) {
286  // This is an explicitly empty handler list.
287  // This requests LoggerDB::updateConfig() to clear all existing log
288  // handlers from this category.
289  categoryConfig.handlers = std::vector<std::string>{};
290  } else if (hasHandlerConfig) {
291  categoryConfig.handlers = std::vector<std::string>{};
292  for (size_t n = 1; n < handlerPieces.size(); ++n) {
293  auto handlerName = trimWhitespace(handlerPieces[n]);
294  if (handlerName.empty()) {
295  throw LogConfigParseError{to<string>(
296  "error parsing configuration for log category \"",
297  categoryName,
298  "\": log handler name cannot be empty")};
299  }
300  categoryConfig.handlers->push_back(handlerName.str());
301  }
302  }
303 
304  // Parse the levelString into a LogLevel
305  levelString = trimWhitespace(levelString);
306  try {
307  categoryConfig.level = stringToLogLevel(levelString);
308  } catch (const std::exception&) {
309  throw LogConfigParseError{to<string>(
310  "invalid log level \"",
311  levelString,
312  "\" for category \"",
313  categoryName,
314  "\"")};
315  }
316 
317  // Check for multiple entries for the same category with different but
318  // equivalent names.
319  auto canonicalName = LogName::canonicalize(categoryName);
320  auto ret = seenCategories.emplace(canonicalName, categoryName.str());
321  if (!ret.second) {
322  throw LogConfigParseError{to<string>(
323  "category \"",
324  canonicalName,
325  "\" listed multiple times under different names: \"",
326  ret.first->second,
327  "\" and \"",
328  categoryName,
329  "\"")};
330  }
331 
332  auto emplaceResult =
333  categoryConfigs.emplace(canonicalName, std::move(categoryConfig));
335  emplaceResult.second,
336  "category name must be new since it was not in seenCategories");
337  }
338 
339  return categoryConfigs;
340 }
341 
342 bool splitNameValue(
343  StringPiece input,
344  StringPiece* outName,
345  StringPiece* outValue) {
346  size_t equalIndex = input.find('=');
347  if (equalIndex == StringPiece::npos) {
348  return false;
349  }
350 
351  StringPiece name{input.begin(), input.begin() + equalIndex};
352  StringPiece value{input.begin() + equalIndex + 1, input.end()};
353 
354  *outName = trimWhitespace(name);
355  *outValue = trimWhitespace(value);
356  return true;
357 }
358 
359 std::pair<std::string, LogHandlerConfig> parseHandlerConfig(StringPiece value) {
360  // Parse the handler name and optional type
361  auto colonIndex = value.find(':');
362  StringPiece namePortion;
363  StringPiece optionsStr;
364  if (colonIndex == StringPiece::npos) {
365  namePortion = value;
366  } else {
367  namePortion = StringPiece{value.begin(), value.begin() + colonIndex};
368  optionsStr = StringPiece{value.begin() + colonIndex + 1, value.end()};
369  }
370 
371  StringPiece handlerName;
372  Optional<StringPiece> handlerType(in_place);
373  if (!splitNameValue(namePortion, &handlerName, &handlerType.value())) {
374  handlerName = trimWhitespace(namePortion);
375  handlerType = folly::none;
376  }
377 
378  // Make sure the handler name and type are not empty.
379  // Also disallow commas in the name: this helps catch accidental errors where
380  // the user left out the ':' and intended to be specifying options instead of
381  // part of the name or type.
382  if (handlerName.empty()) {
383  throw LogConfigParseError{
384  "error parsing log handler configuration: empty log handler name"};
385  }
386  if (handlerName.contains(',')) {
387  throw LogConfigParseError{to<string>(
388  "error parsing configuration for log handler \"",
389  handlerName,
390  "\": name cannot contain a comma when using the basic config format")};
391  }
392  if (handlerType.hasValue()) {
393  if (handlerType->empty()) {
394  throw LogConfigParseError{to<string>(
395  "error parsing configuration for log handler \"",
396  handlerName,
397  "\": empty log handler type")};
398  }
399  if (handlerType->contains(',')) {
400  throw LogConfigParseError{to<string>(
401  "error parsing configuration for log handler \"",
402  handlerName,
403  "\": invalid type \"",
404  handlerType.value(),
405  "\": type name cannot contain a comma when using "
406  "the basic config format")};
407  }
408  }
409 
410  // Parse the options
411  LogHandlerConfig config{handlerType};
412  optionsStr = trimWhitespace(optionsStr);
413  if (!optionsStr.empty()) {
414  std::vector<StringPiece> pieces;
415  folly::split(",", optionsStr, pieces);
417  pieces.size() >= 1, "folly::split() always returns a list of length 1");
418 
419  for (const auto& piece : pieces) {
420  StringPiece optionName;
421  StringPiece optionValue;
422  if (!splitNameValue(piece, &optionName, &optionValue)) {
423  throw LogConfigParseError{to<string>(
424  "error parsing configuration for log handler \"",
425  handlerName,
426  "\": options must be of the form NAME=VALUE")};
427  }
428 
429  auto ret = config.options.emplace(optionName.str(), optionValue.str());
430  if (!ret.second) {
431  throw LogConfigParseError{to<string>(
432  "error parsing configuration for log handler \"",
433  handlerName,
434  "\": duplicate configuration for option \"",
435  optionName,
436  "\"")};
437  }
438  }
439  }
440 
441  return std::make_pair(handlerName.str(), std::move(config));
442 }
443 
444 } // namespace
445 
447  value = trimWhitespace(value);
448  if (value.startsWith('{')) {
449  return parseLogConfigJson(value);
450  }
451 
452  // Split the input string on semicolons.
453  // Everything up to the first semicolon specifies log category configs.
454  // From then on each section specifies a single LogHandler config.
455  std::vector<StringPiece> pieces;
456  folly::split(";", value, pieces);
458  pieces.size() >= 1, "folly::split() always returns a list of length 1");
459 
460  auto categoryConfigs = parseCategoryConfigs(pieces[0]);
461  LogConfig::HandlerConfigMap handlerConfigs;
462  for (size_t n = 1; n < pieces.size(); ++n) {
463  auto handlerInfo = parseHandlerConfig(pieces[n]);
464  auto ret = handlerConfigs.emplace(
465  handlerInfo.first, std::move(handlerInfo.second));
466  if (!ret.second) {
467  throw LogConfigParseError{to<string>(
468  "configuration for log category \"",
469  handlerInfo.first,
470  "\" specified multiple times")};
471  }
472  }
473 
474  return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
475 }
476 
479  opts.allow_trailing_comma = true;
480  auto jsonData = folly::parseJson(json::stripComments(value), opts);
481  return parseLogConfigDynamic(jsonData);
482 }
483 
485  if (!value.isObject()) {
486  throw LogConfigParseError{"JSON config input must be an object"};
487  }
488 
489  std::unordered_map<string, string> seenCategories;
490  LogConfig::CategoryConfigMap categoryConfigs;
491  auto* categories = value.get_ptr("categories");
492  if (categories) {
493  if (!categories->isObject()) {
494  throw LogConfigParseError{to<string>(
495  "unexpected data type for log categories config: got ",
496  dynamicTypename(*categories),
497  ", expected an object")};
498  }
499 
500  for (const auto& entry : categories->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{"category name must be a string"};
505  }
506  auto categoryName = entry.first.asString();
507  auto categoryConfig = parseJsonCategoryConfig(entry.second, categoryName);
508 
509  // Check for multiple entries for the same category with different but
510  // equivalent names.
511  auto canonicalName = LogName::canonicalize(categoryName);
512  auto ret = seenCategories.emplace(canonicalName, categoryName);
513  if (!ret.second) {
514  throw LogConfigParseError{to<string>(
515  "category \"",
516  canonicalName,
517  "\" listed multiple times under different names: \"",
518  ret.first->second,
519  "\" and \"",
520  categoryName,
521  "\"")};
522  }
523 
524  categoryConfigs[canonicalName] = std::move(categoryConfig);
525  }
526  }
527 
528  LogConfig::HandlerConfigMap handlerConfigs;
529  auto* handlers = value.get_ptr("handlers");
530  if (handlers) {
531  if (!handlers->isObject()) {
532  throw LogConfigParseError{to<string>(
533  "unexpected data type for log handlers config: got ",
534  dynamicTypename(*handlers),
535  ", expected an object")};
536  }
537 
538  for (const auto& entry : handlers->items()) {
539  if (!entry.first.isString()) {
540  // This shouldn't really ever happen.
541  // We deserialize the json with allow_non_string_keys set to False.
542  throw LogConfigParseError{"handler name must be a string"};
543  }
544  auto handlerName = entry.first.asString();
545  handlerConfigs.emplace(
546  handlerName, parseJsonHandlerConfig(entry.second, handlerName));
547  }
548  }
549 
550  return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
551 }
552 
554  dynamic categories = dynamic::object;
555  for (const auto& entry : config.getCategoryConfigs()) {
556  categories.insert(entry.first, logConfigToDynamic(entry.second));
557  }
558 
559  dynamic handlers = dynamic::object;
560  for (const auto& entry : config.getHandlerConfigs()) {
561  handlers.insert(entry.first, logConfigToDynamic(entry.second));
562  }
563 
564  return dynamic::object("categories", std::move(categories))(
565  "handlers", std::move(handlers));
566 }
567 
569  dynamic options = dynamic::object;
570  for (const auto& opt : config.options) {
571  options.insert(opt.first, opt.second);
572  }
573  auto result = dynamic::object("options", options);
574  if (config.type.hasValue()) {
575  result("type", config.type.value());
576  }
577  return std::move(result);
578 }
579 
581  auto value = dynamic::object("level", logLevelToString(config.level))(
582  "inherit", config.inheritParentLevel);
583  if (config.handlers.hasValue()) {
584  auto handlers = dynamic::array();
585  for (const auto& handlerName : config.handlers.value()) {
586  handlers.push_back(handlerName);
587  }
588  value("handlers", std::move(handlers));
589  }
590  return std::move(value);
591 }
592 
593 } // namespace folly
static ObjectMaker object()
Definition: dynamic-inl.h:240
dynamic parseJson(StringPiece range)
Definition: json.cpp:900
PskType type
const CategoryConfigMap & getCategoryConfigs() const
Definition: LogConfig.h:45
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
size_type find(const_range_type str) const
Definition: Range.h:721
Optional< std::vector< std::string > > handlers
std::string stripComments(StringPiece jsonC)
Definition: json.cpp:837
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
in_place_tag in_place(in_place_tag={})
Definition: Utility.h:235
LogConfig parseLogConfig(StringPiece value)
Optional< std::string > type
LogLevel stringToLogLevel(StringPiece name)
Definition: LogLevel.cpp:42
string logLevelToString(LogLevel level)
Definition: LogLevel.cpp:109
void split(const Delim &delimiter, const String &input, std::vector< OutputType > &out, bool ignoreEmpty)
Definition: String-inl.h:382
AHArrayT::Config config
const char * name
Definition: http_parser.c:437
constexpr bool empty() const
Definition: Range.h:443
std::unordered_map< std::string, LogCategoryConfig > CategoryConfigMap
Definition: LogConfig.h:35
static std::string canonicalize(folly::StringPiece name)
Definition: LogName.cpp:26
FOLLY_CPP14_CONSTEXPR bool hasValue() const noexcept
Definition: Optional.h:300
void insert(K &&, V &&val)
Definition: dynamic-inl.h:853
LogConfig parseLogConfigDynamic(const dynamic &value)
LogLevel
Definition: LogLevel.h:38
std::unordered_map< std::string, LogHandlerConfig > HandlerConfigMap
Definition: LogConfig.h:36
constexpr Iter begin() const
Definition: Range.h:452
bool startsWith(const const_range_type &other) const
Definition: Range.h:828
static const size_type npos
Definition: Range.h:197
const char * string
Definition: Conv.cpp:212
dynamic logConfigToDynamic(const LogConfig &config)
static void array(EmptyArrayTag)
Definition: dynamic-inl.h:233
StringPiece trimWhitespace(StringPiece sp)
Definition: String.h:568
uint64_t value(const typename LockFreeRingBuffer< T, Atom >::Cursor &rbcursor)
const HandlerConfigMap & getHandlerConfigs() const
Definition: LogConfig.h:48
FOLLY_CPP14_CONSTEXPR const Value & value() const &
Definition: Optional.h:268
LogConfig parseLogConfigJson(StringPiece value)
Range< const char * > StringPiece
bool isObject() const
Definition: dynamic-inl.h:492
#define FOLLY_SAFE_DCHECK(expr, msg)
Definition: SafeAssert.h:42
constexpr None none
Definition: Optional.h:87
const dynamic * get_ptr(json_pointer const &) const &
Definition: dynamic.cpp:371