logging: add a LogConfig::update() method
authorAdam Simpkins <simpkins@fb.com>
Thu, 30 Nov 2017 01:35:21 +0000 (17:35 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Thu, 30 Nov 2017 01:51:07 +0000 (17:51 -0800)
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
folly/experimental/logging/LogConfig.h
folly/experimental/logging/test/ConfigParserTest.cpp

index 1164f12c18c4ed83672e1c61e6c0837daf47bb23..a622f2813cd5d1e93acb9dad7080a9bf5104f636 100644 (file)
@@ -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
index 6b0b9f83a15520000e9cf7d13a163b4e933d6a68..7407cf53530d0bc95a77e26b4cff35bab2da0be7 100644 (file)
@@ -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_;
index ac95884b275782e6611fdcaae034715c8276fd87..6eef6bcdba8c0f2b7e4f9a3efac617005b57cccc 100644 (file)
@@ -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"}}))));
+}