--- /dev/null
+/*
+ * Copyright 2004-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <folly/dynamic.h>
+#include <folly/experimental/logging/LogCategory.h>
+#include <folly/experimental/logging/LogConfig.h>
+#include <folly/experimental/logging/LogConfigParser.h>
+#include <folly/experimental/logging/LogHandlerFactory.h>
+#include <folly/experimental/logging/LoggerDB.h>
+#include <folly/experimental/logging/test/TestLogHandler.h>
+#include <folly/json.h>
+#include <folly/portability/GMock.h>
+#include <folly/portability/GTest.h>
+#include <folly/test/TestUtils.h>
+
+using namespace folly;
+using ::testing::Pair;
+using ::testing::UnorderedElementsAre;
+
+namespace {
+
+MATCHER_P(LogHandlerMatcherImpl, config, "") {
+ return arg->getConfig() == config;
+}
+
+/**
+ * A helper function to use in EXPECT_THAT() for matching a TestLogHandler
+ * with the specified type and options.
+ */
+auto MatchLogHandler(
+ StringPiece type,
+ std::unordered_map<std::string, std::string> options) {
+ return LogHandlerMatcherImpl(LogHandlerConfig{type, std::move(options)});
+}
+auto MatchLogHandler(const LogHandlerConfig& config) {
+ return LogHandlerMatcherImpl(config);
+}
+
+} // namespace
+
+namespace folly {
+/**
+ * Print TestLogHandler objects nicely in test failure messages
+ */
+std::ostream& operator<<(
+ std::ostream& os,
+ const std::shared_ptr<LogHandler>& handler) {
+ auto configHandler = std::dynamic_pointer_cast<TestLogHandler>(handler);
+ if (!configHandler) {
+ os << "unknown handler type";
+ return os;
+ }
+
+ auto config = configHandler->getConfig();
+ os << "ConfigHandler(" << config.type;
+ for (const auto& entry : config.options) {
+ os << ", " << entry.first << "=" << entry.second;
+ }
+ os << ")";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const LogConfig& config) {
+ os << toPrettyJson(logConfigToDynamic(config));
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const LogHandlerConfig& config) {
+ os << toPrettyJson(logConfigToDynamic(config));
+ return os;
+}
+} // namespace folly
+
+TEST(ConfigUpdate, updateLogLevels) {
+ LoggerDB db{LoggerDB::TESTING};
+ db.updateConfig(parseLogConfig("foo.bar=dbg5"));
+ EXPECT_EQ(LogLevel::DBG5, db.getCategory("foo.bar")->getLevel());
+ EXPECT_EQ(LogLevel::DBG5, db.getCategory("foo.bar")->getEffectiveLevel());
+ EXPECT_EQ(LogLevel::MAX_LEVEL, db.getCategory("foo")->getLevel());
+ EXPECT_EQ(LogLevel::ERR, db.getCategory("foo")->getEffectiveLevel());
+ EXPECT_EQ(LogLevel::ERR, db.getCategory("")->getLevel());
+ EXPECT_EQ(LogLevel::ERR, db.getCategory("")->getEffectiveLevel());
+
+ EXPECT_EQ(LogLevel::MAX_LEVEL, db.getCategory("foo.bar.test")->getLevel());
+ EXPECT_EQ(
+ LogLevel::DBG5, db.getCategory("foo.bar.test")->getEffectiveLevel());
+
+ db.updateConfig(
+ parseLogConfig("sys=warn,foo.test=debug,foo.test.stuff=warn"));
+ EXPECT_EQ(LogLevel::WARN, db.getCategory("sys")->getLevel());
+ EXPECT_EQ(LogLevel::WARN, db.getCategory("sys")->getEffectiveLevel());
+ EXPECT_EQ(LogLevel::DEBUG, db.getCategory("foo.test")->getLevel());
+ EXPECT_EQ(LogLevel::DEBUG, db.getCategory("foo.test")->getEffectiveLevel());
+ EXPECT_EQ(LogLevel::WARN, db.getCategory("foo.test.stuff")->getLevel());
+ EXPECT_EQ(
+ LogLevel::DEBUG, db.getCategory("foo.test.stuff")->getEffectiveLevel());
+ EXPECT_EQ(LogLevel::DBG5, db.getCategory("foo.bar")->getEffectiveLevel());
+}
+
+TEST(ConfigUpdate, updateConfig) {
+ LoggerDB db{LoggerDB::TESTING};
+ db.registerHandlerFactory(
+ std::make_unique<TestLogHandlerFactory>("handlerA"));
+ db.registerHandlerFactory(
+ std::make_unique<TestLogHandlerFactory>("handlerB"));
+ EXPECT_EQ(parseLogConfig(".:=ERROR:"), db.getConfig());
+
+ // Create some categories that aren't affected by our config updates below,
+ // just to ensure that they don't show up in getConfig() results since they
+ // have the default config settings.
+ db.getCategory("test.category1");
+ db.getCategory("test.category2");
+ EXPECT_EQ(parseLogConfig(".:=ERROR:"), db.getConfig());
+
+ // Apply an update
+ db.updateConfig(parseLogConfig("INFO:stderr; stderr=handlerA,stream=stderr"));
+ EXPECT_EQ(LogLevel::INFO, db.getCategory("")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("")->getHandlers(),
+ UnorderedElementsAre(
+ MatchLogHandler("handlerA", {{"stream", "stderr"}})));
+ EXPECT_EQ(
+ parseLogConfig(".:=INFO:stderr; stderr=handlerA,stream=stderr"),
+ db.getConfig());
+
+ // Update the log level for category "foo"
+ // This should not affect the existing settings for the root category
+ EXPECT_EQ(LogLevel::MAX_LEVEL, db.getCategory("foo")->getLevel());
+ EXPECT_EQ(true, db.getCategory("foo")->getLevelInfo().second);
+ db.updateConfig(parseLogConfig("foo:=DBG2"));
+ EXPECT_EQ(LogLevel::DBG2, db.getCategory("foo")->getLevel());
+ EXPECT_EQ(false, db.getCategory("foo")->getLevelInfo().second);
+ EXPECT_EQ(LogLevel::INFO, db.getCategory("")->getLevel());
+ EXPECT_EQ(1, db.getCategory("")->getHandlers().size());
+ EXPECT_EQ(
+ parseLogConfig(
+ ".:=INFO:stderr, foo:=DBG2:; stderr=handlerA,stream=stderr"),
+ db.getConfig());
+
+ // Add 2 log handlers to the "bar" log category.
+ db.updateConfig(
+ parseLogConfig("bar=ERROR:new:h2; "
+ "new=handlerB,key=value; "
+ "h2=handlerA,foo=bar"));
+ EXPECT_EQ(LogLevel::INFO, db.getCategory("")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("")->getHandlers(),
+ UnorderedElementsAre(
+ MatchLogHandler("handlerA", {{"stream", "stderr"}})));
+ EXPECT_EQ(LogLevel::ERR, db.getCategory("bar")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("bar")->getHandlers(),
+ UnorderedElementsAre(
+ MatchLogHandler("handlerB", {{"key", "value"}}),
+ MatchLogHandler("handlerA", {{"foo", "bar"}})));
+ EXPECT_EQ(
+ parseLogConfig(".:=INFO:stderr, foo:=DBG2:, bar=ERROR:new:h2; "
+ "stderr=handlerA,stream=stderr; "
+ "new=handlerB,key=value; "
+ "h2=handlerA,foo=bar"),
+ db.getConfig());
+
+ // Updating the "new" log handler settings should automatically update
+ // the settings we see on the "bar" category, even if we don't explicitly
+ // list "bar" in the config update
+ db.updateConfig(parseLogConfig("; new=handlerB,newkey=newvalue"));
+ EXPECT_EQ(LogLevel::INFO, db.getCategory("")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("")->getHandlers(),
+ UnorderedElementsAre(
+ MatchLogHandler("handlerA", {{"stream", "stderr"}})));
+ EXPECT_EQ(LogLevel::ERR, db.getCategory("bar")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("bar")->getHandlers(),
+ UnorderedElementsAre(
+ MatchLogHandler("handlerB", {{"newkey", "newvalue"}}),
+ MatchLogHandler("handlerA", {{"foo", "bar"}})));
+ EXPECT_EQ(
+ parseLogConfig(".:=INFO:stderr, foo:=DBG2:, bar=ERROR:new:h2; "
+ "stderr=handlerA,stream=stderr; "
+ "new=handlerB,newkey=newvalue; "
+ "h2=handlerA,foo=bar"),
+ db.getConfig());
+
+ // Updating the level settings for the "bar" handler should leave its
+ // handlers unchanged.
+ db.updateConfig(parseLogConfig("bar=WARN"));
+ EXPECT_EQ(LogLevel::INFO, db.getCategory("")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("")->getHandlers(),
+ UnorderedElementsAre(
+ MatchLogHandler("handlerA", {{"stream", "stderr"}})));
+ EXPECT_EQ(LogLevel::WARN, db.getCategory("bar")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("bar")->getHandlers(),
+ UnorderedElementsAre(
+ MatchLogHandler("handlerB", {{"newkey", "newvalue"}}),
+ MatchLogHandler("handlerA", {{"foo", "bar"}})));
+ EXPECT_EQ(
+ parseLogConfig(".:=INFO:stderr, foo:=DBG2:, bar=WARN:new:h2; "
+ "stderr=handlerA,stream=stderr; "
+ "new=handlerB,newkey=newvalue; "
+ "h2=handlerA,foo=bar"),
+ db.getConfig());
+
+ // Update the options for the h2 handler in place, and also add it to the
+ // "test.foo" category. The changes should also be reflected on the "bar"
+ // category.
+ db.updateConfig(
+ parseLogConfig("test.foo=INFO:h2; h2=handlerA,reuse_handler=1,foo=xyz"));
+ EXPECT_EQ(LogLevel::INFO, db.getCategory("")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("")->getHandlers(),
+ UnorderedElementsAre(
+ MatchLogHandler("handlerA", {{"stream", "stderr"}})));
+ EXPECT_EQ(LogLevel::WARN, db.getCategory("bar")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("bar")->getHandlers(),
+ UnorderedElementsAre(
+ MatchLogHandler("handlerB", {{"newkey", "newvalue"}}),
+ MatchLogHandler(
+ "handlerA", {{"foo", "xyz"}, {"reuse_handler", "1"}})));
+ EXPECT_EQ(LogLevel::INFO, db.getCategory("test.foo")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("test.foo")->getHandlers(),
+ UnorderedElementsAre(MatchLogHandler(
+ "handlerA", {{"foo", "xyz"}, {"reuse_handler", "1"}})));
+ EXPECT_EQ(
+ parseLogConfig(".:=INFO:stderr, foo:=DBG2:, bar=WARN:new:h2, "
+ "test.foo=INFO:h2; "
+ "stderr=handlerA,stream=stderr; "
+ "new=handlerB,newkey=newvalue; "
+ "h2=handlerA,reuse_handler=1,foo=xyz"),
+ db.getConfig());
+
+ // Explicitly clear the handlers for the "bar" category
+ // This should remove the "new" handler from the LoggerDB since bar was the
+ // only category referring to it.
+ db.updateConfig(parseLogConfig("bar=WARN:"));
+ EXPECT_EQ(LogLevel::INFO, db.getCategory("")->getLevel());
+ EXPECT_THAT(
+ db.getCategory("")->getHandlers(),
+ UnorderedElementsAre(
+ MatchLogHandler("handlerA", {{"stream", "stderr"}})));
+ EXPECT_EQ(LogLevel::WARN, db.getCategory("bar")->getLevel());
+ EXPECT_THAT(db.getCategory("bar")->getHandlers(), UnorderedElementsAre());
+ EXPECT_EQ(
+ parseLogConfig(".:=INFO:stderr, foo:=DBG2:, bar=WARN:, "
+ "test.foo=INFO:h2; "
+ "stderr=handlerA,stream=stderr; "
+ "h2=handlerA,reuse_handler=1,foo=xyz"),
+ db.getConfig());
+
+ // Now test resetConfig()
+ db.resetConfig(
+ parseLogConfig("bar=INFO:h2, test.abc=DBG3; "
+ "h2=handlerB,abc=xyz"));
+ EXPECT_EQ(
+ parseLogConfig(".:=ERR:, bar=INFO:h2, test.abc=DBG3:; "
+ "h2=handlerB,abc=xyz"),
+ db.getConfig());
+}
+
+TEST(ConfigUpdate, getConfigAnonymousHandlers) {
+ LoggerDB db{LoggerDB::TESTING};
+ db.registerHandlerFactory(
+ std::make_unique<TestLogHandlerFactory>("handlerA"));
+ db.registerHandlerFactory(
+ std::make_unique<TestLogHandlerFactory>("handlerB"));
+ EXPECT_EQ(parseLogConfig(".:=ERROR:"), db.getConfig());
+
+ // Manually attach a handler to a category.
+ // It should be reported as "anonymousHandler1"
+ auto handlerFoo = std::make_shared<TestLogHandler>(
+ LogHandlerConfig{"foo", {{"abc", "xyz"}}});
+ db.setLevel("x.y.z", LogLevel::DBG2);
+ db.getCategory("x.y.z")->addHandler(handlerFoo);
+ EXPECT_EQ(
+ parseLogConfig(".:=ERR:, x.y.z=DBG2:anonymousHandler1; "
+ "anonymousHandler1=foo,abc=xyz"),
+ db.getConfig());
+
+ // If we attach the same handler to another category it should still only be
+ // reported once.
+ db.setLevel("test.category", LogLevel::DBG1);
+ db.getCategory("test.category")->addHandler(handlerFoo);
+ EXPECT_EQ(
+ parseLogConfig(".:=ERR:, "
+ "x.y.z=DBG2:anonymousHandler1, "
+ "test.category=DBG1:anonymousHandler1; "
+ "anonymousHandler1=foo,abc=xyz"),
+ db.getConfig());
+
+ // If we use updateConfig() to explicitly define a handler named
+ // "anonymousHandler1", the unnamed handler will be reported as
+ // "anonymousHandler2" instead now.
+ db.updateConfig(parseLogConfig(
+ "a.b.c=INFO:anonymousHandler1; anonymousHandler1=handlerA,key=value"));
+ EXPECT_EQ(
+ parseLogConfig(".:=ERR:, "
+ "a.b.c=INFO:anonymousHandler1, "
+ "x.y.z=DBG2:anonymousHandler2, "
+ "test.category=DBG1:anonymousHandler2; "
+ "anonymousHandler1=handlerA,key=value; "
+ "anonymousHandler2=foo,abc=xyz"),
+ db.getConfig());
+}