logging: if folly::format() fails, also log the arguments
[folly.git] / folly / experimental / logging / LogStreamProcessor.h
index b68de952bea47e5e84bb9b67a1ab28b6b34117e2..f95e00e44e6168da5422bba255fee363a917441d 100644 (file)
@@ -16,6 +16,7 @@
 #pragma once
 
 #include <folly/Conv.h>
+#include <folly/Demangle.h>
 #include <folly/Format.h>
 #include <folly/experimental/logging/LogCategory.h>
 #include <folly/experimental/logging/LogMessage.h>
@@ -24,6 +25,49 @@ namespace folly {
 
 class LogStream;
 
+/*
+ * Helper functions for fallback-formatting of arguments if folly::format()
+ * throws an exception.
+ *
+ * These are in a detail namespace so that we can include a using directive in
+ * order to do proper argument-dependent lookup of the correct toAppend()
+ * function to use.
+ */
+namespace detail {
+/* using override */
+using folly::toAppend;
+template <typename Arg>
+auto fallbackFormatOneArg(std::string* str, const Arg* arg, int) -> decltype(
+    toAppend(std::declval<Arg>(), std::declval<std::string*>()),
+    std::declval<void>()) {
+  str->push_back('(');
+  try {
+#ifdef FOLLY_HAS_RTTI
+    toAppend(folly::demangle(typeid(*arg)), str);
+    str->append(": ");
+#endif
+    toAppend(*arg, str);
+  } catch (const std::exception&) {
+    str->append("<error_converting_to_string>");
+  }
+  str->push_back(')');
+}
+
+template <typename Arg>
+inline void fallbackFormatOneArg(std::string* str, const Arg* arg, long) {
+  str->push_back('(');
+#ifdef FOLLY_HAS_RTTI
+  try {
+    toAppend(folly::demangle(typeid(*arg)), str);
+    str->append(": ");
+  } catch (const std::exception&) {
+    // Ignore the error
+  }
+#endif
+  str->append("<no_string_conversion>)");
+}
+}
+
 /**
  * LogStreamProcessor receives a LogStream and logs it.
  *
@@ -159,26 +203,50 @@ class LogStreamProcessor {
    * exceptions, but instead just log an error string when something goes wrong.
    */
   template <typename... Args>
-  std::string formatLogString(folly::StringPiece fmt, Args&&... args) noexcept {
+  std::string formatLogString(
+      folly::StringPiece fmt,
+      const Args&... args) noexcept {
     try {
-      return folly::sformat(fmt, std::forward<Args>(args)...);
+      return folly::sformat(fmt, args...);
     } catch (const std::exception& ex) {
       // This most likely means that the caller had a bug in their format
       // string/arguments.  Handle the exception here, rather than letting it
       // propagate up, since callers generally do not expect log statements to
       // throw.
       //
-      // Log the format string by itself, to help the developer at least
-      // identify the buggy format string in their code.
-      return folly::to<std::string>(
-          "error formatting log message: ",
-          ex.what(),
-          "; format string: \"",
-          fmt,
-          "\"");
+      // Log the format string and as much of the arguments as we can convert,
+      // to aid debugging.
+      std::string result;
+      result.append("error formatting log message: ");
+      result.append(ex.what());
+      result.append("; format string: \"");
+      result.append(fmt.data(), fmt.size());
+      result.append("\", arguments: ");
+      fallbackFormat(&result, args...);
+      return result;
     }
   }
 
+  /**
+   * Helper function generate a fallback version of the arguments in case
+   * folly::sformat() throws an exception.
+   *
+   * This attempts to convert each argument to a string using a similar
+   * mechanism to folly::to<std::string>(), if supported.
+   */
+  template <typename Arg1, typename... Args>
+  void
+  fallbackFormat(std::string* str, const Arg1& arg1, const Args&... remainder) {
+    detail::fallbackFormatOneArg(str, &arg1, 0);
+    str->append(", ");
+    fallbackFormat(str, remainder...);
+  }
+
+  template <typename Arg>
+  void fallbackFormat(std::string* str, const Arg& arg) {
+    detail::fallbackFormatOneArg(str, &arg, 0);
+  }
+
   const LogCategory* const category_;
   LogLevel const level_;
   folly::StringPiece filename_;