logging: add a StandardLogHandlerFactory helper class
authorAdam Simpkins <simpkins@fb.com>
Thu, 7 Dec 2017 01:29:27 +0000 (17:29 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Thu, 7 Dec 2017 01:41:43 +0000 (17:41 -0800)
Summary:
This moves some of the FileHandlerFactory code out into a new
StandardLogHandlerFactory helper class.  This will make it easier in the future
to add new LogHandlerFactory implementations that create StandardLogHandler
objects.

In particular, I plan to use this soon to split FileHandlerFactory into two
separate classes: one for writing to files on disk and a separate class for
writing to stdout or stderr.

Reviewed By: yfeldblum

Differential Revision: D6494227

fbshipit-source-id: 52e24250d020d21a5395d2a68fa5bd40bb32fbd4

folly/Makefile.am
folly/experimental/logging/FileHandlerFactory.cpp
folly/experimental/logging/FileHandlerFactory.h
folly/experimental/logging/LoggerDB.cpp
folly/experimental/logging/Makefile.am
folly/experimental/logging/StandardLogHandlerFactory.cpp [new file with mode: 0644]
folly/experimental/logging/StandardLogHandlerFactory.h [new file with mode: 0644]

index 9854755..65e8560 100644 (file)
@@ -180,6 +180,7 @@ nobase_follyinclude_HEADERS = \
        experimental/logging/printf.h \
        experimental/logging/RateLimiter.h \
        experimental/logging/StandardLogHandler.h \
+       experimental/logging/StandardLogHandlerFactory.h \
        experimental/logging/xlog.h \
        experimental/NestedCommandLineApp.h \
        experimental/observer/detail/Core.h \
index 93dc13a..c398654 100644 (file)
  */
 #include <folly/experimental/logging/FileHandlerFactory.h>
 
-#include <set>
-
 #include <folly/Conv.h>
-#include <folly/MapUtil.h>
 #include <folly/experimental/logging/AsyncFileWriter.h>
-#include <folly/experimental/logging/GlogStyleFormatter.h>
 #include <folly/experimental/logging/ImmediateFileWriter.h>
 #include <folly/experimental/logging/StandardLogHandler.h>
+#include <folly/experimental/logging/StandardLogHandlerFactory.h>
 
 using std::make_shared;
 using std::shared_ptr;
@@ -30,97 +27,85 @@ using std::string;
 
 namespace folly {
 
-std::shared_ptr<LogHandler> FileHandlerFactory::createHandler(
-    const Options& options) {
-  // Raise an error if we receive unexpected options
-  auto knownOptions =
-      std::set<std::string>{"path", "stream", "async", "max_buffer_size"};
-  for (const auto& entry : options) {
-    if (knownOptions.find(entry.first) == knownOptions.end()) {
-      throw std::invalid_argument(to<string>(
-          "unknown parameter \"", entry.first, "\" for FileHandlerFactory"));
+class FileHandlerFactory::WriterFactory
+    : public StandardLogHandlerFactory::WriterFactory {
+ public:
+  bool processOption(StringPiece name, StringPiece value) override {
+    if (name == "path") {
+      if (!stream_.empty()) {
+        throw std::invalid_argument(
+            "cannot specify both \"path\" and \"stream\" "
+            "parameters for FileHandlerFactory");
+      }
+      path_ = value.str();
+      return true;
+    } else if (name == "stream") {
+      if (!path_.empty()) {
+        throw std::invalid_argument(
+            "cannot specify both \"path\" and \"stream\" "
+            "parameters for FileHandlerFactory");
+      }
+      stream_ = value.str();
+      return true;
+    } else if (name == "async") {
+      async_ = to<bool>(value);
+      return true;
+    } else if (name == "max_buffer_size") {
+      auto size = to<size_t>(value);
+      if (size == 0) {
+        throw std::invalid_argument(to<string>("must be a postive number"));
+      }
+      maxBufferSize_ = size;
+      return true;
+    } else {
+      return false;
     }
   }
 
-  // Construct the formatter
-  // TODO: We should eventually support parameters to control the formatter
-  // behavior.
-  auto formatter = make_shared<GlogStyleFormatter>();
-
-  // Get the output file to use
-  File outputFile;
-  auto* path = get_ptr(options, "path");
-  auto* stream = get_ptr(options, "stream");
-  if (path && stream) {
-    throw std::invalid_argument(
-        "cannot specify both \"path\" and \"stream\" "
-        "parameters for FileHandlerFactory");
-  } else if (path) {
-    outputFile = File{*path, O_WRONLY | O_APPEND | O_CREAT};
-  } else if (stream) {
-    if (*stream == "stderr") {
+  std::shared_ptr<LogWriter> createWriter() override {
+    // Get the output file to use
+    File outputFile;
+    if (!path_.empty()) {
+      outputFile = File{path_, O_WRONLY | O_APPEND | O_CREAT};
+    } else if (stream_ == "stderr") {
       outputFile = File{STDERR_FILENO, /* ownsFd */ false};
-    } else if (*stream == "stdout") {
+    } else if (stream_ == "stdout") {
       outputFile = File{STDOUT_FILENO, /* ownsFd */ false};
     } else {
       throw std::invalid_argument(to<string>(
           "unknown stream for FileHandlerFactory: \"",
-          *stream,
+          stream_,
           "\" expected one of stdout or stderr"));
     }
-  } else {
-    throw std::invalid_argument(
-        "must specify a \"path\" or \"stream\" "
-        "parameter for FileHandlerFactory");
-  }
 
-  // Determine whether we should use ImmediateFileWriter or AsyncFileWriter
-  shared_ptr<LogWriter> writer;
-  bool async = true;
-  auto* asyncOption = get_ptr(options, "async");
-  if (asyncOption) {
-    try {
-      async = to<bool>(*asyncOption);
-    } catch (const std::exception& ex) {
-      throw std::invalid_argument(to<string>(
-          "expected a boolean value for FileHandlerFactory \"async\" "
-          "parameter: ",
-          *asyncOption));
-    }
-  }
-  auto* maxBufferOption = get_ptr(options, "max_buffer_size");
-  if (async) {
-    auto asyncWriter = make_shared<AsyncFileWriter>(std::move(outputFile));
-    if (maxBufferOption) {
-      size_t maxBufferSize;
-      try {
-        maxBufferSize = to<size_t>(*maxBufferOption);
-      } catch (const std::exception& ex) {
-        throw std::invalid_argument(to<string>(
-            "expected an integer value for FileHandlerFactory "
-            "\"max_buffer_size\": ",
-            *maxBufferOption));
+    // Determine whether we should use ImmediateFileWriter or AsyncFileWriter
+    if (async_) {
+      auto asyncWriter = make_shared<AsyncFileWriter>(std::move(outputFile));
+      if (maxBufferSize_.hasValue()) {
+        asyncWriter->setMaxBufferSize(maxBufferSize_.value());
       }
-      if (maxBufferSize == 0) {
+      return asyncWriter;
+    } else {
+      if (maxBufferSize_.hasValue()) {
         throw std::invalid_argument(to<string>(
-            "expected a positive value for FileHandlerFactory "
-            "\"max_buffer_size\": ",
-            *maxBufferOption));
+            "the \"max_buffer_size\" option is only valid for async file "
+            "handlers"));
       }
-      asyncWriter->setMaxBufferSize(maxBufferSize);
+      return make_shared<ImmediateFileWriter>(std::move(outputFile));
     }
-    writer = std::move(asyncWriter);
-  } else {
-    if (maxBufferOption) {
-      throw std::invalid_argument(to<string>(
-          "the \"max_buffer_size\" option is only valid for async file "
-          "handlers"));
-    }
-    writer = make_shared<ImmediateFileWriter>(std::move(outputFile));
   }
 
-  return make_shared<StandardLogHandler>(
-      LogHandlerConfig{getType(), options}, formatter, writer);
+  std::string path_;
+  std::string stream_;
+  bool async_{true};
+  Optional<size_t> maxBufferSize_;
+};
+
+std::shared_ptr<LogHandler> FileHandlerFactory::createHandler(
+    const Options& options) {
+  WriterFactory writerFactory;
+  return StandardLogHandlerFactory::createHandler(
+      getType(), &writerFactory, options);
 }
 
 } // namespace folly
index 20142be..2e8cbba 100644 (file)
@@ -33,6 +33,9 @@ class FileHandlerFactory : public LogHandlerFactory {
   }
 
   std::shared_ptr<LogHandler> createHandler(const Options& options) override;
+
+ private:
+  class WriterFactory;
 };
 
 } // namespace folly
index 299f2d7..1f58c04 100644 (file)
@@ -212,13 +212,27 @@ void LoggerDB::startConfigUpdate(
     // Create the new log handler
     const auto& factory = factoryIter->second;
     std::shared_ptr<LogHandler> handler;
-    if (oldHandler) {
-      handler = factory->updateHandler(oldHandler, entry.second.options);
-      if (handler != oldHandler) {
-        oldToNewHandlerMap->emplace(oldHandler, handler);
+    try {
+      if (oldHandler) {
+        handler = factory->updateHandler(oldHandler, entry.second.options);
+        if (handler != oldHandler) {
+          oldToNewHandlerMap->emplace(oldHandler, handler);
+        }
+      } else {
+        handler = factory->createHandler(entry.second.options);
       }
-    } else {
-      handler = factory->createHandler(entry.second.options);
+    } catch (const std::exception& ex) {
+      // Errors creating or updating the the log handler are generally due to
+      // bad configuration options.  It is useful to update the exception
+      // message to include the name of the log handler we were trying to
+      // update or create.
+      throw std::invalid_argument(to<string>(
+          "error ",
+          oldHandler ? "updating" : "creating",
+          " log handler \"",
+          entry.first,
+          "\": ",
+          exceptionStr(ex)));
     }
     handlerInfo->handlers[entry.first] = handler;
     (*handlers)[entry.first] = handler;
index 61e6408..4edca2a 100644 (file)
@@ -23,6 +23,7 @@ libfollylogging_la_SOURCES = \
        printf.cpp \
        RateLimiter.cpp \
        StandardLogHandler.cpp \
+       StandardLogHandlerFactory.cpp \
        xlog.cpp
 
 libfollylogging_la_LIBADD = $(top_builddir)/libfolly.la
diff --git a/folly/experimental/logging/StandardLogHandlerFactory.cpp b/folly/experimental/logging/StandardLogHandlerFactory.cpp
new file mode 100644 (file)
index 0000000..fd807a9
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 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/experimental/logging/StandardLogHandlerFactory.h>
+
+#include <folly/MapUtil.h>
+#include <folly/String.h>
+#include <folly/experimental/logging/GlogStyleFormatter.h>
+#include <folly/experimental/logging/StandardLogHandler.h>
+
+using std::string;
+
+namespace folly {
+
+class GlogFormatterFactory
+    : public StandardLogHandlerFactory::FormatterFactory {
+ public:
+  bool processOption(StringPiece /* name */, StringPiece /* value */) override {
+    return false;
+  }
+  std::shared_ptr<LogFormatter> createFormatter() override {
+    return std::make_shared<GlogStyleFormatter>();
+  }
+};
+
+std::shared_ptr<StandardLogHandler> StandardLogHandlerFactory::createHandler(
+    StringPiece type,
+    WriterFactory* writerFactory,
+    const Options& options) {
+  std::unique_ptr<FormatterFactory> formatterFactory;
+
+  // Get the log formatter type
+  auto* formatterType = get_ptr(options, "formatter");
+  if (!formatterType || *formatterType == "glog") {
+    formatterFactory = std::make_unique<GlogFormatterFactory>();
+  } else {
+    throw std::invalid_argument(
+        to<string>("unknown log formatter type \"", *formatterType, "\""));
+  }
+
+  // Process the log formatter and log handler options
+  std::vector<string> errors;
+  for (const auto& entry : options) {
+    bool handled = false;
+    try {
+      // Allow both the formatterFactory and writerFactory to consume an
+      // option.  In general they probably should have mutually exclusive
+      // option names, but we don't give precedence to one over the other here.
+      handled |= formatterFactory->processOption(entry.first, entry.second);
+      handled |= writerFactory->processOption(entry.first, entry.second);
+    } catch (const std::exception& ex) {
+      errors.push_back(to<string>(
+          "error processing option \"", entry.first, "\": ", ex.what()));
+      continue;
+    }
+
+    // We explicitly processed the "formatter" option above.
+    handled |= handled || (entry.first == "formatter");
+
+    // Complain about unknown options.
+    if (!handled) {
+      errors.push_back(to<string>("unknown option \"", entry.first, "\""));
+    }
+  }
+
+  if (!errors.empty()) {
+    throw std::invalid_argument(join(", ", errors));
+  }
+
+  // Construct the formatter and writer
+  auto formatter = formatterFactory->createFormatter();
+  auto writer = writerFactory->createWriter();
+
+  return std::make_shared<StandardLogHandler>(
+      LogHandlerConfig{type, options}, formatter, writer);
+}
+
+} // namespace folly
diff --git a/folly/experimental/logging/StandardLogHandlerFactory.h b/folly/experimental/logging/StandardLogHandlerFactory.h
new file mode 100644 (file)
index 0000000..dcea7fb
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <string>
+#include <unordered_map>
+
+#include <folly/Range.h>
+
+namespace folly {
+
+class LogWriter;
+class LogFormatter;
+class StandardLogHandler;
+
+/**
+ * StandardLogHandlerFactory contains helper methods for LogHandlerFactory
+ * implementations that create StandardLogHandler objects.
+ *
+ * StandardLogHandlerFactory does not derive from LogHandlerFactory itself.
+ */
+class StandardLogHandlerFactory {
+ public:
+  using Options = std::unordered_map<std::string, std::string>;
+
+  class OptionProcessor {
+   public:
+    virtual ~OptionProcessor() {}
+
+    /**
+     * Process an option.
+     *
+     * This should return true if the option was processed successfully,
+     * or false if this is an unknown option name.  It should throw an
+     * exception if the option name is known but there is a problem with the
+     * value.
+     */
+    virtual bool processOption(StringPiece name, StringPiece value) = 0;
+  };
+
+  class FormatterFactory : public OptionProcessor {
+   public:
+    virtual std::shared_ptr<LogFormatter> createFormatter() = 0;
+  };
+
+  class WriterFactory : public OptionProcessor {
+   public:
+    virtual std::shared_ptr<LogWriter> createWriter() = 0;
+  };
+
+  static std::shared_ptr<StandardLogHandler> createHandler(
+      StringPiece type,
+      WriterFactory* writerFactory,
+      const Options& options);
+};
+
+} // namespace folly