From 153c4233eff35be3bd9728c969bd22f59ac6051b Mon Sep 17 00:00:00 2001 From: Adam Simpkins Date: Wed, 29 Nov 2017 17:35:21 -0800 Subject: [PATCH] logging: add a LogConfig::update() method Summary: Add a method for merging the settings from two LogConfig objects. This allows LogConfig objects to be merged before applying them to the LoggerDB. The effects are the same as two sequential LoggerDB::updateConfig() calls, but without having to apply the intermediate state to the LoggerDB. Reviewed By: bolinfest Differential Revision: D6342085 fbshipit-source-id: 0f8a1b7d8d195a80bc74342444dd3152d331fcb6 --- folly/experimental/logging/LogConfig.cpp | 28 ++++++++ folly/experimental/logging/LogConfig.h | 29 ++++++++ .../logging/test/ConfigParserTest.cpp | 72 +++++++++++++++++++ 3 files changed, 129 insertions(+) diff --git a/folly/experimental/logging/LogConfig.cpp b/folly/experimental/logging/LogConfig.cpp index 1164f12c..a622f281 100644 --- a/folly/experimental/logging/LogConfig.cpp +++ b/folly/experimental/logging/LogConfig.cpp @@ -26,4 +26,32 @@ bool LogConfig::operator!=(const LogConfig& other) const { return !(*this == other); } +void LogConfig::update(const LogConfig& other) { + // Update handlerConfigs_ with all of the entries from the other LogConfig. + // Any entries already present in our handlerConfigs_ are replaced wholesale. + for (const auto& entry : other.handlerConfigs_) { + auto result = handlerConfigs_.insert(entry); + if (!result.second) { + result.first->second = entry.second; + } + } + + // Update categoryConfigs_ with all of the entries from the other LogConfig. + // + // Any entries already present in our categoryConfigs_ are merged: if the new + // configuration does not include handler settings our entry's settings are + // maintained. + for (const auto& entry : other.categoryConfigs_) { + auto result = categoryConfigs_.insert(entry); + if (!result.second) { + auto* existingEntry = &result.first->second; + auto oldHandlers = std::move(existingEntry->handlers); + *existingEntry = entry.second; + if (!existingEntry->handlers.hasValue()) { + existingEntry->handlers = std::move(oldHandlers); + } + } + } +} + } // namespace folly diff --git a/folly/experimental/logging/LogConfig.h b/folly/experimental/logging/LogConfig.h index 6b0b9f83..7407cf53 100644 --- a/folly/experimental/logging/LogConfig.h +++ b/folly/experimental/logging/LogConfig.h @@ -52,6 +52,35 @@ class LogConfig { bool operator==(const LogConfig& other) const; bool operator!=(const LogConfig& other) const; + /** + * Update this LogConfig object by merging in settings from another + * LogConfig. + * + * All LogHandler settings from the other LogConfig will be inserted into + * this LogConfig. If a log handler with the same name was already defined + * in this LogConfig it will be replaced with the new settings. + * + * All LogCategory settings from the other LogConfig will be inserted into + * this LogConfig. If a log category with the same name was already defined + * in this LogConfig, its settings will be updated with settings from the + * other LogConfig. However, if the other LogConfig does not define handler + * settings for the category it will retain its current handler settings. + * + * This method allows LogConfig objects to be combined before applying them. + * Using LogConfig::update() will produce the same results as if + * LoggerDB::updateConfig() had been called with both configs sequentially. + * In other words, this operation: + * + * configA.update(configB); + * loggerDB.updateConfig(configA); + * + * will produce the same results as: + * + * loggerDB.updateConfig(configA); + * loggerDB.updateConfig(configA); + */ + void update(const LogConfig& other); + private: HandlerConfigMap handlerConfigs_; CategoryConfigMap categoryConfigs_; diff --git a/folly/experimental/logging/test/ConfigParserTest.cpp b/folly/experimental/logging/test/ConfigParserTest.cpp index ac95884b..6eef6bcd 100644 --- a/folly/experimental/logging/test/ConfigParserTest.cpp +++ b/folly/experimental/logging/test/ConfigParserTest.cpp @@ -572,3 +572,75 @@ TEST(LogConfig, toJson) { })JSON"); EXPECT_EQ(expectedJson, logConfigToDynamic(config)); } + +TEST(LogConfig, mergeConfigs) { + auto config = parseLogConfig("bar=ERR:"); + config.update(parseLogConfig("foo:=INFO")); + EXPECT_THAT( + config.getCategoryConfigs(), + UnorderedElementsAre( + Pair("foo", LogCategoryConfig{LogLevel::INFO, false}), + Pair("bar", LogCategoryConfig{LogLevel::ERR, true, {}}))); + EXPECT_THAT(config.getHandlerConfigs(), UnorderedElementsAre()); + + config = + parseLogConfig("WARN:default; default=custom,opt1=value1,opt2=value2"); + config.update(parseLogConfig("folly.io=DBG2,foo=INFO")); + EXPECT_THAT( + config.getCategoryConfigs(), + UnorderedElementsAre( + Pair("", LogCategoryConfig{LogLevel::WARN, true, {"default"}}), + Pair("foo", LogCategoryConfig{LogLevel::INFO, true}), + Pair("folly.io", LogCategoryConfig{LogLevel::DBG2, true}))); + EXPECT_THAT( + config.getHandlerConfigs(), + UnorderedElementsAre(Pair( + "default", + LogHandlerConfig( + "custom", {{"opt1", "value1"}, {"opt2", "value2"}})))); + + // Updating the root category's log level without specifying + // handlers should leave its current handler list intact + config = + parseLogConfig("WARN:default; default=custom,opt1=value1,opt2=value2"); + config.update(parseLogConfig("ERR")); + EXPECT_THAT( + config.getCategoryConfigs(), + UnorderedElementsAre( + Pair("", LogCategoryConfig{LogLevel::ERR, true, {"default"}}))); + EXPECT_THAT( + config.getHandlerConfigs(), + UnorderedElementsAre(Pair( + "default", + LogHandlerConfig( + "custom", {{"opt1", "value1"}, {"opt2", "value2"}})))); + + config = + parseLogConfig("WARN:default; default=custom,opt1=value1,opt2=value2"); + config.update(parseLogConfig(".:=ERR")); + EXPECT_THAT( + config.getCategoryConfigs(), + UnorderedElementsAre( + Pair("", LogCategoryConfig{LogLevel::ERR, false, {"default"}}))); + EXPECT_THAT( + config.getHandlerConfigs(), + UnorderedElementsAre(Pair( + "default", + LogHandlerConfig( + "custom", {{"opt1", "value1"}, {"opt2", "value2"}})))); + + // Test clearing the root category's log handlers + config = + parseLogConfig("WARN:default; default=custom,opt1=value1,opt2=value2"); + config.update(parseLogConfig("FATAL:")); + EXPECT_THAT( + config.getCategoryConfigs(), + UnorderedElementsAre( + Pair("", LogCategoryConfig{LogLevel::FATAL, true, {}}))); + EXPECT_THAT( + config.getHandlerConfigs(), + UnorderedElementsAre(Pair( + "default", + LogHandlerConfig( + "custom", {{"opt1", "value1"}, {"opt2", "value2"}})))); +} -- 2.34.1