From: Adam Simpkins Date: Wed, 21 Jun 2017 02:44:18 +0000 (-0700) Subject: logging: add printf-style logging macros X-Git-Tag: v2017.06.26.00~31 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=cf984921c268141c3cf5174e27ad3e62e3614151 logging: add printf-style logging macros 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 --- diff --git a/folly/Makefile.am b/folly/Makefile.am index e02841a4..a949b1ce 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -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 \ diff --git a/folly/experimental/logging/LogStreamProcessor.cpp b/folly/experimental/logging/LogStreamProcessor.cpp index f74f3a76..ad4f9f19 100644 --- a/folly/experimental/logging/LogStreamProcessor.cpp +++ b/folly/experimental/logging/LogStreamProcessor.cpp @@ -15,11 +15,34 @@ */ #include -#include - #include 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 diff --git a/folly/experimental/logging/LogStreamProcessor.h b/folly/experimental/logging/LogStreamProcessor.h index bb24d4de..b9f78b9b 100644 --- a/folly/experimental/logging/LogStreamProcessor.h +++ b/folly/experimental/logging/LogStreamProcessor.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -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)...)} {} + : LogStreamProcessor{category, + level, + filename, + lineNumber, + INTERNAL, + createLogString(std::forward(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)...)} {} + : LogStreamProcessor{category, + level, + filename, + lineNumber, + INTERNAL, + formatLogString(fmt, std::forward(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; /** diff --git a/folly/experimental/logging/Makefile.am b/folly/experimental/logging/Makefile.am index a758c3f3..ff9deb83 100644 --- a/folly/experimental/logging/Makefile.am +++ b/folly/experimental/logging/Makefile.am @@ -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 index 00000000..dfc523de --- /dev/null +++ b/folly/experimental/logging/printf.cpp @@ -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 + +#include + +#include +#include + +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( + "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 index 00000000..373b0586 --- /dev/null +++ b/folly/experimental/logging/printf.h @@ -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 +#include + +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 index 00000000..02f04bce --- /dev/null +++ b/folly/experimental/logging/test/PrintfTest.cpp @@ -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 +#include +#include + +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(); + 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(); +}