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 1164f12..a622f28 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 6b0b9f8..7407cf5 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 ac95884..6eef6bc 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"}}))));
+}