proxygen
ConfigParserTest.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  */
16 #include <folly/String.h>
17 #include <folly/dynamic.h>
18 #include <folly/json.h>
25 #include <folly/test/TestUtils.h>
26 
27 using namespace folly;
28 
31 
32 TEST(LogConfig, parseBasic) {
33  auto config = parseLogConfig("");
34  EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
35  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
36 
37  config = parseLogConfig(" ");
38  EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
39  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
40 
41  config = parseLogConfig(".=ERROR,folly=DBG2");
43  config.getCategoryConfigs(),
46  Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
47  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
48 
49  config = parseLogConfig(" INFO , folly := FATAL ");
51  config.getCategoryConfigs(),
54  Pair("folly", LogCategoryConfig{LogLevel::FATAL, false})));
55  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
56 
57  config =
58  parseLogConfig("my.category:=INFO , my.other.stuff := 19,foo.bar=DBG7");
60  config.getCategoryConfigs(),
62  Pair("my.category", LogCategoryConfig{LogLevel::INFO, false}),
63  Pair(
64  "my.other.stuff",
65  LogCategoryConfig{static_cast<LogLevel>(19), false}),
66  Pair("foo.bar", LogCategoryConfig{LogLevel::DBG7, true})));
67  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
68 
69  config = parseLogConfig(" ERR ");
71  config.getCategoryConfigs(),
73  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
74 
75  config = parseLogConfig(" ERR: ");
77  config.getCategoryConfigs(),
79  Pair("", LogCategoryConfig{LogLevel::ERR, true, {}})));
80  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
81 
82  config = parseLogConfig(" ERR:stderr; stderr=stream:stream=stderr ");
84  config.getCategoryConfigs(),
86  Pair("", LogCategoryConfig{LogLevel::ERR, true, {"stderr"}})));
88  config.getHandlerConfigs(),
90  Pair("stderr", LogHandlerConfig{"stream", {{"stream", "stderr"}}})));
91 
92  config = parseLogConfig(
93  "ERR:myfile:custom, folly=DBG2, folly.io:=WARN:other;"
94  "myfile=file:path=/tmp/x.log; "
95  "custom=custom:foo=bar,hello=world,a = b = c; "
96  "other=custom2");
98  config.getCategoryConfigs(),
100  Pair(
101  "", LogCategoryConfig{LogLevel::ERR, true, {"myfile", "custom"}}),
102  Pair("folly", LogCategoryConfig{LogLevel::DBG2, true}),
103  Pair(
104  "folly.io",
105  LogCategoryConfig{LogLevel::WARN, false, {"other"}})));
106  EXPECT_THAT(
107  config.getHandlerConfigs(),
109  Pair("myfile", LogHandlerConfig{"file", {{"path", "/tmp/x.log"}}}),
110  Pair(
111  "custom",
113  "custom",
114  {{"foo", "bar"}, {"hello", "world"}, {"a", "b = c"}}}),
115  Pair("other", LogHandlerConfig{"custom2"})));
116 
117  // Test updating existing handler configs, with no handler type
118  config = parseLogConfig("ERR;foo");
119  EXPECT_THAT(
120  config.getCategoryConfigs(),
122  EXPECT_THAT(
123  config.getHandlerConfigs(),
125 
126  config = parseLogConfig("ERR;foo:a=b,c=d");
127  EXPECT_THAT(
128  config.getCategoryConfigs(),
130  EXPECT_THAT(
131  config.getHandlerConfigs(),
133  "foo", LogHandlerConfig{folly::none, {{"a", "b"}, {"c", "d"}}})));
134 
135  config = parseLogConfig("ERR;test=file:path=/tmp/test.log;foo:a=b,c=d");
136  EXPECT_THAT(
137  config.getCategoryConfigs(),
139  EXPECT_THAT(
140  config.getHandlerConfigs(),
142  Pair("foo", LogHandlerConfig{folly::none, {{"a", "b"}, {"c", "d"}}}),
143  Pair("test", LogHandlerConfig{"file", {{"path", "/tmp/test.log"}}})));
144 
145  // Log handler changes with no category changes
146  config = parseLogConfig("; myhandler=custom:foo=bar");
147  EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
148  EXPECT_THAT(
149  config.getHandlerConfigs(),
151  Pair("myhandler", LogHandlerConfig{"custom", {{"foo", "bar"}}})));
152 }
153 
154 TEST(LogConfig, parseBasicErrors) {
155  // Errors in the log category settings
157  parseLogConfig("=="),
159  R"(invalid log level "=" for category "")");
161  parseLogConfig("bogus_level"),
163  R"(invalid log level "bogus_level" for category ".")");
165  parseLogConfig("foo=bogus_level"),
167  R"(invalid log level "bogus_level" for category "foo")");
169  parseLogConfig("foo=WARN,bar=invalid"),
171  R"(invalid log level "invalid" for category "bar")");
173  parseLogConfig("foo=WARN,bar="),
175  R"(invalid log level "" for category "bar")");
177  parseLogConfig("foo=WARN,bar:="),
179  R"(invalid log level "" for category "bar")");
181  parseLogConfig("foo:=,bar:=WARN"),
183  R"(invalid log level "" for category "foo")");
185  parseLogConfig("x"),
187  R"(invalid log level "x" for category ".")");
189  parseLogConfig("x,y,z"),
191  R"(invalid log level "x" for category ".")");
193  parseLogConfig("foo=WARN,"),
195  R"(invalid log level "" for category ".")");
197  parseLogConfig("="),
199  R"(invalid log level "" for category "")");
201  parseLogConfig(":="),
203  R"(invalid log level "" for category "")");
205  parseLogConfig("foo=bar=ERR"),
207  R"(invalid log level "bar=ERR" for category "foo")");
209  parseLogConfig("foo.bar=ERR,foo..bar=INFO"),
211  R"(category "foo\.bar" listed multiple times under different names: )"
212  R"("foo\.+bar" and "foo\.+bar")");
214  parseLogConfig("=ERR,.=INFO"),
216  R"(category "" listed multiple times under different names: )"
217  R"("\.?" and "\.?")");
218 
219  // Errors in the log handler settings
221  parseLogConfig("ERR;"),
223  "error parsing log handler configuration: empty log handler name");
225  parseLogConfig("ERR;foo="),
227  R"(error parsing configuration for log handler "foo": )"
228  "empty log handler type");
230  parseLogConfig("ERR;=file"),
232  "error parsing log handler configuration: empty log handler name");
234  parseLogConfig("ERR;handler1=file;"),
236  "error parsing log handler configuration: empty log handler name");
238  parseLogConfig("ERR;test=file,path=/tmp/test.log;foo:a=b,c=d"),
240  R"(error parsing configuration for log handler "test": )"
241  R"(invalid type "file,path=/tmp/test.log": type name cannot contain )"
242  "a comma when using the basic config format");
244  parseLogConfig("ERR;test,path=/tmp/test.log;foo:a=b,c=d"),
246  R"(error parsing configuration for log handler "test,path": )"
247  "name cannot contain a comma when using the basic config format");
248 }
249 
251  auto config = parseLogConfig("{}");
252  EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
253  config = parseLogConfig(" {} ");
254  EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
255 
256  config = parseLogConfig(R"JSON({
257  "categories": {
258  ".": "ERROR",
259  "folly": "DBG2",
260  }
261  })JSON");
262  EXPECT_THAT(
263  config.getCategoryConfigs(),
266  Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
267  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
268 
269  config = parseLogConfig(R"JSON({
270  "categories": {
271  "": "ERROR",
272  "folly": "DBG2",
273  }
274  })JSON");
275  EXPECT_THAT(
276  config.getCategoryConfigs(),
279  Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
280  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
281 
282  config = parseLogConfig(R"JSON({
283  "categories": {
284  ".": { "level": "INFO" },
285  "folly": { "level": "FATAL", "inherit": false },
286  }
287  })JSON");
288  EXPECT_THAT(
289  config.getCategoryConfigs(),
292  Pair("folly", LogCategoryConfig{LogLevel::FATAL, false})));
293  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
294 
295  config = parseLogConfig(R"JSON({
296  "categories": {
297  "my.category": { "level": "INFO", "inherit": true },
298  // comments are allowed
299  "my.other.stuff": { "level": 19, "inherit": false },
300  "foo.bar": { "level": "DBG7" },
301  },
302  "handlers": {
303  "h1": { "type": "custom", "options": {"foo": "bar", "a": "z"} }
304  }
305  })JSON");
306  EXPECT_THAT(
307  config.getCategoryConfigs(),
309  Pair("my.category", LogCategoryConfig{LogLevel::INFO, true}),
310  Pair(
311  "my.other.stuff",
312  LogCategoryConfig{static_cast<LogLevel>(19), false}),
313  Pair("foo.bar", LogCategoryConfig{LogLevel::DBG7, true})));
314  EXPECT_THAT(
315  config.getHandlerConfigs(),
317  "h1", LogHandlerConfig{"custom", {{"foo", "bar"}, {"a", "z"}}})));
318 
319  // The JSON config parsing should allow unusual log category names
320  // containing whitespace, equal signs, and other characters not allowed in
321  // the basic config style.
322  config = parseLogConfig(R"JSON({
323  "categories": {
324  " my.category ": { "level": "INFO" },
325  " foo; bar=asdf, test": { "level": "DBG1" },
326  },
327  "handlers": {
328  "h1;h2,h3= ": { "type": " x;y " }
329  }
330  })JSON");
331  EXPECT_THAT(
332  config.getCategoryConfigs(),
334  Pair(" my.category ", LogCategoryConfig{LogLevel::INFO, true}),
335  Pair(
336  " foo; bar=asdf, test",
338  EXPECT_THAT(
339  config.getHandlerConfigs(),
340  UnorderedElementsAre(Pair("h1;h2,h3= ", LogHandlerConfig{" x;y "})));
341 }
342 
343 TEST(LogConfig, parseJsonErrors) {
345  parseLogConfigJson("5"),
347  "JSON config input must be an object");
349  parseLogConfigJson("true"),
351  "JSON config input must be an object");
353  parseLogConfigJson(R"("hello")"),
355  "JSON config input must be an object");
357  parseLogConfigJson("[1, 2, 3]"),
359  "JSON config input must be an object");
361  parseLogConfigJson(""), std::runtime_error, "json parse error");
363  parseLogConfigJson("{"), std::runtime_error, "json parse error");
364  EXPECT_THROW_RE(parseLogConfig("{"), std::runtime_error, "json parse error");
366  parseLogConfig("{}}"), std::runtime_error, "json parse error");
367 
368  StringPiece input = R"JSON({
369  "categories": 5
370  })JSON";
372  parseLogConfig(input),
374  "unexpected data type for log categories config: "
375  "got integer, expected an object");
376  input = R"JSON({
377  "categories": {
378  "foo": true,
379  }
380  })JSON";
382  parseLogConfig(input),
384  R"(unexpected data type for configuration of category "foo": )"
385  "got boolean, expected an object, string, or integer");
386 
387  input = R"JSON({
388  "categories": {
389  "foo": [1, 2, 3],
390  }
391  })JSON";
393  parseLogConfig(input),
395  R"(unexpected data type for configuration of category "foo": )"
396  "got array, expected an object, string, or integer");
397 
398  input = R"JSON({
399  "categories": {
400  ".": { "level": "INFO" },
401  "folly": { "level": "FATAL", "inherit": 19 },
402  }
403  })JSON";
405  parseLogConfig(input),
407  R"(unexpected data type for inherit field of category "folly": )"
408  "got integer, expected a boolean");
409  input = R"JSON({
410  "categories": {
411  "folly": { "level": [], },
412  }
413  })JSON";
415  parseLogConfig(input),
417  R"(unexpected data type for level field of category "folly": )"
418  "got array, expected a string or integer");
419  input = R"JSON({
420  "categories": {
421  5: {}
422  }
423  })JSON";
425  parseLogConfig(input), std::runtime_error, "json parse error");
426 
427  input = R"JSON({
428  "categories": {
429  "foo...bar": { "level": "INFO", },
430  "foo..bar": { "level": "INFO", },
431  }
432  })JSON";
434  parseLogConfig(input),
436  R"(category "foo\.bar" listed multiple times under different names: )"
437  R"("foo\.\.+bar" and "foo\.+bar")");
438  input = R"JSON({
439  "categories": {
440  "...": { "level": "ERR", },
441  "": { "level": "INFO", },
442  }
443  })JSON";
445  parseLogConfig(input),
447  R"(category "" listed multiple times under different names: )"
448  R"X("(\.\.\.|)" and "(\.\.\.|)")X");
449 
450  input = R"JSON({
451  "categories": { "folly": { "level": "ERR" } },
452  "handlers": 9.8
453  })JSON";
455  parseLogConfig(input),
457  "unexpected data type for log handlers config: "
458  "got double, expected an object");
459 
460  input = R"JSON({
461  "categories": { "folly": { "level": "ERR" } },
462  "handlers": {
463  "foo": "test"
464  }
465  })JSON";
467  parseLogConfig(input),
469  R"(unexpected data type for configuration of handler "foo": )"
470  "got string, expected an object");
471 
472  input = R"JSON({
473  "categories": { "folly": { "level": "ERR" } },
474  "handlers": {
475  "foo": {}
476  }
477  })JSON";
479  parseLogConfig(input),
481  R"(no handler type specified for log handler "foo")");
482 
483  input = R"JSON({
484  "categories": { "folly": { "level": "ERR" } },
485  "handlers": {
486  "foo": {
487  "type": 19
488  }
489  }
490  })JSON";
492  parseLogConfig(input),
494  R"(unexpected data type for "type" field of handler "foo": )"
495  "got integer, expected a string");
496 
497  input = R"JSON({
498  "categories": { "folly": { "level": "ERR" } },
499  "handlers": {
500  "foo": {
501  "type": "custom",
502  "options": true
503  }
504  }
505  })JSON";
507  parseLogConfig(input),
509  R"(unexpected data type for "options" field of handler "foo": )"
510  "got boolean, expected an object");
511 
512  input = R"JSON({
513  "categories": { "folly": { "level": "ERR" } },
514  "handlers": {
515  "foo": {
516  "type": "custom",
517  "options": ["foo", "bar"]
518  }
519  }
520  })JSON";
522  parseLogConfig(input),
524  R"(unexpected data type for "options" field of handler "foo": )"
525  "got array, expected an object");
526 
527  input = R"JSON({
528  "categories": { "folly": { "level": "ERR" } },
529  "handlers": {
530  "foo": {
531  "type": "custom",
532  "options": {"bar": 5}
533  }
534  }
535  })JSON";
537  parseLogConfig(input),
539  R"(unexpected data type for option "bar" of handler "foo": )"
540  "got integer, expected a string");
541 }
542 
544  auto config = parseLogConfig("");
545  auto expectedJson = folly::parseJson(R"JSON({
546  "categories": {},
547  "handlers": {}
548 })JSON");
549  EXPECT_EQ(expectedJson, logConfigToDynamic(config));
550 
552  "ERROR:h1,foo.bar:=FATAL,folly=INFO:; "
553  "h1=custom:foo=bar");
554  expectedJson = folly::parseJson(R"JSON({
555  "categories" : {
556  "" : {
557  "inherit" : true,
558  "level" : "ERR",
559  "handlers" : ["h1"]
560  },
561  "folly" : {
562  "inherit" : true,
563  "level" : "INFO",
564  "handlers" : []
565  },
566  "foo.bar" : {
567  "inherit" : false,
568  "level" : "FATAL"
569  }
570  },
571  "handlers" : {
572  "h1": {
573  "type": "custom",
574  "options": { "foo": "bar" }
575  }
576  }
577 })JSON");
578  EXPECT_EQ(expectedJson, logConfigToDynamic(config));
579 }
580 
581 TEST(LogConfig, mergeConfigs) {
582  auto config = parseLogConfig("bar=ERR:");
583  config.update(parseLogConfig("foo:=INFO"));
584  EXPECT_THAT(
585  config.getCategoryConfigs(),
587  Pair("foo", LogCategoryConfig{LogLevel::INFO, false}),
588  Pair("bar", LogCategoryConfig{LogLevel::ERR, true, {}})));
589  EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
590 
591  config =
592  parseLogConfig("WARN:default; default=custom:opt1=value1,opt2=value2");
593  config.update(parseLogConfig("folly.io=DBG2,foo=INFO"));
594  EXPECT_THAT(
595  config.getCategoryConfigs(),
597  Pair("", LogCategoryConfig{LogLevel::WARN, true, {"default"}}),
598  Pair("foo", LogCategoryConfig{LogLevel::INFO, true}),
599  Pair("folly.io", LogCategoryConfig{LogLevel::DBG2, true})));
600  EXPECT_THAT(
601  config.getHandlerConfigs(),
603  "default",
605  "custom", {{"opt1", "value1"}, {"opt2", "value2"}}))));
606 
607  // Updating the root category's log level without specifying
608  // handlers should leave its current handler list intact
609  config =
610  parseLogConfig("WARN:default; default=custom:opt1=value1,opt2=value2");
611  config.update(parseLogConfig("ERR"));
612  EXPECT_THAT(
613  config.getCategoryConfigs(),
615  Pair("", LogCategoryConfig{LogLevel::ERR, true, {"default"}})));
616  EXPECT_THAT(
617  config.getHandlerConfigs(),
619  "default",
621  "custom", {{"opt1", "value1"}, {"opt2", "value2"}}))));
622 
623  config =
624  parseLogConfig("WARN:default; default=custom:opt1=value1,opt2=value2");
625  config.update(parseLogConfig(".:=ERR"));
626  EXPECT_THAT(
627  config.getCategoryConfigs(),
629  Pair("", LogCategoryConfig{LogLevel::ERR, false, {"default"}})));
630  EXPECT_THAT(
631  config.getHandlerConfigs(),
633  "default",
635  "custom", {{"opt1", "value1"}, {"opt2", "value2"}}))));
636 
637  // Test clearing the root category's log handlers
638  config =
639  parseLogConfig("WARN:default; default=custom:opt1=value1,opt2=value2");
640  config.update(parseLogConfig("FATAL:"));
641  EXPECT_THAT(
642  config.getCategoryConfigs(),
644  Pair("", LogCategoryConfig{LogLevel::FATAL, true, {}})));
645  EXPECT_THAT(
646  config.getHandlerConfigs(),
648  "default",
650  "custom", {{"opt1", "value1"}, {"opt2", "value2"}}))));
651 
652  // Test updating the settings on a log handler
653  config =
654  parseLogConfig("WARN:default; default=stream:stream=stderr,async=false");
655  config.update(parseLogConfig("INFO; default:async=true"));
656  EXPECT_THAT(
657  config.getCategoryConfigs(),
659  Pair("", LogCategoryConfig{LogLevel::INFO, true, {"default"}})));
660  EXPECT_THAT(
661  config.getHandlerConfigs(),
663  "default",
665  "stream", {{"stream", "stderr"}, {"async", "true"}}))));
666 
667  // Updating the settings for a non-existent log handler should fail
668  config =
669  parseLogConfig("WARN:default; default=stream:stream=stderr,async=false");
671  config.update(parseLogConfig("INFO; other:async=true")),
672  std::invalid_argument,
673  "cannot update configuration for "
674  R"(unknown log handler "other")");
675 }
dynamic parseJson(StringPiece range)
Definition: json.cpp:900
#define EXPECT_THROW_RE(statement, exceptionType, pattern)
Definition: TestUtils.h:119
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
LogConfig parseLogConfig(StringPiece value)
AHArrayT::Config config
internal::UnorderedElementsAreMatcher< ::testing::tuple<> > UnorderedElementsAre()
internal::PairMatcher< FirstMatcher, SecondMatcher > Pair(FirstMatcher first_matcher, SecondMatcher second_matcher)
LogLevel
Definition: LogLevel.h:38
#define EXPECT_THAT(value, matcher)
dynamic logConfigToDynamic(const LogConfig &config)
LogConfig parseLogConfigJson(StringPiece value)
std::string toJson(dynamic const &dyn)
Definition: json.cpp:915
TEST(SequencedExecutor, CPUThreadPoolExecutor)
constexpr None none
Definition: Optional.h:87