Skip to content

Commit

Permalink
logging: add LoggerDB::updateConfig() and resetConfig()
Browse files Browse the repository at this point in the history
Summary: Add methods for applying config changes from a LogConfig object to the LoggerDB.

Reviewed By: bolinfest

Differential Revision: D6200564

fbshipit-source-id: a25eb99e84b2885bf6853e2222db0d7432a6c37b
  • Loading branch information
simpkins authored and facebook-github-bot committed Nov 30, 2017
1 parent b6e14b7 commit 01d4b7d
Show file tree
Hide file tree
Showing 8 changed files with 697 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ if (BUILD_TESTS)
${FOLLY_DIR}/test/SingletonTestStructs.cpp
${FOLLY_DIR}/test/SocketAddressTestHelper.cpp
${FOLLY_DIR}/test/SocketAddressTestHelper.h
${FOLLY_DIR}/experimental/logging/test/TestLogHandler.cpp
${FOLLY_DIR}/experimental/logging/test/TestLogHandler.h
${FOLLY_DIR}/futures/test/TestExecutor.cpp
${FOLLY_DIR}/futures/test/TestExecutor.h
Expand Down Expand Up @@ -386,6 +387,7 @@ if (BUILD_TESTS)
DIRECTORY experimental/logging/test/
TEST async_file_writer_test SOURCES AsyncFileWriterTest.cpp
TEST config_parser_test SOURCES ConfigParserTest.cpp
TEST config_update_test SOURCES ConfigUpdateTest.cpp
TEST file_handler_factory_test SOURCES FileHandlerFactoryTest.cpp
TEST glog_formatter_test SOURCES GlogFormatterTest.cpp
TEST immediate_file_writer_test SOURCES ImmediateFileWriterTest.cpp
Expand Down
18 changes: 18 additions & 0 deletions folly/experimental/logging/LogCategory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <folly/ConstexprMath.h>
#include <folly/ExceptionString.h>
#include <folly/FileUtil.h>
#include <folly/MapUtil.h>
#include <folly/experimental/logging/LogHandler.h>
#include <folly/experimental/logging/LogMessage.h>
#include <folly/experimental/logging/LogName.h>
Expand Down Expand Up @@ -144,6 +145,23 @@ std::vector<std::shared_ptr<LogHandler>> LogCategory::getHandlers() const {
return *(handlers_.rlock());
}

void LogCategory::replaceHandlers(
std::vector<std::shared_ptr<LogHandler>> handlers) {
return handlers_.wlock()->swap(handlers);
}

void LogCategory::updateHandlers(const std::unordered_map<
std::shared_ptr<LogHandler>,
std::shared_ptr<LogHandler>>& handlerMap) {
auto handlers = handlers_.wlock();
for (auto& entry : *handlers) {
auto* ptr = get_ptr(handlerMap, entry);
if (ptr) {
entry = *ptr;
}
}
}

void LogCategory::setLevel(LogLevel level, bool inherit) {
// We have to set the level through LoggerDB, since we require holding
// the LoggerDB lock to iterate through our children in case our effective
Expand Down
20 changes: 20 additions & 0 deletions folly/experimental/logging/LogCategory.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,26 @@ class LogCategory {
*/
std::vector<std::shared_ptr<LogHandler>> getHandlers() const;

/**
* Replace the list of LogHandlers with a completely new list.
*/
void replaceHandlers(std::vector<std::shared_ptr<LogHandler>> handlers);

/**
* Update the LogHandlers attached to this LogCategory by replacing
* currently attached handlers with new LogHandler objects.
*
* The handlerMap argument is a map of (old_handler -> new_handler)
* If any of the LogHandlers currently attached to this category are found in
* the handlerMap, replace them with the new handler indicated in the map.
*
* This is used when the LogHandler configuration is changed requiring one or
* more LogHandler objects to be replaced with new ones.
*/
void updateHandlers(const std::unordered_map<
std::shared_ptr<LogHandler>,
std::shared_ptr<LogHandler>>& handlerMap);

/* Internal methods for use by other parts of the logging library code */

/**
Expand Down
223 changes: 223 additions & 0 deletions folly/experimental/logging/LoggerDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,229 @@ LogConfig LoggerDB::getConfig() const {
return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
}

/**
* Process handler config information when starting a config update operation.
*/
void LoggerDB::startConfigUpdate(
const Synchronized<HandlerInfo>::LockedPtr& handlerInfo,
const LogConfig& config,
NewHandlerMap* handlers,
OldToNewHandlerMap* oldToNewHandlerMap) {
// Get a map of all currently existing LogHandlers.
// This resolves weak_ptrs to shared_ptrs, and ignores expired weak_ptrs.
// This prevents any of these LogHandler pointers from expiring during the
// config update.
for (const auto& entry : handlerInfo->handlers) {
auto handler = entry.second.lock();
if (handler) {
handlers->emplace(entry.first, std::move(handler));
}
}

// Create all of the new LogHandlers needed from this configuration
for (const auto& entry : config.getHandlerConfigs()) {
// Look up the LogHandlerFactory
auto factoryIter = handlerInfo->factories.find(entry.second.type);
if (factoryIter == handlerInfo->factories.end()) {
throw std::invalid_argument(to<std::string>(
"unknown log handler type \"", entry.second.type, "\""));
}

// Check to see if there is an existing LogHandler with this name
std::shared_ptr<LogHandler> oldHandler;
auto iter = handlers->find(entry.first);
if (iter != handlers->end()) {
oldHandler = iter->second;
}

// 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);
}
} else {
handler = factory->createHandler(entry.second.options);
}
handlerInfo->handlers[entry.first] = handler;
(*handlers)[entry.first] = handler;
}

// Before we start making any LogCategory changes, confirm that all handlers
// named in the category configs are known handlers.
for (const auto& entry : config.getCategoryConfigs()) {
if (!entry.second.handlers.hasValue()) {
continue;
}
for (const auto& handlerName : entry.second.handlers.value()) {
auto iter = handlers->find(handlerName);
if (iter == handlers->end()) {
throw std::invalid_argument(to<std::string>(
"unknown log handler \"",
handlerName,
"\" configured for log category \"",
entry.first,
"\""));
}
}
}
}

/**
* Update handlerInfo_ at the end of a config update operation.
*/
void LoggerDB::finishConfigUpdate(
const Synchronized<HandlerInfo>::LockedPtr& handlerInfo,
NewHandlerMap* handlers,
OldToNewHandlerMap* oldToNewHandlerMap) {
// Build a new map to replace handlerInfo->handlers
// This will contain only the LogHandlers that are still in use by the
// current LogCategory settings.
std::unordered_map<std::string, std::weak_ptr<LogHandler>> newHandlerMap;
for (const auto& entry : *handlers) {
newHandlerMap.emplace(entry.first, entry.second);
}
// Drop all of our shared_ptr references to LogHandler objects,
// and then remove entries in newHandlerMap that are unreferenced.
handlers->clear();
oldToNewHandlerMap->clear();
handlerInfo->handlers.clear();
for (auto iter = newHandlerMap.begin(); iter != newHandlerMap.end(); /**/) {
if (iter->second.expired()) {
iter = newHandlerMap.erase(iter);
} else {
++iter;
}
}
handlerInfo->handlers.swap(newHandlerMap);
}

std::vector<std::shared_ptr<LogHandler>> LoggerDB::buildCategoryHandlerList(
const NewHandlerMap& handlerMap,
StringPiece categoryName,
const std::vector<std::string>& categoryHandlerNames) {
std::vector<std::shared_ptr<LogHandler>> catHandlers;
for (const auto& handlerName : categoryHandlerNames) {
auto iter = handlerMap.find(handlerName);
if (iter == handlerMap.end()) {
// This really shouldn't be possible; the checks in startConfigUpdate()
// should have already bailed out if there was an unknown handler.
throw std::invalid_argument(to<std::string>(
"bug: unknown log handler \"",
handlerName,
"\" configured for log category \"",
categoryName,
"\""));
}
catHandlers.push_back(iter->second);
}

return catHandlers;
}

void LoggerDB::updateConfig(const LogConfig& config) {
// Grab the handlerInfo_ lock.
// We hold it in write mode for the entire config update operation. This
// ensures that only a single config update operation ever runs at once.
auto handlerInfo = handlerInfo_.wlock();

NewHandlerMap handlers;
OldToNewHandlerMap oldToNewHandlerMap;
startConfigUpdate(handlerInfo, config, &handlers, &oldToNewHandlerMap);

// If an existing LogHandler was replaced with a new one,
// walk all current LogCategories and replace this handler.
if (!oldToNewHandlerMap.empty()) {
auto loggerMap = loggersByName_.rlock();
for (const auto& entry : *loggerMap) {
entry.second->updateHandlers(oldToNewHandlerMap);
}
}

// Update log levels and handlers mentioned in the config update
auto loggersByName = loggersByName_.wlock();
for (const auto& entry : config.getCategoryConfigs()) {
LogCategory* category =
getOrCreateCategoryLocked(*loggersByName, entry.first);

// Update the log handlers
if (entry.second.handlers.hasValue()) {
auto catHandlers = buildCategoryHandlerList(
handlers, entry.first, entry.second.handlers.value());
category->replaceHandlers(std::move(catHandlers));
}

// Update the level settings
category->setLevelLocked(
entry.second.level, entry.second.inheritParentLevel);
}

finishConfigUpdate(handlerInfo, &handlers, &oldToNewHandlerMap);
}

void LoggerDB::resetConfig(const LogConfig& config) {
// Grab the handlerInfo_ lock.
// We hold it in write mode for the entire config update operation. This
// ensures that only a single config update operation ever runs at once.
auto handlerInfo = handlerInfo_.wlock();

NewHandlerMap handlers;
OldToNewHandlerMap oldToNewHandlerMap;
startConfigUpdate(handlerInfo, config, &handlers, &oldToNewHandlerMap);

// Make sure all log categories mentioned in the new config exist.
// This ensures that we will cover them in our walk below.
LogCategory* rootCategory;
{
auto loggersByName = loggersByName_.wlock();
rootCategory = getOrCreateCategoryLocked(*loggersByName, "");
for (const auto& entry : config.getCategoryConfigs()) {
getOrCreateCategoryLocked(*loggersByName, entry.first);
}
}

{
// Update all log categories
auto loggersByName = loggersByName_.rlock();
for (const auto& entry : *loggersByName) {
auto* category = entry.second.get();

auto configIter = config.getCategoryConfigs().find(category->getName());
if (configIter == config.getCategoryConfigs().end()) {
// This category is not listed in the config settings.
// Reset it to the default settings.
category->clearHandlers();

if (category == rootCategory) {
category->setLevelLocked(LogLevel::ERR, false);
} else {
category->setLevelLocked(LogLevel::MAX_LEVEL, true);
}
continue;
}

const auto& catConfig = configIter->second;

// Update the category log level
category->setLevelLocked(catConfig.level, catConfig.inheritParentLevel);

// Update the category handlers list.
// If the handler list is not set in the config, clear out any existing
// handlers rather than leaving it as-is.
std::vector<std::shared_ptr<LogHandler>> catHandlers;
if (catConfig.handlers.hasValue()) {
catHandlers = buildCategoryHandlerList(
handlers, entry.first, catConfig.handlers.value());
}
category->replaceHandlers(std::move(catHandlers));
}
}

finishConfigUpdate(handlerInfo, &handlers, &oldToNewHandlerMap);
}

LogCategory* LoggerDB::getOrCreateCategoryLocked(
LoggerNameMap& loggersByName,
StringPiece name) {
Expand Down
44 changes: 44 additions & 0 deletions folly/experimental/logging/LoggerDB.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,29 @@ class LoggerDB {
*/
LogConfig getConfig() const;

/**
* Update the current LoggerDB state with the specified LogConfig settings.
*
* Log categories and handlers listed in the LogConfig object will be updated
* to the new state listed in the LogConfig. Settings on categories and
* handlers not listed in the config will be left as-is.
*/
void updateConfig(const LogConfig& config);

/**
* Reset the current LoggerDB state to the specified LogConfig settings.
*
* All LogCategories not mentioned in the new LogConfig will have all
* currently configured log handlers removed and their log level set to its
* default state. For the root category the default log level is ERR; for
* all other categories the default level is MAX_LEVEL with log level
* inheritance enabled.
*
* LogCategories listed in the new config but without LogHandler information
* defined will have all existing handlers removed.
*/
void resetConfig(const LogConfig& config);

/**
* Apply a configuration string specifying a series a log levels.
*
Expand Down Expand Up @@ -237,6 +260,24 @@ class LoggerDB {
folly::StringPiece name,
LogCategory* parent);

using NewHandlerMap =
std::unordered_map<std::string, std::shared_ptr<LogHandler>>;
using OldToNewHandlerMap = std::
unordered_map<std::shared_ptr<LogHandler>, std::shared_ptr<LogHandler>>;
void startConfigUpdate(
const Synchronized<HandlerInfo>::LockedPtr& handlerInfo,
const LogConfig& config,
NewHandlerMap* handlers,
OldToNewHandlerMap* oldToNewHandlerMap);
void finishConfigUpdate(
const Synchronized<HandlerInfo>::LockedPtr& handlerInfo,
NewHandlerMap* handlers,
OldToNewHandlerMap* oldToNewHandlerMap);
std::vector<std::shared_ptr<LogHandler>> buildCategoryHandlerList(
const NewHandlerMap& handlerMap,
StringPiece categoryName,
const std::vector<std::string>& categoryHandlerNames);

static void internalWarningImpl(
folly::StringPiece filename,
int lineNumber,
Expand All @@ -256,6 +297,9 @@ class LoggerDB {

/**
* The LogHandlers and LogHandlerFactories.
*
* For lock ordering purposes, if you need to acquire both the loggersByName_
* and handlerInfo_ locks, the handlerInfo_ lock must be acquired first.
*/
folly::Synchronized<HandlerInfo> handlerInfo_;

Expand Down
Loading

0 comments on commit 01d4b7d

Please sign in to comment.