logging: add LoggerDB::flushAllHandlers()
authorAdam Simpkins <simpkins@fb.com>
Tue, 20 Jun 2017 18:02:00 +0000 (11:02 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Tue, 20 Jun 2017 18:06:44 +0000 (11:06 -0700)
Summary:
Add a method to flush all LogHandler objects.

This will be necessary to implement FB_LOG(FATAL), as we will want to flush all
outstanding messages before crashing.

Reviewed By: wez

Differential Revision: D5189501

fbshipit-source-id: faf260b8e71e5dfed4a3b1c1aee32f072bd7b764

folly/experimental/logging/LogCategory.cpp
folly/experimental/logging/LogCategory.h
folly/experimental/logging/LoggerDB.cpp
folly/experimental/logging/LoggerDB.h
folly/experimental/logging/test/LoggerDBTest.cpp
folly/experimental/logging/test/TestLogHandler.h

index 6f02c9d..c475bf6 100644 (file)
@@ -112,6 +112,10 @@ void LogCategory::clearHandlers() {
   // LogHandler destructors.
 }
 
+std::vector<std::shared_ptr<LogHandler>> LogCategory::getHandlers() const {
+  return *(handlers_.rlock());
+}
+
 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
index e589400..d7989f4 100644 (file)
@@ -150,6 +150,11 @@ class LogCategory {
    */
   void clearHandlers();
 
+  /**
+   * Get the list of LogHandlers attached to this category.
+   */
+  std::vector<std::shared_ptr<LogHandler>> getHandlers() const;
+
   /* Internal methods for use by other parts of the logging library code */
 
   /**
index 0d64345..4c8d3a8 100644 (file)
  */
 #include <folly/experimental/logging/LoggerDB.h>
 
+#include <set>
+
 #include <folly/Conv.h>
 #include <folly/FileUtil.h>
 #include <folly/String.h>
 #include <folly/experimental/logging/LogCategory.h>
+#include <folly/experimental/logging/LogHandler.h>
 #include <folly/experimental/logging/LogLevel.h>
 #include <folly/experimental/logging/Logger.h>
 #include <folly/experimental/logging/RateLimiter.h>
@@ -40,7 +43,8 @@ class LoggerDBSingleton {
     //
     // However, we do call db_->cleanupHandlers() to destroy any registered
     // LogHandler objects.  The LogHandlers can be user-defined objects and may
-    // hold resources that should be cleaned up.
+    // hold resources that should be cleaned up.  This also ensures that the
+    // LogHandlers flush all outstanding messages before we exit.
     db_->cleanupHandlers();
   }
 
@@ -174,6 +178,26 @@ void LoggerDB::cleanupHandlers() {
   }
 }
 
+void LoggerDB::flushAllHandlers() {
+  // Build a set of all LogHandlers.  We use a set to avoid calling flush()
+  // more than once on the same handler if it is registered on multiple
+  // different categories.
+  std::set<std::shared_ptr<LogHandler>> handlers;
+  {
+    auto loggersByName = loggersByName_.wlock();
+    for (const auto& entry : *loggersByName) {
+      for (const auto& handler : entry.second->getHandlers()) {
+        handlers.emplace(handler);
+      }
+    }
+  }
+
+  // Call flush() on each handler
+  for (const auto& handler : handlers) {
+    handler->flush();
+  }
+}
+
 LogLevel LoggerDB::xlogInit(
     StringPiece categoryName,
     std::atomic<LogLevel>* xlogCategoryLevel,
index b1a1f24..9580b74 100644 (file)
@@ -91,6 +91,12 @@ class LoggerDB {
    */
   void cleanupHandlers();
 
+  /**
+   * Call flush() on all LogHandler objects registered on any LogCategory in
+   * this LoggerDB.
+   */
+  void flushAllHandlers();
+
   /**
    * Initialize the LogCategory* and std::atomic<LogLevel> used by an XLOG()
    * statement.
index 181214c..d7fbf54 100644 (file)
@@ -15,6 +15,7 @@
  */
 #include <folly/experimental/logging/Logger.h>
 #include <folly/experimental/logging/LoggerDB.h>
+#include <folly/experimental/logging/test/TestLogHandler.h>
 #include <folly/portability/GTest.h>
 
 using namespace folly;
@@ -34,6 +35,45 @@ TEST(LoggerDB, getCategory) {
   LoggerDB db{LoggerDB::TESTING};
 }
 
+TEST(LoggerDB, flushAllHandlers) {
+  LoggerDB db{LoggerDB::TESTING};
+  auto* cat1 = db.getCategory("foo");
+  auto* cat2 = db.getCategory("foo.bar.test");
+  auto* cat3 = db.getCategory("hello.world");
+  auto* cat4 = db.getCategory("other.category");
+
+  auto h1 = std::make_shared<TestLogHandler>();
+  auto h2 = std::make_shared<TestLogHandler>();
+  auto h3 = std::make_shared<TestLogHandler>();
+
+  cat1->addHandler(h1);
+
+  cat2->addHandler(h2);
+  cat2->addHandler(h3);
+
+  cat3->addHandler(h1);
+  cat3->addHandler(h2);
+  cat3->addHandler(h3);
+
+  cat4->addHandler(h1);
+
+  EXPECT_EQ(0, h1->getFlushCount());
+  EXPECT_EQ(0, h2->getFlushCount());
+  EXPECT_EQ(0, h3->getFlushCount());
+
+  // Calling flushAllHandlers() should only flush each handler once,
+  // even when they are attached to multiple categories.
+  db.flushAllHandlers();
+  EXPECT_EQ(1, h1->getFlushCount());
+  EXPECT_EQ(1, h2->getFlushCount());
+  EXPECT_EQ(1, h3->getFlushCount());
+
+  db.flushAllHandlers();
+  EXPECT_EQ(2, h1->getFlushCount());
+  EXPECT_EQ(2, h2->getFlushCount());
+  EXPECT_EQ(2, h3->getFlushCount());
+}
+
 TEST(LoggerDB, processConfigString) {
   LoggerDB db{LoggerDB::TESTING};
   db.processConfigString("foo.bar=dbg5");
index ae74313..32cba46 100644 (file)
@@ -25,6 +25,9 @@ namespace folly {
 
 /**
  * A LogHandler that simply keeps a vector of all LogMessages it receives.
+ *
+ * This class is not thread-safe.  It is intended to be used in single-threaded
+ * tests.
  */
 class TestLogHandler : public LogHandler {
  public:
@@ -38,9 +41,16 @@ class TestLogHandler : public LogHandler {
     messages_.emplace_back(message, handlerCategory);
   }
 
-  void flush() override {}
+  void flush() override {
+    ++flushCount_;
+  }
+
+  uint64_t getFlushCount() const {
+    return flushCount_;
+  }
 
  private:
   std::vector<std::pair<LogMessage, const LogCategory*>> messages_;
+  uint64_t flushCount_{0};
 };
 }