#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>
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.
*
* 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_;
EXPECT_EQ(
"error formatting log message: "
"invalid format argument {:6.3f}: invalid specifier 'f'; "
- "format string: \"param1: {:06d}, param2: {:6.3f}\"",
+ "format string: \"param1: {:06d}, param2: {:6.3f}\", "
+ "arguments: (int: 1234), (char [13]: hello world!)",
messages[0].first.getMessage());
EXPECT_EQ("LoggerTest.cpp", pathBasename(messages[0].first.getFileName()));
EXPECT_EQ(LogLevel::WARN, messages[0].first.getLevel());
}
class ToStringFailure {};
+class FormattableButNoToString {};
[[noreturn]] void toAppend(
const ToStringFailure& /* arg */,
"error converting ToStringFailure object to a string");
}
+namespace folly {
+template <>
+class FormatValue<ToStringFailure> {
+ public:
+ explicit FormatValue(ToStringFailure) {}
+
+ template <class FormatCallback>
+ void format(FormatArg& arg, FormatCallback& cb) const {
+ FormatValue<std::string>("ToStringFailure").format(arg, cb);
+ }
+};
+
+template <>
+class FormatValue<FormattableButNoToString> {
+ public:
+ explicit FormatValue(FormattableButNoToString) {}
+
+ template <class FormatCallback>
+ void format(FormatArg&, FormatCallback&) const {
+ throw std::runtime_error("test");
+ }
+};
+}
+
TEST_F(LoggerTest, toStringError) {
// Use the folly::to<string> log API, with an object that will throw
// an exception when we try to convert it to a string.
EXPECT_EQ(logger_.getCategory(), messages[0].second);
}
+TEST_F(LoggerTest, formatFallbackError) {
+ // Check the behavior if logf() fails, and toAppend() also fails.
+ ToStringFailure obj;
+ FB_LOGF(logger_, WARN, "param1: {}, param2: {}, {}", 1234, obj);
+
+ auto& messages = handler_->getMessages();
+ ASSERT_EQ(1, messages.size());
+ EXPECT_EQ(
+ "error formatting log message: "
+ "invalid format argument {}: argument index out of range, max=2; "
+ "format string: \"param1: {}, param2: {}, {}\", "
+ "arguments: (int: 1234), (ToStringFailure: <error_converting_to_string>)",
+ messages[0].first.getMessage());
+ EXPECT_EQ("LoggerTest.cpp", pathBasename(messages[0].first.getFileName()));
+ EXPECT_EQ(LogLevel::WARN, messages[0].first.getLevel());
+ EXPECT_FALSE(messages[0].first.containsNewlines());
+ EXPECT_EQ(logger_.getCategory(), messages[0].first.getCategory());
+ EXPECT_EQ(logger_.getCategory(), messages[0].second);
+}
+
+TEST_F(LoggerTest, formatFallbackUnsupported) {
+ // Check the behavior if logf() fails, and toAppend() also fails.
+ FormattableButNoToString obj;
+ FB_LOGF(logger_, WARN, "param1: {}, param2: {}", 1234, obj);
+
+ auto& messages = handler_->getMessages();
+ ASSERT_EQ(1, messages.size());
+ EXPECT_EQ(
+ "error formatting log message: "
+ "test; "
+ "format string: \"param1: {}, param2: {}\", "
+ "arguments: (int: 1234), "
+ "(FormattableButNoToString: <no_string_conversion>)",
+ messages[0].first.getMessage());
+ EXPECT_EQ("LoggerTest.cpp", pathBasename(messages[0].first.getFileName()));
+ EXPECT_EQ(LogLevel::WARN, messages[0].first.getLevel());
+ EXPECT_FALSE(messages[0].first.containsNewlines());
+ EXPECT_EQ(logger_.getCategory(), messages[0].first.getCategory());
+ EXPECT_EQ(logger_.getCategory(), messages[0].second);
+}
+
TEST_F(LoggerTest, streamingArgs) {
auto& messages = handler_->getMessages();
EXPECT_EQ(
"error formatting log message: "
"invalid format argument {}: argument index out of range, max=1; "
- "format string: \"whoops: {}, {}\"",
+ "format string: \"whoops: {}, {}\", "
+ "arguments: (int: 5)",
messages[0].first.getMessage());
messages.clear();
}