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) {
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=file,stream=stderr ");
111 config.getCategoryConfigs(),
112 UnorderedElementsAre(
113 Pair("", LogCategoryConfig{LogLevel::ERR, true, {"stderr"}})));
115 config.getHandlerConfigs(),
116 UnorderedElementsAre(
117 Pair("stderr", LogHandlerConfig{"file", {{"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 // Log handler changes with no category changes
145 config = parseLogConfig("; myhandler=custom,foo=bar");
146 EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
148 config.getHandlerConfigs(),
149 UnorderedElementsAre(
150 Pair("myhandler", LogHandlerConfig{"custom", {{"foo", "bar"}}})));
153 TEST(LogConfig, parseBasicErrors) {
154 // Errors in the log category settings
156 parseLogConfig("=="),
158 "invalid log level \"=\" for category \"\"");
160 parseLogConfig("bogus_level"),
162 "invalid log level \"bogus_level\" for category \".\"");
164 parseLogConfig("foo=bogus_level"),
166 "invalid log level \"bogus_level\" for category \"foo\"");
168 parseLogConfig("foo=WARN,bar=invalid"),
170 "invalid log level \"invalid\" for category \"bar\"");
172 parseLogConfig("foo=WARN,bar="),
174 "invalid log level \"\" for category \"bar\"");
176 parseLogConfig("foo=WARN,bar:="),
178 "invalid log level \"\" for category \"bar\"");
180 parseLogConfig("foo:=,bar:=WARN"),
182 "invalid log level \"\" for category \"foo\"");
186 "invalid log level \"x\" for category \".\"");
188 parseLogConfig("x,y,z"),
190 "invalid log level \"x\" for category \".\"");
192 parseLogConfig("foo=WARN,"),
194 "invalid log level \"\" for category \".\"");
198 "invalid log level \"\" for category \"\"");
200 parseLogConfig(":="),
202 "invalid log level \"\" for category \"\"");
204 parseLogConfig("foo=bar=ERR"),
206 "invalid log level \"bar=ERR\" for category \"foo\"");
208 parseLogConfig("foo.bar=ERR,foo..bar=INFO"),
210 "category \"foo\\.bar\" listed multiple times under different names: "
211 "\"foo\\.+bar\" and \"foo\\.+bar\"");
213 parseLogConfig("=ERR,.=INFO"),
215 "category \"\" listed multiple times under different names: "
216 "\"\\.?\" and \"\\.?\"");
218 // Errors in the log handler settings
220 parseLogConfig("ERR;"),
222 "error parsing log handler configuration \"\": "
223 "expected data in the form NAME=TYPE");
225 parseLogConfig("ERR;foo"),
227 "error parsing log handler configuration \"foo\": "
228 "expected data in the form NAME=TYPE");
230 parseLogConfig("ERR;foo="),
232 "error parsing configuration for log handler \"foo\": "
233 "empty log handler type");
235 parseLogConfig("ERR;=file"),
237 "error parsing log handler configuration: empty log handler name");
239 parseLogConfig("ERR;handler1=file;"),
241 "error parsing log handler configuration \"\": "
242 "expected data in the form NAME=TYPE");
245 TEST(LogConfig, parseJson) {
246 auto config = parseLogConfig("{}");
247 EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
248 config = parseLogConfig(" {} ");
249 EXPECT_THAT(config.getCategoryConfigs(), UnorderedElementsAre());
251 config = parseLogConfig(R"JSON({
258 config.getCategoryConfigs(),
259 UnorderedElementsAre(
260 Pair("", LogCategoryConfig{LogLevel::ERR, true}),
261 Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
262 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
264 config = parseLogConfig(R"JSON({
271 config.getCategoryConfigs(),
272 UnorderedElementsAre(
273 Pair("", LogCategoryConfig{LogLevel::ERR, true}),
274 Pair("folly", LogCategoryConfig{LogLevel::DBG2, true})));
275 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
277 config = parseLogConfig(R"JSON({
279 ".": { "level": "INFO" },
280 "folly": { "level": "FATAL", "inherit": false },
284 config.getCategoryConfigs(),
285 UnorderedElementsAre(
286 Pair("", LogCategoryConfig{LogLevel::INFO, true}),
287 Pair("folly", LogCategoryConfig{LogLevel::FATAL, false})));
288 EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre());
290 config = parseLogConfig(R"JSON({
292 "my.category": { "level": "INFO", "inherit": true },
293 // comments are allowed
294 "my.other.stuff": { "level": 19, "inherit": false },
295 "foo.bar": { "level": "DBG7" },
298 "h1": { "type": "custom", "options": {"foo": "bar", "a": "z"} }
302 config.getCategoryConfigs(),
303 UnorderedElementsAre(
304 Pair("my.category", LogCategoryConfig{LogLevel::INFO, true}),
307 LogCategoryConfig{static_cast<LogLevel>(19), false}),
308 Pair("foo.bar", LogCategoryConfig{LogLevel::DBG7, true})));
310 config.getHandlerConfigs(),
311 UnorderedElementsAre(Pair(
312 "h1", LogHandlerConfig{"custom", {{"foo", "bar"}, {"a", "z"}}})));
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({
319 " my.category ": { "level": "INFO" },
320 " foo; bar=asdf, test": { "level": "DBG1" },
323 "h1;h2,h3= ": { "type": " x;y " }
327 config.getCategoryConfigs(),
328 UnorderedElementsAre(
329 Pair(" my.category ", LogCategoryConfig{LogLevel::INFO, true}),
331 " foo; bar=asdf, test",
332 LogCategoryConfig{LogLevel::DBG1, true})));
334 config.getHandlerConfigs(),
335 UnorderedElementsAre(Pair("h1;h2,h3= ", LogHandlerConfig{" x;y "})));
338 TEST(LogConfig, parseJsonErrors) {
340 parseLogConfigJson("5"),
342 "JSON config input must be an object");
344 parseLogConfigJson("true"),
346 "JSON config input must be an object");
348 parseLogConfigJson("\"hello\""),
350 "JSON config input must be an object");
352 parseLogConfigJson("[1, 2, 3]"),
354 "JSON config input must be an object");
356 parseLogConfigJson(""), std::runtime_error, "json parse error");
358 parseLogConfigJson("{"), std::runtime_error, "json parse error");
359 EXPECT_THROW_RE(parseLogConfig("{"), std::runtime_error, "json parse error");
361 parseLogConfig("{}}"), std::runtime_error, "json parse error");
363 StringPiece input = R"JSON({
367 parseLogConfig(input),
369 "unexpected data type for log categories config: "
370 "got integer, expected an object");
377 parseLogConfig(input),
379 "unexpected data type for configuration of category \"foo\": "
380 "got boolean, expected an object, string, or integer");
388 parseLogConfig(input),
390 "unexpected data type for configuration of category \"foo\": "
391 "got array, expected an object, string, or integer");
395 ".": { "level": "INFO" },
396 "folly": { "level": "FATAL", "inherit": 19 },
400 parseLogConfig(input),
402 "unexpected data type for inherit field of category \"folly\": "
403 "got integer, expected a boolean");
406 "folly": { "level": [], },
410 parseLogConfig(input),
412 "unexpected data type for level field of category \"folly\": "
413 "got array, expected a string or integer");
420 parseLogConfig(input), std::runtime_error, "json parse error");
424 "foo...bar": { "level": "INFO", },
425 "foo..bar": { "level": "INFO", },
429 parseLogConfig(input),
431 "category \"foo\\.bar\" listed multiple times under different names: "
432 "\"foo\\.\\.+bar\" and \"foo\\.+bar\"");
435 "...": { "level": "ERR", },
436 "": { "level": "INFO", },
440 parseLogConfig(input),
442 "category \"\" listed multiple times under different names: "
443 "\"(\\.\\.\\.|)\" and \"(\\.\\.\\.|)\"");
446 "categories": { "folly": { "level": "ERR" } },
450 parseLogConfig(input),
452 "unexpected data type for log handlers config: "
453 "got double, expected an object");
456 "categories": { "folly": { "level": "ERR" } },
462 parseLogConfig(input),
464 "unexpected data type for configuration of handler \"foo\": "
465 "got string, expected an object");
468 "categories": { "folly": { "level": "ERR" } },
474 parseLogConfig(input),
476 "no handler type specified for log handler \"foo\"");
479 "categories": { "folly": { "level": "ERR" } },
487 parseLogConfig(input),
489 "unexpected data type for \"type\" field of handler \"foo\": "
490 "got integer, expected a string");
493 "categories": { "folly": { "level": "ERR" } },
502 parseLogConfig(input),
504 "unexpected data type for \"options\" field of handler \"foo\": "
505 "got boolean, expected an object");
508 "categories": { "folly": { "level": "ERR" } },
512 "options": ["foo", "bar"]
517 parseLogConfig(input),
519 "unexpected data type for \"options\" field of handler \"foo\": "
520 "got array, expected an object");
523 "categories": { "folly": { "level": "ERR" } },
527 "options": {"bar": 5}
532 parseLogConfig(input),
534 "unexpected data type for option \"bar\" of handler \"foo\": "
535 "got integer, expected a string");
538 TEST(LogConfig, toJson) {
539 auto config = parseLogConfig("");
540 auto expectedJson = folly::parseJson(R"JSON({
544 EXPECT_EQ(expectedJson, logConfigToDynamic(config));
546 config = parseLogConfig(
547 "ERROR:h1,foo.bar:=FATAL,folly=INFO:; "
548 "h1=custom,foo=bar");
549 expectedJson = folly::parseJson(R"JSON({
569 "options": { "foo": "bar" }
573 EXPECT_EQ(expectedJson, logConfigToDynamic(config));