logging: add printf-style logging macros
authorAdam Simpkins <simpkins@fb.com>
Wed, 21 Jun 2017 02:44:18 +0000 (19:44 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Wed, 21 Jun 2017 02:50:53 +0000 (19:50 -0700)
Summary:
Add new `FB_LOGC()` and `XLOGC()` macros that accept C-style printf format
strings.  (The `FB_LOGF()` and `XLOGF()` macro names are already used for
`folly::format()` style formatting.)

This will make it easier for users to update existing printf-style code to use
this new logging library.

These are in a separate `printf.h` header file that must be explicitly included
to have access to these macros.  The intent is to encourage users to use one of
the other APIs (streaming, append-style, or `folly::format()`) instead of these
printf-like APIs in new code.

Reviewed By: omry

Differential Revision: D5269974

fbshipit-source-id: 56e55f9642bb00806d9b4c762fb6a91778ef6ad3

folly/Makefile.am
folly/experimental/logging/LogStreamProcessor.cpp
folly/experimental/logging/LogStreamProcessor.h
folly/experimental/logging/Makefile.am
folly/experimental/logging/printf.cpp [new file with mode: 0644]
folly/experimental/logging/printf.h [new file with mode: 0644]
folly/experimental/logging/test/PrintfTest.cpp [new file with mode: 0644]

index e02841a..a949b1c 100644 (file)
@@ -139,6 +139,7 @@ nobase_follyinclude_HEADERS = \
        experimental/logging/LogStream.h \
        experimental/logging/LogStreamProcessor.h \
        experimental/logging/LogWriter.h \
+       experimental/logging/printf.h \
        experimental/logging/RateLimiter.h \
        experimental/logging/StandardLogHandler.h \
        experimental/logging/xlog.h \
index f74f3a7..ad4f9f1 100644 (file)
  */
 #include <folly/experimental/logging/LogStreamProcessor.h>
 
-#include <cassert>
-
 #include <folly/experimental/logging/LogStream.h>
 
 namespace folly {
+
+LogStreamProcessor::LogStreamProcessor(
+    const LogCategory* category,
+    LogLevel level,
+    folly::StringPiece filename,
+    unsigned int lineNumber,
+    AppendType) noexcept
+    : category_{category},
+      level_{level},
+      filename_{filename},
+      lineNumber_{lineNumber} {}
+
+LogStreamProcessor::LogStreamProcessor(
+    const LogCategory* category,
+    LogLevel level,
+    const char* filename,
+    unsigned int lineNumber,
+    InternalType,
+    std::string&& msg) noexcept
+    : category_{category},
+      level_{level},
+      filename_{filename},
+      lineNumber_{lineNumber},
+      message_{std::move(msg)} {}
+
 void LogStreamProcessor::operator&(std::ostream& stream) noexcept {
   // Note that admitMessage() is not noexcept and theoretically may throw.
   // However, the only exception that should be possible is std::bad_alloc if
index bb24d4d..b9f78b9 100644 (file)
@@ -18,6 +18,7 @@
 #include <folly/Conv.h>
 #include <folly/Demangle.h>
 #include <folly/Format.h>
+#include <folly/Portability.h>
 #include <folly/experimental/logging/LogCategory.h>
 #include <folly/experimental/logging/LogMessage.h>
 #include <cstdlib>
@@ -100,11 +101,7 @@ class LogStreamProcessor {
       LogLevel level,
       folly::StringPiece filename,
       unsigned int lineNumber,
-      AppendType) noexcept
-      : category_{category},
-        level_{level},
-        filename_{filename},
-        lineNumber_{lineNumber} {}
+      AppendType) noexcept;
 
   /**
    * LogStreamProcessor constructor for use with a LOG() macro with arguments
@@ -123,11 +120,12 @@ class LogStreamProcessor {
       unsigned int lineNumber,
       AppendType,
       Args&&... args) noexcept
-      : category_{category},
-        level_{level},
-        filename_{filename},
-        lineNumber_{lineNumber},
-        message_{createLogString(std::forward<Args>(args)...)} {}
+      : LogStreamProcessor{category,
+                           level,
+                           filename,
+                           lineNumber,
+                           INTERNAL,
+                           createLogString(std::forward<Args>(args)...)} {}
 
   /**
    * LogStreamProcessor constructor for use with a LOG() macro with arguments
@@ -147,11 +145,12 @@ class LogStreamProcessor {
       FormatType,
       folly::StringPiece fmt,
       Args&&... args) noexcept
-      : category_{category},
-        level_{level},
-        filename_{filename},
-        lineNumber_{lineNumber},
-        message_{formatLogString(fmt, std::forward<Args>(args)...)} {}
+      : LogStreamProcessor{category,
+                           level,
+                           filename,
+                           lineNumber,
+                           INTERNAL,
+                           formatLogString(fmt, std::forward<Args>(args)...)} {}
 
   /**
    * This version of operator&() is typically used when the user specifies
@@ -168,6 +167,15 @@ class LogStreamProcessor {
   void operator&(LogStream&& stream) noexcept;
 
  private:
+  enum InternalType { INTERNAL };
+  LogStreamProcessor(
+      const LogCategory* category,
+      LogLevel level,
+      const char* filename,
+      unsigned int lineNumber,
+      InternalType,
+      std::string&& msg) noexcept;
+
   std::string extractMessageString(LogStream& stream) noexcept;
 
   /**
index a758c3f..ff9deb8 100644 (file)
@@ -15,6 +15,7 @@ libfollylogging_la_SOURCES = \
        LogName.cpp \
        LogStream.cpp \
        LogStreamProcessor.cpp \
+       printf.cpp \
        RateLimiter.cpp \
        StandardLogHandler.cpp \
        xlog.cpp
diff --git a/folly/experimental/logging/printf.cpp b/folly/experimental/logging/printf.cpp
new file mode 100644 (file)
index 0000000..dfc523d
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2004-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <folly/experimental/logging/printf.h>
+
+#include <cstdarg>
+
+#include <folly/Conv.h>
+#include <folly/String.h>
+
+namespace folly {
+
+std::string loggingFormatPrintf(const char* format, ...) noexcept {
+  va_list ap;
+  va_start(ap, format);
+  SCOPE_EXIT {
+    va_end(ap);
+  };
+  try {
+    return stringVPrintf(format, ap);
+  } catch (const std::exception&) {
+    // We don't bother including the exception message here.
+    // The exceptions thrown by stringVPrintf() don't normally have much useful
+    // information regarding what precisely went wrong.
+    return folly::to<std::string>(
+        "error formatting printf-style log message: ", format);
+  }
+}
+}
diff --git a/folly/experimental/logging/printf.h b/folly/experimental/logging/printf.h
new file mode 100644 (file)
index 0000000..373b058
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2004-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+/*
+ * C-style printf-like macros for the logging library.
+ *
+ * These are defined in their own separate header file to discourage their use
+ * in new code.  These macros make it somewhat easier to convert existing code
+ * using printf()-like statements to the logging library.  However, new code
+ * should generally prefer to use one of the other macro forms instead
+ * (simple argument concatenation, folly::format based, or iostream-like).
+ *
+ * These use a "C" suffix to the macro name since these use C-style format
+ * syntax.  (The "F" suffix is used for folly:format()-style.)
+ */
+
+#include <folly/experimental/logging/Logger.h>
+#include <folly/experimental/logging/xlog.h>
+
+namespace folly {
+std::string loggingFormatPrintf(
+    FOLLY_PRINTF_FORMAT const char* format,
+    ...) noexcept FOLLY_PRINTF_FORMAT_ATTR(1, 2);
+}
+
+/**
+ * Log a message to the specified logger using a printf-style format string.
+ */
+#define FB_LOGC(logger, level, fmt, ...)   \
+  FB_LOG_IMPL(                             \
+      logger,                              \
+      ::folly::LogLevel::level,            \
+      ::folly::LogStreamProcessor::APPEND, \
+      ::folly::loggingFormatPrintf(fmt, ##__VA_ARGS__))
+
+/**
+ * Log a message to the file's default log category using a printf-style format
+ * string.
+ */
+#define XLOGC(level, fmt, ...)             \
+  XLOG_IMPL(                               \
+      ::folly::LogLevel::level,            \
+      ::folly::LogStreamProcessor::APPEND, \
+      ::folly::loggingFormatPrintf(fmt, ##__VA_ARGS__))
diff --git a/folly/experimental/logging/test/PrintfTest.cpp b/folly/experimental/logging/test/PrintfTest.cpp
new file mode 100644 (file)
index 0000000..02f04bc
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2004-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <folly/experimental/logging/printf.h>
+#include <folly/experimental/logging/test/TestLogHandler.h>
+#include <folly/portability/GTest.h>
+
+using namespace folly;
+using std::make_shared;
+
+TEST(PrintfTest, printfStyleMacros) {
+  LoggerDB db{LoggerDB::TESTING};
+  Logger logger{&db, "test"};
+  auto* category = logger.getCategory();
+
+  auto handler = make_shared<TestLogHandler>();
+  category->addHandler(handler);
+  category->setLevel(LogLevel::DEBUG, true);
+
+  Logger foo{&db, "test.foo.bar"};
+  Logger foobar{&db, "test.foo.bar"};
+  Logger footest{&db, "test.foo.test"};
+  Logger footest1234{&db, "test.foo.test.1234"};
+  Logger other{&db, "test.other"};
+  db.setLevel("test", LogLevel::ERROR);
+  db.setLevel("test.foo", LogLevel::DBG2);
+  db.setLevel("test.foo.test", LogLevel::DBG7);
+
+  auto& messages = handler->getMessages();
+
+  // test.other's effective level should be ERROR, so a warning
+  // message to it should be discarded
+  FB_LOGC(other, WARN, "this should be discarded: %d", 5);
+  ASSERT_EQ(0, messages.size());
+
+  // Disabled log messages should not evaluate their arguments
+  bool argumentEvaluated = false;
+  auto getValue = [&] {
+    argumentEvaluated = true;
+    return 5;
+  };
+  FB_LOGC(foobar, DBG3, "discarded message: %d", getValue());
+  EXPECT_FALSE(argumentEvaluated);
+
+  FB_LOGC(foobar, DBG1, "this message should pass: %d", getValue());
+  ASSERT_EQ(1, messages.size());
+  EXPECT_EQ("this message should pass: 5", messages[0].first.getMessage());
+  EXPECT_TRUE(argumentEvaluated);
+  messages.clear();
+
+  // The FB_LOGC() macro should work even if the format string does not contain
+  // any format sequences.  Ideally people would just use FB_LOG() if they
+  // aren't actually formatting anything, but making FB_LOGC() work in this
+  // scenario still makes it easier for people to switch legacy printf-style
+  // code to FB_LOGC().
+  FB_LOGC(foobar, DBG1, "no actual format arguments");
+  ASSERT_EQ(1, messages.size());
+  EXPECT_EQ("no actual format arguments", messages[0].first.getMessage());
+  messages.clear();
+
+  // Similar checks with XLOGC()
+  auto* xlogCategory = XLOG_GET_CATEGORY();
+  xlogCategory->addHandler(handler);
+  xlogCategory->setLevel(LogLevel::DBG5, true);
+
+  argumentEvaluated = false;
+  XLOGC(DBG9, "failing log check: %d", getValue());
+  EXPECT_FALSE(argumentEvaluated);
+
+  XLOGC(DBG5, "passing log: %03d", getValue());
+  ASSERT_EQ(1, messages.size());
+  EXPECT_EQ("passing log: 005", messages[0].first.getMessage());
+  EXPECT_TRUE(argumentEvaluated);
+  messages.clear();
+
+  XLOGC(DBG1, "no xlog format arguments");
+  ASSERT_EQ(1, messages.size());
+  EXPECT_EQ("no xlog format arguments", messages[0].first.getMessage());
+  messages.clear();
+
+  // Errors attempting to format the message should not throw
+  FB_LOGC(footest1234, ERROR, "width overflow: %999999999999999999999d", 5);
+  ASSERT_EQ(1, messages.size());
+  EXPECT_EQ(
+      "error formatting printf-style log message: "
+      "width overflow: %999999999999999999999d",
+      messages[0].first.getMessage());
+  messages.clear();
+}