2 * Copyright 2004-present Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
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>
26 using namespace folly;
28 using ::testing::Pair;
29 using ::testing::UnorderedElementsAre;
32 std::ostream& operator<<(std::ostream& os, const LogCategoryConfig& config) {
33 os << logLevelToString(config.level);
34 if (!config.inheritParentLevel) {
37 if (config.handlers.hasValue()) {
38 os << ":" << join(",", config.handlers.value());
43 std::ostream& operator<<(std::ostream& os, const LogHandlerConfig& config) {
44 os << (config.type ? config.type.value() : "[no type]");
46 for (const auto& opt : config.options) {
53 os << opt.first << "=" << opt.second;
59 TEST(LogConfig, parseBasic) {
60 auto config = parseLogConfig("");
61 EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
62 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
64 config = parseLogConfig(" ");
65 EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
66 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
68 config = parseLogConfig(".=ERROR,folly=DBG2");
70 config.getCategoryConfigs(),
72 Pair("", LogCategoryConfig{LogLevel::ERR, true}),
73 Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
74 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
76 config = parseLogConfig(" INFO , folly := FATAL ");
78 config.getCategoryConfigs(),
80 Pair("", LogCategoryConfig{LogLevel::INFO, true}),
81 Pair("folly", LogCategoryConfig{LogLevel::FATAL, false})));
82 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
85 parseLogConfig("my.category:=INFO , my.other.stuff := 19,foo.bar=DBG7");
87 config.getCategoryConfigs(),
89 Pair("my.category", LogCategoryConfig{LogLevel::INFO, false}),
92 LogCategoryConfig{static_cast<LogLevel>(19), false}),
93 Pair("foo.bar", LogCategoryConfig{LogLevel::DBG7, true})));
94 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
96 config = parseLogConfig(" ERR ");
98 config.getCategoryConfigs(),
99 UnorderedElementsAre(Pair("", LogCategoryConfig{LogLevel::ERR, true})));
100 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
102 config = parseLogConfig(" ERR: ");
104 config.getCategoryConfigs(),
105 UnorderedElementsAre(
106 Pair("", LogCategoryConfig{LogLevel::ERR, true, {}})));
107 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
109 config = parseLogConfig(" ERR:stderr; stderr=stream:stream=stderr ");
111 config.getCategoryConfigs(),
112 UnorderedElementsAre(
113 Pair("", LogCategoryConfig{LogLevel::ERR, true, {"stderr"}})));
115 config.getHandlerConfigs(),
116 UnorderedElementsAre(
117 Pair("stderr", LogHandlerConfig{"stream", {{"stream", "stderr"}}})));
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; "
125 config.getCategoryConfigs(),
126 UnorderedElementsAre(
128 "", LogCategoryConfig{LogLevel::ERR, true, {"myfile", "custom"}}),
129 Pair("folly", LogCategoryConfig{LogLevel::DBG2, true}),
132 LogCategoryConfig{LogLevel::WARN, false, {"other"}})));
134 config.getHandlerConfigs(),
135 UnorderedElementsAre(
136 Pair("myfile", LogHandlerConfig{"file", {{"path", "/tmp/x.log"}}}),
141 {{"foo", "bar"}, {"hello", "world"}, {"a", "b = c"}}}),
142 Pair("other", LogHandlerConfig{"custom2"})));
144 // Test updating existing handler configs, with no handler type
145 config = parseLogConfig("ERR;foo");
147 config.getCategoryConfigs(),
148 UnorderedElementsAre(Pair("", LogCategoryConfig{LogLevel::ERR, true})));
150 config.getHandlerConfigs(),
151 UnorderedElementsAre(Pair("foo", LogHandlerConfig{})));
153 config = parseLogConfig("ERR;foo:a=b,c=d");
155 config.getCategoryConfigs(),
156 UnorderedElementsAre(Pair("", LogCategoryConfig{LogLevel::ERR, true})));
158 config.getHandlerConfigs(),
159 UnorderedElementsAre(Pair(
160 "foo", LogHandlerConfig{folly::none, {{"a", "b"}, {"c", "d"}}})));
162 config = parseLogConfig("ERR;test=file:path=/tmp/test.log;foo:a=b,c=d");
164 config.getCategoryConfigs(),
165 UnorderedElementsAre(Pair("", LogCategoryConfig{LogLevel::ERR, true})));
167 config.getHandlerConfigs(),
168 UnorderedElementsAre(
169 Pair("foo", LogHandlerConfig{folly::none, {{"a", "b"}, {"c", "d"}}}),
170 Pair("test", LogHandlerConfig{"file", {{"path", "/tmp/test.log"}}})));
172 // Log handler changes with no category changes
173 config = parseLogConfig("; myhandler=custom:foo=bar");
174 EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
176 config.getHandlerConfigs(),
177 UnorderedElementsAre(
178 Pair("myhandler", LogHandlerConfig{"custom", {{"foo", "bar"}}})));
181 TEST(LogConfig, parseBasicErrors) {
182 // Errors in the log category settings
184 parseLogConfig("=="),
186 R"(invalid log level "=" for category "")");
188 parseLogConfig("bogus_level"),
190 R"(invalid log level "bogus_level" for category ".")");
192 parseLogConfig("foo=bogus_level"),
194 R"(invalid log level "bogus_level" for category "foo")");
196 parseLogConfig("foo=WARN,bar=invalid"),
198 R"(invalid log level "invalid" for category "bar")");
200 parseLogConfig("foo=WARN,bar="),
202 R"(invalid log level "" for category "bar")");
204 parseLogConfig("foo=WARN,bar:="),
206 R"(invalid log level "" for category "bar")");
208 parseLogConfig("foo:=,bar:=WARN"),
210 R"(invalid log level "" for category "foo")");
214 R"(invalid log level "x" for category ".")");
216 parseLogConfig("x,y,z"),
218 R"(invalid log level "x" for category ".")");
220 parseLogConfig("foo=WARN,"),
222 R"(invalid log level "" for category ".")");
226 R"(invalid log level "" for category "")");
228 parseLogConfig(":="),
230 R"(invalid log level "" for category "")");
232 parseLogConfig("foo=bar=ERR"),
234 R"(invalid log level "bar=ERR" for category "foo")");
236 parseLogConfig("foo.bar=ERR,foo..bar=INFO"),
238 R"(category "foo\.bar" listed multiple times under different names: )"
239 R"("foo\.+bar" and "foo\.+bar")");
241 parseLogConfig("=ERR,.=INFO"),
243 R"(category "" listed multiple times under different names: )"
244 R"("\.?" and "\.?")");
246 // Errors in the log handler settings
248 parseLogConfig("ERR;"),
250 "error parsing log handler configuration: empty log handler name");
252 parseLogConfig("ERR;foo="),
254 R"(error parsing configuration for log handler "foo": )"
255 "empty log handler type");
257 parseLogConfig("ERR;=file"),
259 "error parsing log handler configuration: empty log handler name");
261 parseLogConfig("ERR;handler1=file;"),
263 "error parsing log handler configuration: empty log handler name");
265 parseLogConfig("ERR;test=file,path=/tmp/test.log;foo:a=b,c=d"),
267 R"(error parsing configuration for log handler "test": )"
268 R"(invalid type "file,path=/tmp/test.log": type name cannot contain )"
269 "a comma when using the basic config format");
271 parseLogConfig("ERR;test,path=/tmp/test.log;foo:a=b,c=d"),
273 R"(error parsing configuration for log handler "test,path": )"
274 "name cannot contain a comma when using the basic config format");
277 TEST(LogConfig, parseJson) {
278 auto config = parseLogConfig("{}");
279 EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
280 config = parseLogConfig(" {} ");
281 EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
283 config = parseLogConfig(R"JSON({
290 config.getCategoryConfigs(),
291 UnorderedElementsAre(
292 Pair("", LogCategoryConfig{LogLevel::ERR, true}),
293 Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
294 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
296 config = parseLogConfig(R"JSON({
303 config.getCategoryConfigs(),
304 UnorderedElementsAre(
305 Pair("", LogCategoryConfig{LogLevel::ERR, true}),
306 Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
307 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
309 config = parseLogConfig(R"JSON({
311 ".": { "level": "INFO" },
312 "folly": { "level": "FATAL", "inherit": false },
316 config.getCategoryConfigs(),
317 UnorderedElementsAre(
318 Pair("", LogCategoryConfig{LogLevel::INFO, true}),
319 Pair("folly", LogCategoryConfig{LogLevel::FATAL, false})));
320 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
322 config = parseLogConfig(R"JSON({
324 "my.category": { "level": "INFO", "inherit": true },
325 // comments are allowed
326 "my.other.stuff": { "level": 19, "inherit": false },
327 "foo.bar": { "level": "DBG7" },
330 "h1": { "type": "custom", "options": {"foo": "bar", "a": "z"} }
334 config.getCategoryConfigs(),
335 UnorderedElementsAre(
336 Pair("my.category", LogCategoryConfig{LogLevel::INFO, true}),
339 LogCategoryConfig{static_cast<LogLevel>(19), false}),
340 Pair("foo.bar", LogCategoryConfig{LogLevel::DBG7, true})));
342 config.getHandlerConfigs(),
343 UnorderedElementsAre(Pair(
344 "h1", LogHandlerConfig{"custom", {{"foo", "bar"}, {"a", "z"}}})));
346 // The JSON config parsing should allow unusual log category names
347 // containing whitespace, equal signs, and other characters not allowed in
348 // the basic config style.
349 config = parseLogConfig(R"JSON({
351 " my.category ": { "level": "INFO" },
352 " foo; bar=asdf, test": { "level": "DBG1" },
355 "h1;h2,h3= ": { "type": " x;y " }
359 config.getCategoryConfigs(),
360 UnorderedElementsAre(
361 Pair(" my.category ", LogCategoryConfig{LogLevel::INFO, true}),
363 " foo; bar=asdf, test",
364 LogCategoryConfig{LogLevel::DBG1, true})));
366 config.getHandlerConfigs(),
367 UnorderedElementsAre(Pair("h1;h2,h3= ", LogHandlerConfig{" x;y "})));
370 TEST(LogConfig, parseJsonErrors) {
372 parseLogConfigJson("5"),
374 "JSON config input must be an object");
376 parseLogConfigJson("true"),
378 "JSON config input must be an object");
380 parseLogConfigJson(R"("hello")"),
382 "JSON config input must be an object");
384 parseLogConfigJson("[1, 2, 3]"),
386 "JSON config input must be an object");
388 parseLogConfigJson(""), std::runtime_error, "json parse error");
390 parseLogConfigJson("{"), std::runtime_error, "json parse error");
391 EXPECT_THROW_RE(parseLogConfig("{"), std::runtime_error, "json parse error");
393 parseLogConfig("{}}"), std::runtime_error, "json parse error");
395 StringPiece input = R"JSON({
399 parseLogConfig(input),
401 "unexpected data type for log categories config: "
402 "got integer, expected an object");
409 parseLogConfig(input),
411 R"(unexpected data type for configuration of category "foo": )"
412 "got boolean, expected an object, string, or integer");
420 parseLogConfig(input),
422 R"(unexpected data type for configuration of category "foo": )"
423 "got array, expected an object, string, or integer");
427 ".": { "level": "INFO" },
428 "folly": { "level": "FATAL", "inherit": 19 },
432 parseLogConfig(input),
434 R"(unexpected data type for inherit field of category "folly": )"
435 "got integer, expected a boolean");
438 "folly": { "level": [], },
442 parseLogConfig(input),
444 R"(unexpected data type for level field of category "folly": )"
445 "got array, expected a string or integer");
452 parseLogConfig(input), std::runtime_error, "json parse error");
456 "foo...bar": { "level": "INFO", },
457 "foo..bar": { "level": "INFO", },
461 parseLogConfig(input),
463 R"(category "foo\.bar" listed multiple times under different names: )"
464 R"("foo\.\.+bar" and "foo\.+bar")");
467 "...": { "level": "ERR", },
468 "": { "level": "INFO", },
472 parseLogConfig(input),
474 R"(category "" listed multiple times under different names: )"
475 R"X("(\.\.\.|)" and "(\.\.\.|)")X");
478 "categories": { "folly": { "level": "ERR" } },
482 parseLogConfig(input),
484 "unexpected data type for log handlers config: "
485 "got double, expected an object");
488 "categories": { "folly": { "level": "ERR" } },
494 parseLogConfig(input),
496 R"(unexpected data type for configuration of handler "foo": )"
497 "got string, expected an object");
500 "categories": { "folly": { "level": "ERR" } },
506 parseLogConfig(input),
508 R"(no handler type specified for log handler "foo")");
511 "categories": { "folly": { "level": "ERR" } },
519 parseLogConfig(input),
521 R"(unexpected data type for "type" field of handler "foo": )"
522 "got integer, expected a string");
525 "categories": { "folly": { "level": "ERR" } },
534 parseLogConfig(input),
536 R"(unexpected data type for "options" field of handler "foo": )"
537 "got boolean, expected an object");
540 "categories": { "folly": { "level": "ERR" } },
544 "options": ["foo", "bar"]
549 parseLogConfig(input),
551 R"(unexpected data type for "options" field of handler "foo": )"
552 "got array, expected an object");
555 "categories": { "folly": { "level": "ERR" } },
559 "options": {"bar": 5}
564 parseLogConfig(input),
566 R"(unexpected data type for option "bar" of handler "foo": )"
567 "got integer, expected a string");
570 TEST(LogConfig, toJson) {
571 auto config = parseLogConfig("");
572 auto expectedJson = folly::parseJson(R"JSON({
576 EXPECT_EQ(expectedJson, logConfigToDynamic(config));
578 config = parseLogConfig(
579 "ERROR:h1,foo.bar:=FATAL,folly=INFO:; "
580 "h1=custom:foo=bar");
581 expectedJson = folly::parseJson(R"JSON({
601 "options": { "foo": "bar" }
605 EXPECT_EQ(expectedJson, logConfigToDynamic(config));
608 TEST(LogConfig, mergeConfigs) {
609 auto config = parseLogConfig("bar=ERR:");
610 config.update(parseLogConfig("foo:=INFO"));
612 config.getCategoryConfigs(),
613 UnorderedElementsAre(
614 Pair("foo", LogCategoryConfig{LogLevel::INFO, false}),
615 Pair("bar", LogCategoryConfig{LogLevel::ERR, true, {}})));
616 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
619 parseLogConfig("WARN:default; default=custom:opt1=value1,opt2=value2");
620 config.update(parseLogConfig("folly.io=DBG2,foo=INFO"));
622 config.getCategoryConfigs(),
623 UnorderedElementsAre(
624 Pair("", LogCategoryConfig{LogLevel::WARN, true, {"default"}}),
625 Pair("foo", LogCategoryConfig{LogLevel::INFO, true}),
626 Pair("folly.io", LogCategoryConfig{LogLevel::DBG2, true})));
628 config.getHandlerConfigs(),
629 UnorderedElementsAre(Pair(
632 "custom", {{"opt1", "value1"}, {"opt2", "value2"}}))));
634 // Updating the root category's log level without specifying
635 // handlers should leave its current handler list intact
637 parseLogConfig("WARN:default; default=custom:opt1=value1,opt2=value2");
638 config.update(parseLogConfig("ERR"));
640 config.getCategoryConfigs(),
641 UnorderedElementsAre(
642 Pair("", LogCategoryConfig{LogLevel::ERR, true, {"default"}})));
644 config.getHandlerConfigs(),
645 UnorderedElementsAre(Pair(
648 "custom", {{"opt1", "value1"}, {"opt2", "value2"}}))));
651 parseLogConfig("WARN:default; default=custom:opt1=value1,opt2=value2");
652 config.update(parseLogConfig(".:=ERR"));
654 config.getCategoryConfigs(),
655 UnorderedElementsAre(
656 Pair("", LogCategoryConfig{LogLevel::ERR, false, {"default"}})));
658 config.getHandlerConfigs(),
659 UnorderedElementsAre(Pair(
662 "custom", {{"opt1", "value1"}, {"opt2", "value2"}}))));
664 // Test clearing the root category's log handlers
666 parseLogConfig("WARN:default; default=custom:opt1=value1,opt2=value2");
667 config.update(parseLogConfig("FATAL:"));
669 config.getCategoryConfigs(),
670 UnorderedElementsAre(
671 Pair("", LogCategoryConfig{LogLevel::FATAL, true, {}})));
673 config.getHandlerConfigs(),
674 UnorderedElementsAre(Pair(
677 "custom", {{"opt1", "value1"}, {"opt2", "value2"}}))));
679 // Test updating the settings on a log handler
681 parseLogConfig("WARN:default; default=stream:stream=stderr,async=false");
682 config.update(parseLogConfig("INFO; default:async=true"));
684 config.getCategoryConfigs(),
685 UnorderedElementsAre(
686 Pair("", LogCategoryConfig{LogLevel::INFO, true, {"default"}})));
688 config.getHandlerConfigs(),
689 UnorderedElementsAre(Pair(
692 "stream", {{"stream", "stderr"}, {"async", "true"}}))));
694 // Updating the settings for a non-existent log handler should fail
696 parseLogConfig("WARN:default; default=stream:stream=stderr,async=false");
698 config.update(parseLogConfig("INFO; other:async=true")),
699 std::invalid_argument,
700 "cannot update configuration for "
701 R"(unknown log handler "other")");