ac95884b275782e6611fdcaae034715c8276fd87
[folly.git] / folly / experimental / logging / test / ConfigParserTest.cpp
1 /*
2  * Copyright 2004-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/experimental/logging/LogCategory.h>
19 #include <folly/experimental/logging/LogConfig.h>
20 #include <folly/experimental/logging/LogConfigParser.h>
21 #include <folly/json.h>
22 #include <folly/portability/GMock.h>
23 #include <folly/portability/GTest.h>
24 #include <folly/test/TestUtils.h>
25
26 using namespace folly;
27
28 using ::testing::Pair;
29 using ::testing::UnorderedElementsAre;
30
31 namespace folly {
32 std::ostream& operator<<(std::ostream& os, const LogCategoryConfig& config) {
33   os << logLevelToString(config.level);
34   if (!config.inheritParentLevel) {
35     os << "!";
36   }
37   if (config.handlers.hasValue()) {
38     os << ":" << join(",", config.handlers.value());
39   }
40   return os;
41 }
42
43 std::ostream& operator<<(std::ostream& os, const LogHandlerConfig& config) {
44   os << config.type;
45   bool first = true;
46   for (const auto& opt : config.options) {
47     if (!first) {
48       os << ",";
49     } else {
50       os << ":";
51       first = false;
52     }
53     os << opt.first << "=" << opt.second;
54   }
55   return os;
56 }
57 } // namespace folly
58
59 TEST(LogConfig, parseBasic) {
60   auto config = parseLogConfig("");
61   EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
62   EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
63
64   config = parseLogConfig("   ");
65   EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
66   EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
67
68   config = parseLogConfig(".=ERROR,folly=DBG2");
69   EXPECT_THAT(
70       config.getCategoryConfigs(),
71       UnorderedElementsAre(
72           Pair("", LogCategoryConfig{LogLevel::ERR, true}),
73           Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
74   EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
75
76   config = parseLogConfig(" INFO , folly  := FATAL   ");
77   EXPECT_THAT(
78       config.getCategoryConfigs(),
79       UnorderedElementsAre(
80           Pair("", LogCategoryConfig{LogLevel::INFO, true}),
81           Pair("folly", LogCategoryConfig{LogLevel::FATAL, false})));
82   EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
83
84   config =
85       parseLogConfig("my.category:=INFO , my.other.stuff  := 19,foo.bar=DBG7");
86   EXPECT_THAT(
87       config.getCategoryConfigs(),
88       UnorderedElementsAre(
89           Pair("my.category", LogCategoryConfig{LogLevel::INFO, false}),
90           Pair(
91               "my.other.stuff",
92               LogCategoryConfig{static_cast<LogLevel>(19), false}),
93           Pair("foo.bar", LogCategoryConfig{LogLevel::DBG7, true})));
94   EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
95
96   config = parseLogConfig(" ERR ");
97   EXPECT_THAT(
98       config.getCategoryConfigs(),
99       UnorderedElementsAre(Pair("", LogCategoryConfig{LogLevel::ERR, true})));
100   EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
101
102   config = parseLogConfig(" ERR: ");
103   EXPECT_THAT(
104       config.getCategoryConfigs(),
105       UnorderedElementsAre(
106           Pair("", LogCategoryConfig{LogLevel::ERR, true, {}})));
107   EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
108
109   config = parseLogConfig(" ERR:stderr; stderr=file,stream=stderr ");
110   EXPECT_THAT(
111       config.getCategoryConfigs(),
112       UnorderedElementsAre(
113           Pair("", LogCategoryConfig{LogLevel::ERR, true, {"stderr"}})));
114   EXPECT_THAT(
115       config.getHandlerConfigs(),
116       UnorderedElementsAre(
117           Pair("stderr", LogHandlerConfig{"file", {{"stream", "stderr"}}})));
118
119   config = parseLogConfig(
120       "ERR:myfile:custom, folly=DBG2, folly.io:=WARN:other;"
121       "myfile=file,path=/tmp/x.log; "
122       "custom=custom,foo=bar,hello=world,a = b = c; "
123       "other=custom2");
124   EXPECT_THAT(
125       config.getCategoryConfigs(),
126       UnorderedElementsAre(
127           Pair(
128               "", LogCategoryConfig{LogLevel::ERR, true, {"myfile", "custom"}}),
129           Pair("folly", LogCategoryConfig{LogLevel::DBG2, true}),
130           Pair(
131               "folly.io",
132               LogCategoryConfig{LogLevel::WARN, false, {"other"}})));
133   EXPECT_THAT(
134       config.getHandlerConfigs(),
135       UnorderedElementsAre(
136           Pair("myfile", LogHandlerConfig{"file", {{"path", "/tmp/x.log"}}}),
137           Pair(
138               "custom",
139               LogHandlerConfig{
140                   "custom",
141                   {{"foo", "bar"}, {"hello", "world"}, {"a", "b = c"}}}),
142           Pair("other", LogHandlerConfig{"custom2"})));
143
144   // Log handler changes with no category changes
145   config = parseLogConfig("; myhandler=custom,foo=bar");
146   EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
147   EXPECT_THAT(
148       config.getHandlerConfigs(),
149       UnorderedElementsAre(
150           Pair("myhandler", LogHandlerConfig{"custom", {{"foo", "bar"}}})));
151 }
152
153 TEST(LogConfig, parseBasicErrors) {
154   // Errors in the log category settings
155   EXPECT_THROW_RE(
156       parseLogConfig("=="),
157       LogConfigParseError,
158       "invalid log level \"=\" for category \"\"");
159   EXPECT_THROW_RE(
160       parseLogConfig("bogus_level"),
161       LogConfigParseError,
162       "invalid log level \"bogus_level\" for category \".\"");
163   EXPECT_THROW_RE(
164       parseLogConfig("foo=bogus_level"),
165       LogConfigParseError,
166       "invalid log level \"bogus_level\" for category \"foo\"");
167   EXPECT_THROW_RE(
168       parseLogConfig("foo=WARN,bar=invalid"),
169       LogConfigParseError,
170       "invalid log level \"invalid\" for category \"bar\"");
171   EXPECT_THROW_RE(
172       parseLogConfig("foo=WARN,bar="),
173       LogConfigParseError,
174       "invalid log level \"\" for category \"bar\"");
175   EXPECT_THROW_RE(
176       parseLogConfig("foo=WARN,bar:="),
177       LogConfigParseError,
178       "invalid log level \"\" for category \"bar\"");
179   EXPECT_THROW_RE(
180       parseLogConfig("foo:=,bar:=WARN"),
181       LogConfigParseError,
182       "invalid log level \"\" for category \"foo\"");
183   EXPECT_THROW_RE(
184       parseLogConfig("x"),
185       LogConfigParseError,
186       "invalid log level \"x\" for category \".\"");
187   EXPECT_THROW_RE(
188       parseLogConfig("x,y,z"),
189       LogConfigParseError,
190       "invalid log level \"x\" for category \".\"");
191   EXPECT_THROW_RE(
192       parseLogConfig("foo=WARN,"),
193       LogConfigParseError,
194       "invalid log level \"\" for category \".\"");
195   EXPECT_THROW_RE(
196       parseLogConfig("="),
197       LogConfigParseError,
198       "invalid log level \"\" for category \"\"");
199   EXPECT_THROW_RE(
200       parseLogConfig(":="),
201       LogConfigParseError,
202       "invalid log level \"\" for category \"\"");
203   EXPECT_THROW_RE(
204       parseLogConfig("foo=bar=ERR"),
205       LogConfigParseError,
206       "invalid log level \"bar=ERR\" for category \"foo\"");
207   EXPECT_THROW_RE(
208       parseLogConfig("foo.bar=ERR,foo..bar=INFO"),
209       LogConfigParseError,
210       "category \"foo\\.bar\" listed multiple times under different names: "
211       "\"foo\\.+bar\" and \"foo\\.+bar\"");
212   EXPECT_THROW_RE(
213       parseLogConfig("=ERR,.=INFO"),
214       LogConfigParseError,
215       "category \"\" listed multiple times under different names: "
216       "\"\\.?\" and \"\\.?\"");
217
218   // Errors in the log handler settings
219   EXPECT_THROW_RE(
220       parseLogConfig("ERR;"),
221       LogConfigParseError,
222       "error parsing log handler configuration \"\": "
223       "expected data in the form NAME=TYPE");
224   EXPECT_THROW_RE(
225       parseLogConfig("ERR;foo"),
226       LogConfigParseError,
227       "error parsing log handler configuration \"foo\": "
228       "expected data in the form NAME=TYPE");
229   EXPECT_THROW_RE(
230       parseLogConfig("ERR;foo="),
231       LogConfigParseError,
232       "error parsing configuration for log handler \"foo\": "
233       "empty log handler type");
234   EXPECT_THROW_RE(
235       parseLogConfig("ERR;=file"),
236       LogConfigParseError,
237       "error parsing log handler configuration: empty log handler name");
238   EXPECT_THROW_RE(
239       parseLogConfig("ERR;handler1=file;"),
240       LogConfigParseError,
241       "error parsing log handler configuration \"\": "
242       "expected data in the form NAME=TYPE");
243 }
244
245 TEST(LogConfig, parseJson) {
246   auto config = parseLogConfig("{}");
247   EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
248   config = parseLogConfig("  {}   ");
249   EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
250
251   config = parseLogConfig(R"JSON({
252     "categories": {
253       ".": "ERROR",
254       "folly": "DBG2",
255     }
256   })JSON");
257   EXPECT_THAT(
258       config.getCategoryConfigs(),
259       UnorderedElementsAre(
260           Pair("", LogCategoryConfig{LogLevel::ERR, true}),
261           Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
262   EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
263
264   config = parseLogConfig(R"JSON({
265     "categories": {
266       "": "ERROR",
267       "folly": "DBG2",
268     }
269   })JSON");
270   EXPECT_THAT(
271       config.getCategoryConfigs(),
272       UnorderedElementsAre(
273           Pair("", LogCategoryConfig{LogLevel::ERR, true}),
274           Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
275   EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
276
277   config = parseLogConfig(R"JSON({
278     "categories": {
279       ".": { "level": "INFO" },
280       "folly": { "level": "FATAL", "inherit": false },
281     }
282   })JSON");
283   EXPECT_THAT(
284       config.getCategoryConfigs(),
285       UnorderedElementsAre(
286           Pair("", LogCategoryConfig{LogLevel::INFO, true}),
287           Pair("folly", LogCategoryConfig{LogLevel::FATAL, false})));
288   EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
289
290   config = parseLogConfig(R"JSON({
291     "categories": {
292       "my.category": { "level": "INFO", "inherit": true },
293       // comments are allowed
294       "my.other.stuff": { "level": 19, "inherit": false },
295       "foo.bar": { "level": "DBG7" },
296     },
297     "handlers": {
298       "h1": { "type": "custom", "options": {"foo": "bar", "a": "z"} }
299     }
300   })JSON");
301   EXPECT_THAT(
302       config.getCategoryConfigs(),
303       UnorderedElementsAre(
304           Pair("my.category", LogCategoryConfig{LogLevel::INFO, true}),
305           Pair(
306               "my.other.stuff",
307               LogCategoryConfig{static_cast<LogLevel>(19), false}),
308           Pair("foo.bar", LogCategoryConfig{LogLevel::DBG7, true})));
309   EXPECT_THAT(
310       config.getHandlerConfigs(),
311       UnorderedElementsAre(Pair(
312           "h1", LogHandlerConfig{"custom", {{"foo", "bar"}, {"a", "z"}}})));
313
314   // The JSON config parsing should allow unusual log category names
315   // containing whitespace, equal signs, and other characters not allowed in
316   // the basic config style.
317   config = parseLogConfig(R"JSON({
318     "categories": {
319       "  my.category  ": { "level": "INFO" },
320       " foo; bar=asdf, test": { "level": "DBG1" },
321     },
322     "handlers": {
323       "h1;h2,h3= ": { "type": " x;y " }
324     }
325   })JSON");
326   EXPECT_THAT(
327       config.getCategoryConfigs(),
328       UnorderedElementsAre(
329           Pair("  my.category  ", LogCategoryConfig{LogLevel::INFO, true}),
330           Pair(
331               " foo; bar=asdf, test",
332               LogCategoryConfig{LogLevel::DBG1, true})));
333   EXPECT_THAT(
334       config.getHandlerConfigs(),
335       UnorderedElementsAre(Pair("h1;h2,h3= ", LogHandlerConfig{" x;y "})));
336 }
337
338 TEST(LogConfig, parseJsonErrors) {
339   EXPECT_THROW_RE(
340       parseLogConfigJson("5"),
341       LogConfigParseError,
342       "JSON config input must be an object");
343   EXPECT_THROW_RE(
344       parseLogConfigJson("true"),
345       LogConfigParseError,
346       "JSON config input must be an object");
347   EXPECT_THROW_RE(
348       parseLogConfigJson("\"hello\""),
349       LogConfigParseError,
350       "JSON config input must be an object");
351   EXPECT_THROW_RE(
352       parseLogConfigJson("[1, 2, 3]"),
353       LogConfigParseError,
354       "JSON config input must be an object");
355   EXPECT_THROW_RE(
356       parseLogConfigJson(""), std::runtime_error, "json parse error");
357   EXPECT_THROW_RE(
358       parseLogConfigJson("{"), std::runtime_error, "json parse error");
359   EXPECT_THROW_RE(parseLogConfig("{"), std::runtime_error, "json parse error");
360   EXPECT_THROW_RE(
361       parseLogConfig("{}}"), std::runtime_error, "json parse error");
362
363   StringPiece input = R"JSON({
364     "categories": 5
365   })JSON";
366   EXPECT_THROW_RE(
367       parseLogConfig(input),
368       LogConfigParseError,
369       "unexpected data type for log categories config: "
370       "got integer, expected an object");
371   input = R"JSON({
372     "categories": {
373       "foo": true,
374     }
375   })JSON";
376   EXPECT_THROW_RE(
377       parseLogConfig(input),
378       LogConfigParseError,
379       "unexpected data type for configuration of category \"foo\": "
380       "got boolean, expected an object, string, or integer");
381
382   input = R"JSON({
383     "categories": {
384       "foo": [1, 2, 3],
385     }
386   })JSON";
387   EXPECT_THROW_RE(
388       parseLogConfig(input),
389       LogConfigParseError,
390       "unexpected data type for configuration of category \"foo\": "
391       "got array, expected an object, string, or integer");
392
393   input = R"JSON({
394     "categories": {
395       ".": { "level": "INFO" },
396       "folly": { "level": "FATAL", "inherit": 19 },
397     }
398   })JSON";
399   EXPECT_THROW_RE(
400       parseLogConfig(input),
401       LogConfigParseError,
402       "unexpected data type for inherit field of category \"folly\": "
403       "got integer, expected a boolean");
404   input = R"JSON({
405     "categories": {
406       "folly": { "level": [], },
407     }
408   })JSON";
409   EXPECT_THROW_RE(
410       parseLogConfig(input),
411       LogConfigParseError,
412       "unexpected data type for level field of category \"folly\": "
413       "got array, expected a string or integer");
414   input = R"JSON({
415     "categories": {
416       5: {}
417     }
418   })JSON";
419   EXPECT_THROW_RE(
420       parseLogConfig(input), std::runtime_error, "json parse error");
421
422   input = R"JSON({
423     "categories": {
424       "foo...bar": { "level": "INFO", },
425       "foo..bar": { "level": "INFO", },
426     }
427   })JSON";
428   EXPECT_THROW_RE(
429       parseLogConfig(input),
430       LogConfigParseError,
431       "category \"foo\\.bar\" listed multiple times under different names: "
432       "\"foo\\.\\.+bar\" and \"foo\\.+bar\"");
433   input = R"JSON({
434     "categories": {
435       "...": { "level": "ERR", },
436       "": { "level": "INFO", },
437     }
438   })JSON";
439   EXPECT_THROW_RE(
440       parseLogConfig(input),
441       LogConfigParseError,
442       "category \"\" listed multiple times under different names: "
443       "\"(\\.\\.\\.|)\" and \"(\\.\\.\\.|)\"");
444
445   input = R"JSON({
446     "categories": { "folly": { "level": "ERR" } },
447     "handlers": 9.8
448   })JSON";
449   EXPECT_THROW_RE(
450       parseLogConfig(input),
451       LogConfigParseError,
452       "unexpected data type for log handlers config: "
453       "got double, expected an object");
454
455   input = R"JSON({
456     "categories": { "folly": { "level": "ERR" } },
457     "handlers": {
458       "foo": "test"
459     }
460   })JSON";
461   EXPECT_THROW_RE(
462       parseLogConfig(input),
463       LogConfigParseError,
464       "unexpected data type for configuration of handler \"foo\": "
465       "got string, expected an object");
466
467   input = R"JSON({
468     "categories": { "folly": { "level": "ERR" } },
469     "handlers": {
470       "foo": {}
471     }
472   })JSON";
473   EXPECT_THROW_RE(
474       parseLogConfig(input),
475       LogConfigParseError,
476       "no handler type specified for log handler \"foo\"");
477
478   input = R"JSON({
479     "categories": { "folly": { "level": "ERR" } },
480     "handlers": {
481       "foo": {
482         "type": 19
483       }
484     }
485   })JSON";
486   EXPECT_THROW_RE(
487       parseLogConfig(input),
488       LogConfigParseError,
489       "unexpected data type for \"type\" field of handler \"foo\": "
490       "got integer, expected a string");
491
492   input = R"JSON({
493     "categories": { "folly": { "level": "ERR" } },
494     "handlers": {
495       "foo": {
496         "type": "custom",
497         "options": true
498       }
499     }
500   })JSON";
501   EXPECT_THROW_RE(
502       parseLogConfig(input),
503       LogConfigParseError,
504       "unexpected data type for \"options\" field of handler \"foo\": "
505       "got boolean, expected an object");
506
507   input = R"JSON({
508     "categories": { "folly": { "level": "ERR" } },
509     "handlers": {
510       "foo": {
511         "type": "custom",
512         "options": ["foo", "bar"]
513       }
514     }
515   })JSON";
516   EXPECT_THROW_RE(
517       parseLogConfig(input),
518       LogConfigParseError,
519       "unexpected data type for \"options\" field of handler \"foo\": "
520       "got array, expected an object");
521
522   input = R"JSON({
523     "categories": { "folly": { "level": "ERR" } },
524     "handlers": {
525       "foo": {
526         "type": "custom",
527         "options": {"bar": 5}
528       }
529     }
530   })JSON";
531   EXPECT_THROW_RE(
532       parseLogConfig(input),
533       LogConfigParseError,
534       "unexpected data type for option \"bar\" of handler \"foo\": "
535       "got integer, expected a string");
536 }
537
538 TEST(LogConfig, toJson) {
539   auto config = parseLogConfig("");
540   auto expectedJson = folly::parseJson(R"JSON({
541   "categories": {},
542   "handlers": {}
543 })JSON");
544   EXPECT_EQ(expectedJson, logConfigToDynamic(config));
545
546   config = parseLogConfig(
547       "ERROR:h1,foo.bar:=FATAL,folly=INFO:; "
548       "h1=custom,foo=bar");
549   expectedJson = folly::parseJson(R"JSON({
550   "categories" : {
551     "" : {
552       "inherit" : true,
553       "level" : "ERR",
554       "handlers" : ["h1"]
555     },
556     "folly" : {
557       "inherit" : true,
558       "level" : "INFO",
559       "handlers" : []
560     },
561     "foo.bar" : {
562       "inherit" : false,
563       "level" : "FATAL"
564     }
565   },
566   "handlers" : {
567     "h1": {
568       "type": "custom",
569       "options": { "foo": "bar" }
570     }
571   }
572 })JSON");
573   EXPECT_EQ(expectedJson, logConfigToDynamic(config));
574 }