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

logging: add a LoggerDB::internalWarning() function

Summary:
A few places in the logging code need to report issues when something goes
wrong in the logging code itself.  This adds a new LoggerDB::internalWarning()
method for use in these places.

This provides a default implementation that logs these warnings with
_CrtDbgReport() on Windows, and writes to stderr on non-Windows platforms.
The default implementation can also be overridden for programs that want some
alternative behavior.

Reviewed By: wez

Differential Revision: D5157731

fbshipit-source-id: e27f5587d58b3941fca8d5b38c8c3edbcb75d7f3
parent dc37f8ea
...@@ -71,17 +71,16 @@ void LogCategory::processMessage(const LogMessage& message) const { ...@@ -71,17 +71,16 @@ void LogCategory::processMessage(const LogMessage& message) const {
try { try {
handlers[n]->handleMessage(message, this); handlers[n]->handleMessage(message, this);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
// If a LogHandler throws an exception, complain about this fact on // Use LoggerDB::internalWarning() to report the error, but continue
// stderr to avoid swallowing the error information completely. We // trying to log the message to any other handlers attached to ourself or
// don't propagate the exception up to our caller: most code does not // one of our parent categories.
// prepare for log statements to throw. We also want to continue LoggerDB::internalWarning(
// trying to log the message to any other handlers attached to ourself __FILE__,
// or one of our parent categories. __LINE__,
fprintf( "log handler for category \"",
stderr, name_,
"WARNING: log handler for category %s threw an error: %s\n", "\" threw an error: ",
name_.c_str(), folly::exceptionStr(ex));
folly::exceptionStr(ex).c_str());
} }
} }
......
...@@ -15,10 +15,17 @@ ...@@ -15,10 +15,17 @@
*/ */
#include <folly/experimental/logging/LoggerDB.h> #include <folly/experimental/logging/LoggerDB.h>
#if _WIN32
#include <crtdbg.h>
#endif
#include <folly/Conv.h>
#include <folly/FileUtil.h>
#include <folly/String.h> #include <folly/String.h>
#include <folly/experimental/logging/LogCategory.h> #include <folly/experimental/logging/LogCategory.h>
#include <folly/experimental/logging/LogLevel.h> #include <folly/experimental/logging/LogLevel.h>
#include <folly/experimental/logging/Logger.h> #include <folly/experimental/logging/Logger.h>
#include <folly/experimental/logging/RateLimiter.h>
namespace folly { namespace folly {
...@@ -215,4 +222,83 @@ LogCategory* LoggerDB::xlogInitCategory( ...@@ -215,4 +222,83 @@ LogCategory* LoggerDB::xlogInitCategory(
isInitialized->store(true, std::memory_order_release); isInitialized->store(true, std::memory_order_release);
return category; return category;
} }
std::atomic<LoggerDB::InternalWarningHandler> LoggerDB::warningHandler_;
void LoggerDB::internalWarningImpl(
folly::StringPiece filename,
int lineNumber,
std::string&& msg) noexcept {
auto handler = warningHandler_.load();
if (handler) {
handler(filename, lineNumber, std::move(msg));
} else {
defaultInternalWarningImpl(filename, lineNumber, std::move(msg));
}
}
void LoggerDB::setInternalWarningHandler(InternalWarningHandler handler) {
// This API is intentionally pretty basic. It has a number of limitations:
//
// - We only support plain function pointers, and not full std::function
// objects. This makes it possible to use std::atomic to access the
// handler pointer, and also makes it safe to store in a zero-initialized
// file-static pointer.
//
// - We don't support any void* argument to the handler. The caller is
// responsible for storing any callback state themselves.
//
// - When replacing or unsetting a handler we don't make any guarantees about
// when the old handler will stop being called. It may still be called
// from other threads briefly even after setInternalWarningHandler()
// returns. This is also a consequence of using std::atomic rather than a
// full lock.
//
// This provides the minimum capabilities needed to customize the handler,
// while still keeping the implementation simple and safe to use even before
// main().
warningHandler_.store(handler);
}
void LoggerDB::defaultInternalWarningImpl(
folly::StringPiece filename,
int lineNumber,
std::string&& msg) noexcept {
// Rate limit to 10 messages every 5 seconds.
//
// We intentonally use a leaky Meyer's singleton here over folly::Singleton:
// - We want this code to work even before main()
// - This singleton does not depend on any other singletons.
static auto* rateLimiter =
new logging::IntervalRateLimiter{10, std::chrono::seconds(5)};
if (!rateLimiter->check()) {
return;
}
#if _WIN32
// Use _CrtDbgReport() to report the error
_CrtDbgReport(
_CRT_WARN, filename, lineNumber, "folly::logging", "%s", msg.c_str());
#else
if (folly::kIsDebug) {
// Write directly to file descriptor 2.
//
// It's possible the application has closed fd 2 and is using it for
// something other than stderr. However we have no good way to detect
// this, which is the main reason we only write to stderr in debug build
// modes. assert() also writes directly to stderr on failure, which seems
// like a reasonable precedent.
//
// Another option would be to use openlog() and syslog(). However
// calling openlog() may inadvertently affect the behavior of other parts
// of the program also using syslog().
//
// We don't check for write errors here, since there's not much else we can
// do if it fails.
auto fullMsg = folly::to<std::string>(
"logging warning:", filename, ":", lineNumber, ": ", msg, "\n");
folly::writeFull(STDERR_FILENO, fullMsg.data(), fullMsg.size());
}
#endif
}
} }
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
#pragma once #pragma once
#include <folly/Conv.h>
#include <folly/CppAttributes.h> #include <folly/CppAttributes.h>
#include <folly/Range.h> #include <folly/Range.h>
#include <folly/Synchronized.h> #include <folly/Synchronized.h>
...@@ -118,6 +119,43 @@ class LoggerDB { ...@@ -118,6 +119,43 @@ class LoggerDB {
*/ */
explicit LoggerDB(TestConstructorArg); explicit LoggerDB(TestConstructorArg);
/**
* internalWarning() is used to report a problem when something goes wrong
* internally in the logging library.
*
* We can't log these messages through the normal logging flow since logging
* itself has failed.
*
* Example scenarios where this is used:
* - We fail to write to a log file (for instance, when the disk is full)
* - A LogHandler throws an unexpected exception
*/
template <typename... Args>
static void internalWarning(
folly::StringPiece file,
int lineNumber,
Args&&... args) noexcept {
internalWarningImpl(
file, lineNumber, folly::to<std::string>(std::forward<Args>(args)...));
}
using InternalWarningHandler =
void (*)(folly::StringPiece file, int lineNumber, std::string&&);
/**
* Set a function to be called when the logging library generates an internal
* warning.
*
* The supplied handler should never throw exceptions.
*
* If a null handler is supplied, the default built-in handler will be used.
*
* The default handler reports the message with _CrtDbgReport(_CRT_WARN) on
* Windows, and prints the message to stderr on other platforms. It also
* rate limits messages if they are arriving too quickly.
*/
static void setInternalWarningHandler(InternalWarningHandler handler);
private: private:
using LoggerNameMap = std::unordered_map< using LoggerNameMap = std::unordered_map<
folly::StringPiece, folly::StringPiece,
...@@ -138,6 +176,15 @@ class LoggerDB { ...@@ -138,6 +176,15 @@ class LoggerDB {
folly::StringPiece name, folly::StringPiece name,
LogCategory* parent); LogCategory* parent);
static void internalWarningImpl(
folly::StringPiece filename,
int lineNumber,
std::string&& msg) noexcept;
static void defaultInternalWarningImpl(
folly::StringPiece filename,
int lineNumber,
std::string&& msg) noexcept;
/** /**
* A map of LogCategory objects by name. * A map of LogCategory objects by name.
* *
...@@ -145,5 +192,7 @@ class LoggerDB { ...@@ -145,5 +192,7 @@ class LoggerDB {
* have to be in canonical form. * have to be in canonical form.
*/ */
folly::Synchronized<LoggerNameMap> loggersByName_; folly::Synchronized<LoggerNameMap> loggersByName_;
static std::atomic<InternalWarningHandler> warningHandler_;
}; };
} }
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