From: Adam Simpkins Date: Thu, 7 Dec 2017 01:29:27 +0000 (-0800) Subject: logging: add a StandardLogHandlerFactory helper class X-Git-Tag: v2017.12.11.00~19 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=f805cad2ece30186014139b1a1dc71aa09a352c4 logging: add a StandardLogHandlerFactory helper class 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 --- diff --git a/folly/Makefile.am b/folly/Makefile.am index 98547554..65e85608 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -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 \ diff --git a/folly/experimental/logging/FileHandlerFactory.cpp b/folly/experimental/logging/FileHandlerFactory.cpp index 93dc13a4..c398654f 100644 --- a/folly/experimental/logging/FileHandlerFactory.cpp +++ b/folly/experimental/logging/FileHandlerFactory.cpp @@ -15,14 +15,11 @@ */ #include -#include - #include -#include #include -#include #include #include +#include using std::make_shared; using std::shared_ptr; @@ -30,97 +27,85 @@ using std::string; namespace folly { -std::shared_ptr FileHandlerFactory::createHandler( - const Options& options) { - // Raise an error if we receive unexpected options - auto knownOptions = - std::set{"path", "stream", "async", "max_buffer_size"}; - for (const auto& entry : options) { - if (knownOptions.find(entry.first) == knownOptions.end()) { - throw std::invalid_argument(to( - "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(value); + return true; + } else if (name == "max_buffer_size") { + auto size = to(value); + if (size == 0) { + throw std::invalid_argument(to("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(); - - // 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 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( "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 writer; - bool async = true; - auto* asyncOption = get_ptr(options, "async"); - if (asyncOption) { - try { - async = to(*asyncOption); - } catch (const std::exception& ex) { - throw std::invalid_argument(to( - "expected a boolean value for FileHandlerFactory \"async\" " - "parameter: ", - *asyncOption)); - } - } - auto* maxBufferOption = get_ptr(options, "max_buffer_size"); - if (async) { - auto asyncWriter = make_shared(std::move(outputFile)); - if (maxBufferOption) { - size_t maxBufferSize; - try { - maxBufferSize = to(*maxBufferOption); - } catch (const std::exception& ex) { - throw std::invalid_argument(to( - "expected an integer value for FileHandlerFactory " - "\"max_buffer_size\": ", - *maxBufferOption)); + // Determine whether we should use ImmediateFileWriter or AsyncFileWriter + if (async_) { + auto asyncWriter = make_shared(std::move(outputFile)); + if (maxBufferSize_.hasValue()) { + asyncWriter->setMaxBufferSize(maxBufferSize_.value()); } - if (maxBufferSize == 0) { + return asyncWriter; + } else { + if (maxBufferSize_.hasValue()) { throw std::invalid_argument(to( - "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(std::move(outputFile)); } - writer = std::move(asyncWriter); - } else { - if (maxBufferOption) { - throw std::invalid_argument(to( - "the \"max_buffer_size\" option is only valid for async file " - "handlers")); - } - writer = make_shared(std::move(outputFile)); } - return make_shared( - LogHandlerConfig{getType(), options}, formatter, writer); + std::string path_; + std::string stream_; + bool async_{true}; + Optional maxBufferSize_; +}; + +std::shared_ptr FileHandlerFactory::createHandler( + const Options& options) { + WriterFactory writerFactory; + return StandardLogHandlerFactory::createHandler( + getType(), &writerFactory, options); } } // namespace folly diff --git a/folly/experimental/logging/FileHandlerFactory.h b/folly/experimental/logging/FileHandlerFactory.h index 20142be5..2e8cbba8 100644 --- a/folly/experimental/logging/FileHandlerFactory.h +++ b/folly/experimental/logging/FileHandlerFactory.h @@ -33,6 +33,9 @@ class FileHandlerFactory : public LogHandlerFactory { } std::shared_ptr createHandler(const Options& options) override; + + private: + class WriterFactory; }; } // namespace folly diff --git a/folly/experimental/logging/LoggerDB.cpp b/folly/experimental/logging/LoggerDB.cpp index 299f2d71..1f58c047 100644 --- a/folly/experimental/logging/LoggerDB.cpp +++ b/folly/experimental/logging/LoggerDB.cpp @@ -212,13 +212,27 @@ void LoggerDB::startConfigUpdate( // Create the new log handler const auto& factory = factoryIter->second; std::shared_ptr 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( + "error ", + oldHandler ? "updating" : "creating", + " log handler \"", + entry.first, + "\": ", + exceptionStr(ex))); } handlerInfo->handlers[entry.first] = handler; (*handlers)[entry.first] = handler; diff --git a/folly/experimental/logging/Makefile.am b/folly/experimental/logging/Makefile.am index 61e64088..4edca2ac 100644 --- a/folly/experimental/logging/Makefile.am +++ b/folly/experimental/logging/Makefile.am @@ -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 index 00000000..fd807a91 --- /dev/null +++ b/folly/experimental/logging/StandardLogHandlerFactory.cpp @@ -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 + +#include +#include +#include +#include + +using std::string; + +namespace folly { + +class GlogFormatterFactory + : public StandardLogHandlerFactory::FormatterFactory { + public: + bool processOption(StringPiece /* name */, StringPiece /* value */) override { + return false; + } + std::shared_ptr createFormatter() override { + return std::make_shared(); + } +}; + +std::shared_ptr StandardLogHandlerFactory::createHandler( + StringPiece type, + WriterFactory* writerFactory, + const Options& options) { + std::unique_ptr formatterFactory; + + // Get the log formatter type + auto* formatterType = get_ptr(options, "formatter"); + if (!formatterType || *formatterType == "glog") { + formatterFactory = std::make_unique(); + } else { + throw std::invalid_argument( + to("unknown log formatter type \"", *formatterType, "\"")); + } + + // Process the log formatter and log handler options + std::vector 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( + "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("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( + 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 index 00000000..dcea7fb3 --- /dev/null +++ b/folly/experimental/logging/StandardLogHandlerFactory.h @@ -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 +#include + +#include + +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; + + 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 createFormatter() = 0; + }; + + class WriterFactory : public OptionProcessor { + public: + virtual std::shared_ptr createWriter() = 0; + }; + + static std::shared_ptr createHandler( + StringPiece type, + WriterFactory* writerFactory, + const Options& options); +}; + +} // namespace folly