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 6f02c9dca6a090f6d474907747fcab70920ca4d6..c475bf6e686e9851e6bb13cb90da756298c7cf01 100644 (file)
@@ -112,6 +112,10 @@ void LogCategory::clearHandlers() {
   // LogHandler destructors.
 }
 
   // 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
 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 e589400e910f0ab0f8d460e463259f81679741b2..d7989f4b62bbc50ababde25290b557b5145c8ec7 100644 (file)
@@ -150,6 +150,11 @@ class LogCategory {
    */
   void clearHandlers();
 
    */
   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 */
 
   /**
   /* Internal methods for use by other parts of the logging library code */
 
   /**
index 0d6434513413401928ee41460c80c28e4a61ae3a..4c8d3a80423e5837c9f53f1a848c329cf9fb0a36 100644 (file)
  */
 #include <folly/experimental/logging/LoggerDB.h>
 
  */
 #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/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>
 #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
     //
     // 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();
   }
 
     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,
 LogLevel LoggerDB::xlogInit(
     StringPiece categoryName,
     std::atomic<LogLevel>* xlogCategoryLevel,
index b1a1f249d233280493b9a63c209ab3fd3857e91f..9580b7434c2adc0f9ac403431faa6921f48243f4 100644 (file)
@@ -91,6 +91,12 @@ class LoggerDB {
    */
   void cleanupHandlers();
 
    */
   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.
   /**
    * Initialize the LogCategory* and std::atomic<LogLevel> used by an XLOG()
    * statement.
index 181214c3a5585f9299747f7663d024b404792ea5..d7fbf5434336b55da2607f93190935ec9102fb6e 100644 (file)
@@ -15,6 +15,7 @@
  */
 #include <folly/experimental/logging/Logger.h>
 #include <folly/experimental/logging/LoggerDB.h>
  */
 #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;
 #include <folly/portability/GTest.h>
 
 using namespace folly;
@@ -34,6 +35,45 @@ TEST(LoggerDB, getCategory) {
   LoggerDB db{LoggerDB::TESTING};
 }
 
   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");
 TEST(LoggerDB, processConfigString) {
   LoggerDB db{LoggerDB::TESTING};
   db.processConfigString("foo.bar=dbg5");
index ae74313b2f6291a15a3c04ff0113bd5c6231fd5e..32cba46b168fb7d2721498e3aa972351447a6e68 100644 (file)
@@ -25,6 +25,9 @@ namespace folly {
 
 /**
  * A LogHandler that simply keeps a vector of all LogMessages it receives.
 
 /**
  * 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:
  */
 class TestLogHandler : public LogHandler {
  public:
@@ -38,9 +41,16 @@ class TestLogHandler : public LogHandler {
     messages_.emplace_back(message, handlerCategory);
   }
 
     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_;
 
  private:
   std::vector<std::pair<LogMessage, const LogCategory*>> messages_;
+  uint64_t flushCount_{0};
 };
 }
 };
 }