Commit f805cad2 authored by Adam Simpkins's avatar Adam Simpkins Committed by Facebook Github Bot

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
parent 989c5112
...@@ -180,6 +180,7 @@ nobase_follyinclude_HEADERS = \ ...@@ -180,6 +180,7 @@ nobase_follyinclude_HEADERS = \
experimental/logging/printf.h \ experimental/logging/printf.h \
experimental/logging/RateLimiter.h \ experimental/logging/RateLimiter.h \
experimental/logging/StandardLogHandler.h \ experimental/logging/StandardLogHandler.h \
experimental/logging/StandardLogHandlerFactory.h \
experimental/logging/xlog.h \ experimental/logging/xlog.h \
experimental/NestedCommandLineApp.h \ experimental/NestedCommandLineApp.h \
experimental/observer/detail/Core.h \ experimental/observer/detail/Core.h \
......
...@@ -15,14 +15,11 @@ ...@@ -15,14 +15,11 @@
*/ */
#include <folly/experimental/logging/FileHandlerFactory.h> #include <folly/experimental/logging/FileHandlerFactory.h>
#include <set>
#include <folly/Conv.h> #include <folly/Conv.h>
#include <folly/MapUtil.h>
#include <folly/experimental/logging/AsyncFileWriter.h> #include <folly/experimental/logging/AsyncFileWriter.h>
#include <folly/experimental/logging/GlogStyleFormatter.h>
#include <folly/experimental/logging/ImmediateFileWriter.h> #include <folly/experimental/logging/ImmediateFileWriter.h>
#include <folly/experimental/logging/StandardLogHandler.h> #include <folly/experimental/logging/StandardLogHandler.h>
#include <folly/experimental/logging/StandardLogHandlerFactory.h>
using std::make_shared; using std::make_shared;
using std::shared_ptr; using std::shared_ptr;
...@@ -30,97 +27,85 @@ using std::string; ...@@ -30,97 +27,85 @@ using std::string;
namespace folly { namespace folly {
std::shared_ptr<LogHandler> FileHandlerFactory::createHandler( class FileHandlerFactory::WriterFactory
const Options& options) { : public StandardLogHandlerFactory::WriterFactory {
// Raise an error if we receive unexpected options public:
auto knownOptions = bool processOption(StringPiece name, StringPiece value) override {
std::set<std::string>{"path", "stream", "async", "max_buffer_size"}; if (name == "path") {
for (const auto& entry : options) { if (!stream_.empty()) {
if (knownOptions.find(entry.first) == knownOptions.end()) { throw std::invalid_argument(
throw std::invalid_argument(to<string>( "cannot specify both \"path\" and \"stream\" "
"unknown parameter \"", entry.first, "\" for FileHandlerFactory")); "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 std::shared_ptr<LogWriter> createWriter() override {
// TODO: We should eventually support parameters to control the formatter // Get the output file to use
// behavior. File outputFile;
auto formatter = make_shared<GlogStyleFormatter>(); if (!path_.empty()) {
outputFile = File{path_, O_WRONLY | O_APPEND | O_CREAT};
// Get the output file to use } else if (stream_ == "stderr") {
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") {
outputFile = File{STDERR_FILENO, /* ownsFd */ false}; outputFile = File{STDERR_FILENO, /* ownsFd */ false};
} else if (*stream == "stdout") { } else if (stream_ == "stdout") {
outputFile = File{STDOUT_FILENO, /* ownsFd */ false}; outputFile = File{STDOUT_FILENO, /* ownsFd */ false};
} else { } else {
throw std::invalid_argument(to<string>( throw std::invalid_argument(to<string>(
"unknown stream for FileHandlerFactory: \"", "unknown stream for FileHandlerFactory: \"",
*stream, stream_,
"\" expected one of stdout or stderr")); "\" 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 // Determine whether we should use ImmediateFileWriter or AsyncFileWriter
shared_ptr<LogWriter> writer; if (async_) {
bool async = true; auto asyncWriter = make_shared<AsyncFileWriter>(std::move(outputFile));
auto* asyncOption = get_ptr(options, "async"); if (maxBufferSize_.hasValue()) {
if (asyncOption) { asyncWriter->setMaxBufferSize(maxBufferSize_.value());
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));
} }
if (maxBufferSize == 0) { return asyncWriter;
} else {
if (maxBufferSize_.hasValue()) {
throw std::invalid_argument(to<string>( throw std::invalid_argument(to<string>(
"expected a positive value for FileHandlerFactory " "the \"max_buffer_size\" option is only valid for async file "
"\"max_buffer_size\": ", "handlers"));
*maxBufferOption));
} }
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>( std::string path_;
LogHandlerConfig{getType(), options}, formatter, writer); 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 } // namespace folly
...@@ -33,6 +33,9 @@ class FileHandlerFactory : public LogHandlerFactory { ...@@ -33,6 +33,9 @@ class FileHandlerFactory : public LogHandlerFactory {
} }
std::shared_ptr<LogHandler> createHandler(const Options& options) override; std::shared_ptr<LogHandler> createHandler(const Options& options) override;
private:
class WriterFactory;
}; };
} // namespace folly } // namespace folly
...@@ -212,13 +212,27 @@ void LoggerDB::startConfigUpdate( ...@@ -212,13 +212,27 @@ void LoggerDB::startConfigUpdate(
// Create the new log handler // Create the new log handler
const auto& factory = factoryIter->second; const auto& factory = factoryIter->second;
std::shared_ptr<LogHandler> handler; std::shared_ptr<LogHandler> handler;
if (oldHandler) { try {
handler = factory->updateHandler(oldHandler, entry.second.options); if (oldHandler) {
if (handler != oldHandler) { handler = factory->updateHandler(oldHandler, entry.second.options);
oldToNewHandlerMap->emplace(oldHandler, handler); if (handler != oldHandler) {
oldToNewHandlerMap->emplace(oldHandler, handler);
}
} else {
handler = factory->createHandler(entry.second.options);
} }
} else { } catch (const std::exception& ex) {
handler = factory->createHandler(entry.second.options); // 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; handlerInfo->handlers[entry.first] = handler;
(*handlers)[entry.first] = handler; (*handlers)[entry.first] = handler;
......
...@@ -23,6 +23,7 @@ libfollylogging_la_SOURCES = \ ...@@ -23,6 +23,7 @@ libfollylogging_la_SOURCES = \
printf.cpp \ printf.cpp \
RateLimiter.cpp \ RateLimiter.cpp \
StandardLogHandler.cpp \ StandardLogHandler.cpp \
StandardLogHandlerFactory.cpp \
xlog.cpp xlog.cpp
libfollylogging_la_LIBADD = $(top_builddir)/libfolly.la libfollylogging_la_LIBADD = $(top_builddir)/libfolly.la
......
/*
* 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
/*
* 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment