Commit cf984921 authored by Adam Simpkins's avatar Adam Simpkins Committed by Facebook Github Bot

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
parent e38fcb3a
...@@ -139,6 +139,7 @@ nobase_follyinclude_HEADERS = \ ...@@ -139,6 +139,7 @@ nobase_follyinclude_HEADERS = \
experimental/logging/LogStream.h \ experimental/logging/LogStream.h \
experimental/logging/LogStreamProcessor.h \ experimental/logging/LogStreamProcessor.h \
experimental/logging/LogWriter.h \ experimental/logging/LogWriter.h \
experimental/logging/printf.h \
experimental/logging/RateLimiter.h \ experimental/logging/RateLimiter.h \
experimental/logging/StandardLogHandler.h \ experimental/logging/StandardLogHandler.h \
experimental/logging/xlog.h \ experimental/logging/xlog.h \
......
...@@ -15,11 +15,34 @@ ...@@ -15,11 +15,34 @@
*/ */
#include <folly/experimental/logging/LogStreamProcessor.h> #include <folly/experimental/logging/LogStreamProcessor.h>
#include <cassert>
#include <folly/experimental/logging/LogStream.h> #include <folly/experimental/logging/LogStream.h>
namespace folly { 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 { void LogStreamProcessor::operator&(std::ostream& stream) noexcept {
// Note that admitMessage() is not noexcept and theoretically may throw. // Note that admitMessage() is not noexcept and theoretically may throw.
// However, the only exception that should be possible is std::bad_alloc if // However, the only exception that should be possible is std::bad_alloc if
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <folly/Conv.h> #include <folly/Conv.h>
#include <folly/Demangle.h> #include <folly/Demangle.h>
#include <folly/Format.h> #include <folly/Format.h>
#include <folly/Portability.h>
#include <folly/experimental/logging/LogCategory.h> #include <folly/experimental/logging/LogCategory.h>
#include <folly/experimental/logging/LogMessage.h> #include <folly/experimental/logging/LogMessage.h>
#include <cstdlib> #include <cstdlib>
...@@ -100,11 +101,7 @@ class LogStreamProcessor { ...@@ -100,11 +101,7 @@ class LogStreamProcessor {
LogLevel level, LogLevel level,
folly::StringPiece filename, folly::StringPiece filename,
unsigned int lineNumber, unsigned int lineNumber,
AppendType) noexcept AppendType) noexcept;
: category_{category},
level_{level},
filename_{filename},
lineNumber_{lineNumber} {}
/** /**
* LogStreamProcessor constructor for use with a LOG() macro with arguments * LogStreamProcessor constructor for use with a LOG() macro with arguments
...@@ -123,11 +120,12 @@ class LogStreamProcessor { ...@@ -123,11 +120,12 @@ class LogStreamProcessor {
unsigned int lineNumber, unsigned int lineNumber,
AppendType, AppendType,
Args&&... args) noexcept Args&&... args) noexcept
: category_{category}, : LogStreamProcessor{category,
level_{level}, level,
filename_{filename}, filename,
lineNumber_{lineNumber}, lineNumber,
message_{createLogString(std::forward<Args>(args)...)} {} INTERNAL,
createLogString(std::forward<Args>(args)...)} {}
/** /**
* LogStreamProcessor constructor for use with a LOG() macro with arguments * LogStreamProcessor constructor for use with a LOG() macro with arguments
...@@ -147,11 +145,12 @@ class LogStreamProcessor { ...@@ -147,11 +145,12 @@ class LogStreamProcessor {
FormatType, FormatType,
folly::StringPiece fmt, folly::StringPiece fmt,
Args&&... args) noexcept Args&&... args) noexcept
: category_{category}, : LogStreamProcessor{category,
level_{level}, level,
filename_{filename}, filename,
lineNumber_{lineNumber}, lineNumber,
message_{formatLogString(fmt, std::forward<Args>(args)...)} {} INTERNAL,
formatLogString(fmt, std::forward<Args>(args)...)} {}
/** /**
* This version of operator&() is typically used when the user specifies * This version of operator&() is typically used when the user specifies
...@@ -168,6 +167,15 @@ class LogStreamProcessor { ...@@ -168,6 +167,15 @@ class LogStreamProcessor {
void operator&(LogStream&& stream) noexcept; void operator&(LogStream&& stream) noexcept;
private: 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; std::string extractMessageString(LogStream& stream) noexcept;
/** /**
......
...@@ -15,6 +15,7 @@ libfollylogging_la_SOURCES = \ ...@@ -15,6 +15,7 @@ libfollylogging_la_SOURCES = \
LogName.cpp \ LogName.cpp \
LogStream.cpp \ LogStream.cpp \
LogStreamProcessor.cpp \ LogStreamProcessor.cpp \
printf.cpp \
RateLimiter.cpp \ RateLimiter.cpp \
StandardLogHandler.cpp \ StandardLogHandler.cpp \
xlog.cpp xlog.cpp
......
/*
* 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);
}
}
}
/*
* 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__))
/*
* 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();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment