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

logging: add a default handler during early initialization

Summary:
This updates the code to create a default LogHandler that logs to stderr when
the main LoggerDB singleton is first created.  With this change users will get
reasonable log behavior even if they never call `folly::initLogging()` (log
messages will go to stderr instead of being ignored).

This new initialization code can potentially run very early during program
initialization (before `main()` starts and before folly `Singleton`
initialization) if some code in the program logs messages before main.  However
this current initialization code should be safe to run before main.

For compilers that support weak symbols (clang and gcc), I have defined
`initializeLoggerDB()` as a weak symbol, allowing users to override this
behavior at link time on a per-program basis should they choose to.

Reviewed By: yfeldblum

Differential Revision: D6893207

fbshipit-source-id: 13b72a01aa383c4611204df88ff8dad93abcc7b2
parent b05a90dc
......@@ -22,58 +22,13 @@
namespace folly {
/**
* The base logging settings to be applied in initLogging(),
* before any user-specified settings.
*
* This defines a log handler named "default" that writes to stderr,
* and configures the root log category to log to this handler and have a log
* level setting of WARN.
*
* Note that the default log handler uses async=false, so that log messages are
* written immediately to stderr from the thread that generated the log
* message. This is often undesirable, as it can slow down normal processing
* waiting for logging I/O. However, using async=true has some trade-offs of
* its own: it causes a new thread to be started, and not all message may get
* flushed to stderr if the program crashes. For now, using async=false seems
* like the safer trade-off here, but many programs may wish to change this
* default.
*
* The user-specified config string may override any of these values.
* If the user-specified config string ends up not using the default log
* handler, the handler will be automatically forgotten by the LoggerDB code.
*/
constexpr StringPiece kDefaultLoggingConfig =
".=WARN:default; default=stream:stream=stderr,async=false";
void initLogging(StringPiece configString) {
// Register the StreamHandlerFactory
LoggerDB::get().registerHandlerFactory(
std::make_unique<StreamHandlerFactory>());
// TODO: In the future it would be nice to build a better mechanism so that
// additional LogHandlerFactory objects could be automatically registered on
// startup if they are linked into our current executable.
//
// For now we register only the StreamHandlerFactory. There is a
// FileHandlerFactory, but we do not register it by default: it allows
// appending to arbitrary files based on the config settings, and we do not
// want to allow this by default for security reasons. (In the future
// maybe it would be worth enabling the FileHandlerFactory by default if we
// confirm that we are not a setuid or setgid binary. i.e., if the real
// UID+GID is the same as the effective UID+GID.)
// Parse the default log level settings before processing the input config
// string.
auto config = parseLogConfig(kDefaultLoggingConfig);
// Then apply the configuration string
if (!configString.empty()) {
auto inputConfig = parseLogConfig(configString);
config.update(inputConfig);
if (configString.empty()) {
return;
}
// Now apply the configuration to the LoggerDB
// Parse and apply the config string
auto config = parseLogConfig(configString);
LoggerDB::get().updateConfig(config);
}
......
......@@ -30,15 +30,12 @@ namespace folly {
* The input string will be parsed with parseLogConfig() and then applied to
* the main LoggerDB singleton.
*
* Before it is applied, the input configuration settings are first combined
* with some basic defaults on the root log category. The defaults set the
* root log level to WARN, and attach a log handler named "default" that writes
* messages to stderr. However, these base settings can be overridden if the
* input string specifies alternate settings for the root log category.
* This will apply the configuration using LoggerDB::updateConfig(), so the new
* configuration will be merged into the existing initial configuration defined
* by initializeLoggerDB().
*
* Note that it is safe for code to use logging functions before calling
* initLogging(). However, messages logged before initLogging() is called will
* be ignored since no log handler objects have been defined.
* Callers that do want to completely replace the settings can call
* LoggerDB::resetConfig() instead of using initLogging().
*/
void initLogging(folly::StringPiece configString = "");
......
......@@ -27,17 +27,60 @@
#include <folly/experimental/logging/LogLevel.h>
#include <folly/experimental/logging/Logger.h>
#include <folly/experimental/logging/RateLimiter.h>
#include <folly/experimental/logging/StreamHandlerFactory.h>
#include <folly/portability/Config.h>
using std::string;
namespace folly {
/*
* The default implementation of initializeLoggerDB().
*
* This is defined as a weak symbol to allow programs to provide their own
* alternative definition if desired.
*/
#if FOLLY_HAVE_WEAK_SYMBOLS
void initializeLoggerDB(LoggerDB& db) __attribute__((weak));
#endif
void initializeLoggerDB(LoggerDB& db) {
// Register the StreamHandlerFactory
//
// This is the only LogHandlerFactory that we register by default. We
// intentionally do not register FileHandlerFactory, since this allows
// LoggerDB::updateConfig() to open and write to arbitrary files. This is
// potentially a security concern if programs accept user-customizable log
// configuration settings from untrusted sources.
//
// Users can always register additional LogHandlerFactory objects on their
// own inside their main() function.
db.registerHandlerFactory(std::make_unique<StreamHandlerFactory>());
// Build a default LogConfig object.
// This writes messages to stderr synchronously (immediately, in the thread
// that generated the message), using the default GLOG-style formatter.
auto defaultHandlerConfig =
LogHandlerConfig("stream", {{"stream", "stderr"}, {"async", "false"}});
auto rootCategoryConfig =
LogCategoryConfig(LogLevel::WARNING, false, {"default"});
LogConfig config(
/* handlerConfigs */ {{"default", defaultHandlerConfig}},
/* categoryConfig */ {{"", rootCategoryConfig}});
// Update the configuration
db.updateConfig(config);
}
namespace {
class LoggerDBSingleton {
public:
explicit LoggerDBSingleton(LoggerDB* db) : db_{db} {}
explicit LoggerDBSingleton(LoggerDB* FOLLY_NONNULL db) : db_{db} {
// Call initializeLoggerDB() to apply some basic initial configuration.
initializeLoggerDB(*db_);
}
~LoggerDBSingleton() {
// We intentionally leak the LoggerDB object on destruction.
// We intentionally leak the LoggerDB object on normal destruction.
// We want Logger objects to remain valid for the entire lifetime of the
// program, without having to worry about destruction ordering issues, or
// making the Logger perform reference counting on the LoggerDB.
......@@ -50,6 +93,7 @@ class LoggerDBSingleton {
// hold resources that should be cleaned up. This also ensures that the
// LogHandlers flush all outstanding messages before we exit.
db_->cleanupHandlers();
db_.release();
}
LoggerDB& getDB() const {
......@@ -57,7 +101,10 @@ class LoggerDBSingleton {
}
private:
LoggerDB* db_;
// Store LoggerDB as a unique_ptr so it will be automatically destroyed if
// initializeLoggerDB() throws in the constructor. We will explicitly
// release this during the normal destructor.
std::unique_ptr<LoggerDB> db_;
};
} // namespace
......
......@@ -293,4 +293,29 @@ class LoggerDB {
static std::atomic<InternalWarningHandler> warningHandler_;
};
/**
* initializeLoggerDB() will be called to configure the main LoggerDB singleton
* the first time that LoggerDB::get() is called.
*
* This function can be used apply basic default settings to the LoggerDB,
* including default log level and log handler settings.
*
* A default implementation is provided as a weak symbol, so it can be
* overridden on a per-program basis if you want to customize the initial
* LoggerDB settings for your program.
*
* However, note that this function may be invoked before main() starts (if
* other code that runs before main uses the logging library). Therefore you
* should be careful about what code runs here. For instance, you probably
* should not create LogHandler objects that spawn new threads. It is
* generally a good idea to defer more complicated setup until after main()
* starts.
*
* The default implementation configures the root log category to write all
* warning and higher-level log messages to stderr, using a format similar to
* that used by GLOG.
*/
void initializeLoggerDB(LoggerDB& db);
} // namespace folly
......@@ -95,8 +95,7 @@ class FatalTests(unittest.TestCase):
def test_static_init(self):
err = self.run_helper(env={'CRASH_DURING_INIT': '1'})
regex = self.get_crash_regex(br'crashing during static initialization',
glog=False)
regex = self.get_crash_regex(br'crashing during static initialization')
self.assertRegex(err, regex)
def test_static_destruction(self):
......
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