}
}
-LogStream::LogStream() : std::ostream(nullptr) {
+LogStream::LogStream(LogStreamProcessor* processor)
+ : std::ostream(nullptr), processor_{processor} {
rdbuf(&buffer_);
}
std::string str_;
};
+class LogStreamProcessor;
+
/**
* A std::ostream implementation for use by the logging macros.
*
// each FB_LOG() or XLOG() statement. Inlining them just causes extra code
// bloat, with minimal benefit--for debug log statements these never even get
// called in the common case where the log statement is disabled.
- LogStream();
+ explicit LogStream(LogStreamProcessor* processor);
~LogStream();
bool empty() const {
return buffer_.extractString();
}
+ LogStreamProcessor* getProcessor() const {
+ return processor_;
+ }
+
private:
LogStreamBuffer buffer_;
+ LogStreamProcessor* const processor_;
};
}
#include <folly/experimental/logging/LogStreamProcessor.h>
#include <folly/experimental/logging/LogStream.h>
+#include <folly/experimental/logging/xlog.h>
namespace folly {
folly::StringPiece filename,
unsigned int lineNumber,
AppendType) noexcept
+ : LogStreamProcessor(
+ category,
+ level,
+ filename,
+ lineNumber,
+ INTERNAL,
+ std::string()) {}
+
+LogStreamProcessor::LogStreamProcessor(
+ XlogCategoryInfo<true>* categoryInfo,
+ LogLevel level,
+ folly::StringPiece categoryName,
+ bool isCategoryNameOverridden,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ AppendType) noexcept
+ : LogStreamProcessor(
+ categoryInfo,
+ level,
+ categoryName,
+ isCategoryNameOverridden,
+ filename,
+ lineNumber,
+ INTERNAL,
+ std::string()) {}
+
+LogStreamProcessor::LogStreamProcessor(
+ XlogFileScopeInfo* fileScopeInfo,
+ LogLevel level,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ AppendType) noexcept
+ : LogStreamProcessor(
+ fileScopeInfo,
+ level,
+ filename,
+ lineNumber,
+ INTERNAL,
+ std::string()) {}
+
+LogStreamProcessor::LogStreamProcessor(
+ const LogCategory* category,
+ LogLevel level,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ InternalType,
+ std::string&& msg) noexcept
: category_{category},
level_{level},
filename_{filename},
- lineNumber_{lineNumber} {}
+ lineNumber_{lineNumber},
+ message_{std::move(msg)},
+ stream_{this} {}
+
+namespace {
+LogCategory* getXlogCategory(
+ XlogCategoryInfo<true>* categoryInfo,
+ folly::StringPiece categoryName,
+ bool isCategoryNameOverridden) {
+ if (!categoryInfo->isInitialized()) {
+ return categoryInfo->init(categoryName, isCategoryNameOverridden);
+ }
+ return categoryInfo->getCategory(&xlog_detail::xlogFileScopeInfo);
+}
+
+LogCategory* getXlogCategory(XlogFileScopeInfo* fileScopeInfo) {
+ // By the time a LogStreamProcessor is created, the XlogFileScopeInfo object
+ // should have already been initialized to perform the log level check.
+ // Therefore we never need to check if it is initialized here.
+ return fileScopeInfo->category;
+}
+}
+
+/**
+ * Construct a LogStreamProcessor from an XlogCategoryInfo.
+ *
+ * We intentionally define this in LogStreamProcessor.cpp instead of
+ * LogStreamProcessor.h to avoid having it inlined at every XLOG() call site,
+ * to reduce the emitted code size.
+ */
+LogStreamProcessor::LogStreamProcessor(
+ XlogCategoryInfo<true>* categoryInfo,
+ LogLevel level,
+ folly::StringPiece categoryName,
+ bool isCategoryNameOverridden,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ InternalType,
+ std::string&& msg) noexcept
+ : category_{getXlogCategory(
+ categoryInfo,
+ categoryName,
+ isCategoryNameOverridden)},
+ level_{level},
+ filename_{filename},
+ lineNumber_{lineNumber},
+ message_{std::move(msg)},
+ stream_{this} {}
+/**
+ * Construct a LogStreamProcessor from an XlogFileScopeInfo.
+ *
+ * We intentionally define this in LogStreamProcessor.cpp instead of
+ * LogStreamProcessor.h to avoid having it inlined at every XLOG() call site,
+ * to reduce the emitted code size.
+ */
LogStreamProcessor::LogStreamProcessor(
- const LogCategory* category,
+ XlogFileScopeInfo* fileScopeInfo,
LogLevel level,
- const char* filename,
+ folly::StringPiece filename,
unsigned int lineNumber,
InternalType,
std::string&& msg) noexcept
- : category_{category},
+ : category_{getXlogCategory(fileScopeInfo)},
level_{level},
filename_{filename},
lineNumber_{lineNumber},
- message_{std::move(msg)} {}
+ message_{std::move(msg)},
+ stream_{this} {}
-void LogStreamProcessor::operator&(std::ostream& stream) noexcept {
+/*
+ * We intentionally define the LogStreamProcessor destructor in
+ * LogStreamProcessor.cpp instead of LogStreamProcessor.h to avoid having it
+ * emitted inline at every log statement site. This helps reduce the emitted
+ * code size for each log statement.
+ */
+LogStreamProcessor::~LogStreamProcessor() noexcept {
+ // The LogStreamProcessor destructor is responsible for logging the message.
+ // Doing this in the destructor avoids an separate function call to log the
+ // message being emitted inline at every log statement site.
+ logNow();
+}
+
+void LogStreamProcessor::logNow() noexcept {
// Note that admitMessage() is not noexcept and theoretically may throw.
// However, the only exception that should be possible is std::bad_alloc if
// we fail to allocate memory. We intentionally let our noexcept specifier
//
// Any other error here is unexpected and we also want to fail hard
// in that situation too.
- auto& logStream = static_cast<LogStream&>(stream);
category_->admitMessage(LogMessage{category_,
level_,
filename_,
lineNumber_,
- extractMessageString(logStream)});
+ extractMessageString(stream_)});
}
-void LogStreamProcessor::operator&(LogStream&& stream) noexcept {
- // This version of operator&() is generally only invoked when
- // no streaming arguments were supplied to the logging macro.
- // Therefore we don't bother calling extractMessageString(stream),
- // and just directly use message_.
- DCHECK(stream.empty());
-
- category_->admitMessage(LogMessage{
- category_, level_, filename_, lineNumber_, std::move(message_)});
+void LogStreamVoidify<true>::operator&(std::ostream& stream) {
+ // Non-fatal log messages wait until the LogStreamProcessor destructor to log
+ // the message. However for fatal messages we log immediately in the &
+ // operator, since it is marked noreturn.
+ //
+ // This does result in slightly larger emitted code for fatal log messages
+ // (since the operator & call cannot be completely omitted). However, fatal
+ // log messages should typically be much more rare than non-fatal messages,
+ // so the small amount of extra overhead shouldn't be a big deal.
+ auto& logStream = static_cast<LogStream&>(stream);
+ logStream.getProcessor()->logNow();
+ abort();
}
std::string LogStreamProcessor::extractMessageString(
*/
#pragma once
+#include <folly/CPortability.h>
#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 <folly/experimental/logging/LogStream.h>
#include <cstdlib>
namespace folly {
-class LogStream;
-
/*
* Helper functions for fallback-formatting of arguments if folly::format()
* throws an exception.
}
}
+template <bool IsInHeaderFile>
+class XlogCategoryInfo;
+class XlogFileScopeInfo;
+
/**
* LogStreamProcessor receives a LogStream and logs it.
*
unsigned int lineNumber,
AppendType) noexcept;
+ /**
+ * LogStreamProcessor constructors for use with XLOG() macros with no extra
+ * arguments.
+ *
+ * These are defined separately from the above constructor so that the work
+ * of initializing the XLOG LogCategory data is done in a separate function
+ * body defined in LogStreamProcessor.cpp. We intentionally want to avoid
+ * inlining this work at every XLOG() statement, to reduce the emitted code
+ * size.
+ */
+ LogStreamProcessor(
+ XlogCategoryInfo<true>* categoryInfo,
+ LogLevel level,
+ folly::StringPiece categoryName,
+ bool isCategoryNameOverridden,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ AppendType) noexcept;
+ LogStreamProcessor(
+ XlogFileScopeInfo* fileScopeInfo,
+ LogLevel level,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ AppendType) noexcept;
+ LogStreamProcessor(
+ XlogFileScopeInfo* fileScopeInfo,
+ LogLevel level,
+ folly::StringPiece /* categoryName */,
+ bool /* isCategoryNameOverridden */,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ AppendType) noexcept
+ : LogStreamProcessor(fileScopeInfo, level, filename, lineNumber, APPEND) {
+ }
+
/**
* LogStreamProcessor constructor for use with a LOG() macro with arguments
* to be concatenated with folly::to<std::string>()
LogStreamProcessor(
const LogCategory* category,
LogLevel level,
- const char* filename,
+ folly::StringPiece filename,
unsigned int lineNumber,
AppendType,
Args&&... args) noexcept
- : LogStreamProcessor{category,
- level,
- filename,
- lineNumber,
- INTERNAL,
- createLogString(std::forward<Args>(args)...)} {}
+ : LogStreamProcessor(
+ category,
+ level,
+ filename,
+ lineNumber,
+ INTERNAL,
+ createLogString(std::forward<Args>(args)...)) {}
+
+ /**
+ * Versions of the above constructor for use in XLOG() statements.
+ */
+ template <typename... Args>
+ LogStreamProcessor(
+ XlogCategoryInfo<true>* categoryInfo,
+ LogLevel level,
+ folly::StringPiece categoryName,
+ bool isCategoryNameOverridden,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ AppendType,
+ Args&&... args) noexcept
+ : LogStreamProcessor(
+ categoryInfo,
+ level,
+ categoryName,
+ isCategoryNameOverridden,
+ filename,
+ lineNumber,
+ INTERNAL,
+ createLogString(std::forward<Args>(args)...)) {}
+ template <typename... Args>
+ LogStreamProcessor(
+ XlogFileScopeInfo* fileScopeInfo,
+ LogLevel level,
+ folly::StringPiece /* categoryName */,
+ bool /* isCategoryNameOverridden */,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ AppendType,
+ Args&&... args) noexcept
+ : LogStreamProcessor(
+ fileScopeInfo,
+ level,
+ filename,
+ lineNumber,
+ INTERNAL,
+ createLogString(std::forward<Args>(args)...)) {}
/**
* LogStreamProcessor constructor for use with a LOG() macro with arguments
LogStreamProcessor(
const LogCategory* category,
LogLevel level,
- const char* filename,
+ folly::StringPiece filename,
unsigned int lineNumber,
FormatType,
folly::StringPiece fmt,
Args&&... args) noexcept
- : LogStreamProcessor{category,
- level,
- filename,
- lineNumber,
- INTERNAL,
- formatLogString(fmt, std::forward<Args>(args)...)} {}
+ : LogStreamProcessor(
+ category,
+ level,
+ filename,
+ lineNumber,
+ INTERNAL,
+ formatLogString(fmt, std::forward<Args>(args)...)) {}
+
+ /**
+ * Versions of the above constructor for use in XLOG() statements.
+ */
+ template <typename... Args>
+ LogStreamProcessor(
+ XlogCategoryInfo<true>* categoryInfo,
+ LogLevel level,
+ folly::StringPiece categoryName,
+ bool isCategoryNameOverridden,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ FormatType,
+ folly::StringPiece fmt,
+ Args&&... args) noexcept
+ : LogStreamProcessor(
+ categoryInfo,
+ level,
+ categoryName,
+ isCategoryNameOverridden,
+ filename,
+ lineNumber,
+ INTERNAL,
+ formatLogString(fmt, std::forward<Args>(args)...)) {}
+
+ template <typename... Args>
+ LogStreamProcessor(
+ XlogFileScopeInfo* fileScopeInfo,
+ LogLevel level,
+ folly::StringPiece /* categoryName */,
+ bool /* isCategoryNameOverridden */,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ FormatType,
+ folly::StringPiece fmt,
+ Args&&... args) noexcept
+ : LogStreamProcessor(
+ fileScopeInfo,
+ level,
+ filename,
+ lineNumber,
+ INTERNAL,
+ formatLogString(fmt, std::forward<Args>(args)...)) {}
+
+ ~LogStreamProcessor() noexcept;
/**
* This version of operator&() is typically used when the user specifies
*/
void operator&(LogStream&& stream) noexcept;
+ std::ostream& stream() noexcept {
+ return stream_;
+ }
+
+ void logNow() noexcept;
+
private:
enum InternalType { INTERNAL };
LogStreamProcessor(
const LogCategory* category,
LogLevel level,
- const char* filename,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ InternalType,
+ std::string&& msg) noexcept;
+ LogStreamProcessor(
+ XlogCategoryInfo<true>* categoryInfo,
+ LogLevel level,
+ folly::StringPiece categoryName,
+ bool isCategoryNameOverridden,
+ folly::StringPiece filename,
+ unsigned int lineNumber,
+ InternalType,
+ std::string&& msg) noexcept;
+ LogStreamProcessor(
+ XlogFileScopeInfo* fileScopeInfo,
+ LogLevel level,
+ folly::StringPiece filename,
unsigned int lineNumber,
InternalType,
std::string&& msg) noexcept;
* exceptions, but instead just log an error string when something goes wrong.
*/
template <typename... Args>
- std::string createLogString(Args&&... args) noexcept {
+ FOLLY_NOINLINE std::string createLogString(Args&&... args) noexcept {
try {
return folly::to<std::string>(std::forward<Args>(args)...);
} catch (const std::exception& ex) {
* exceptions, but instead just log an error string when something goes wrong.
*/
template <typename... Args>
- std::string formatLogString(
+ FOLLY_NOINLINE std::string formatLogString(
folly::StringPiece fmt,
const Args&... args) noexcept {
try {
folly::StringPiece filename_;
unsigned int lineNumber_;
std::string message_;
+ LogStream stream_;
};
-/*
- * This template subclass of LogStreamProcessor exists primarily so that
- * we can specify the [[noreturn]] attribute correctly on operator&()
- * This lets the compiler know that code after LOG(FATAL) is unreachable.
+/**
+ * LogStreamVoidify() is a helper class used in the FB_LOG() and XLOG() macros.
+ *
+ * It's only purpose is to provide an & operator overload that returns void.
+ * This allows the log macros to expand roughly to:
+ *
+ * (logEnabled) ? (void)0
+ * : LogStreamVoidify{} & LogStreamProcessor{}.stream() << "msg";
+ *
+ * This enables the right hand (':') side of the ternary ? expression to have a
+ * void type, and allows various streaming operator expressions to be placed on
+ * the right hand side of the expression.
+ *
+ * Operator & is used since it has higher precedence than ?:, but lower
+ * precedence than <<.
+ *
+ * This class is templated on whether the log message is fatal so that the
+ * operator& can be declared [[noreturn]] for fatal log messages. This
+ * prevents the compiler from complaining about functions that do not return a
+ * value after a fatal log statement.
*/
template <bool Fatal>
-class LogStreamProcessorT : public LogStreamProcessor {
+class LogStreamVoidify {
public:
- using LogStreamProcessor::LogStreamProcessor;
-
- void operator&(std::ostream& stream) noexcept {
- LogStreamProcessor::operator&(stream);
- }
- void operator&(LogStream&& stream) noexcept {
- LogStreamProcessor::operator&(std::move(stream));
- }
+ /**
+ * In the default (non-fatal) case, the & operator implementation is a no-op.
+ *
+ * We perform the actual logging in the LogStreamProcessor destructor. It
+ * feels slightly hacky to perform logging in the LogStreamProcessor
+ * destructor instead of here, since the LogStreamProcessor destructor is not
+ * evaluated until the very end of the statement. In practice log
+ * statements really shouldn't be in the middle of larger statements with
+ * other side effects, so this ordering distinction shouldn't make much
+ * difference.
+ *
+ * However, by keeping this function a no-op we reduce the amount of code
+ * generated for log statements. This function call can be completely
+ * eliminated by the compiler, leaving only the LogStreamProcessor destructor
+ * invocation, which cannot be eliminated.
+ */
+ void operator&(std::ostream&)noexcept {}
};
template <>
-class LogStreamProcessorT<true> : public LogStreamProcessor {
+class LogStreamVoidify<true> {
public:
- using LogStreamProcessor::LogStreamProcessor;
-
- [[noreturn]] void operator&(std::ostream& stream) noexcept {
- LogStreamProcessor::operator&(stream);
- // We'll never actually reach here: the LogCategory code is responsible for
- // crashing on FATAL messages. However, add an abort() call so the
- // compiler knows we really cannot return here.
- std::abort();
- }
- [[noreturn]] void operator&(LogStream&& stream) noexcept {
- LogStreamProcessor::operator&(std::move(stream));
- // We'll never actually reach here: the LogCategory code is responsible for
- // crashing on FATAL messages. However, add an abort() call so the
- // compiler knows we really cannot return here.
- std::abort();
- }
+ /**
+ * A specialized noreturn version of operator&() for fatal log statements.
+ */
+ [[noreturn]] void operator&(std::ostream&);
};
}
*
* This macro generally should not be used directly by end users.
*/
-#define FB_LOG_IMPL(logger, level, type, ...) \
- (!(logger).getCategory()->logCheck(level)) \
- ? (void)0 \
- : ::folly::LogStreamProcessorT<::folly::isLogLevelFatal( \
- level)>{(logger).getCategory(), \
- (level), \
- __FILE__, \
- __LINE__, \
- (type), \
- ##__VA_ARGS__} & \
- ::folly::LogStream()
+#define FB_LOG_IMPL(logger, level, type, ...) \
+ (!(logger).getCategory()->logCheck(level)) \
+ ? (void)0 \
+ : ::folly::LogStreamVoidify<::folly::isLogLevelFatal(level)>{} & \
+ ::folly::LogStreamProcessor{(logger).getCategory(), \
+ (level), \
+ __FILE__, \
+ __LINE__, \
+ (type), \
+ ##__VA_ARGS__} \
+ .stream()
/**
* Log a message to the specified logger.
using namespace folly;
TEST(LogStream, simple) {
- LogStream ls;
+ LogStream ls{nullptr};
ls << "test";
ls << " foobar";
TEST(LogStream, largeMessage) {
std::string largeString(4096, 'a');
- LogStream ls;
+ LogStream ls{nullptr};
ls << "prefix ";
ls << largeString;
ls << " suffix";
namespace logging_test {
void testXlogFile1Dbg1(folly::StringPiece msg) {
- XLOG(DBG1, "file1: ", msg);
+ XLOG(DBG1) << "file1: " << msg;
}
}
#include <folly/experimental/logging/test/XlogHeader2.h>
#include <folly/experimental/logging/xlog.h>
#include <folly/portability/GTest.h>
+#include <folly/test/TestUtils.h>
using namespace folly;
using std::make_shared;
// settings for the entire program. Fortunately all of the other unit tests do
// use testing LoggerDB objects.
+TEST(Xlog, xlogName) {
+ EXPECT_EQ("xlog_test.main_file", XLOG_GET_CATEGORY_NAME());
+ EXPECT_EQ("xlog_test.main_file", XLOG_GET_CATEGORY()->getName());
+}
+
TEST(Xlog, xlog) {
auto handler = make_shared<TestLogHandler>();
LoggerDB::get()->getCategory("xlog_test")->addHandler(handler);
}
return categoryName;
}
+
+template <bool IsInHeaderFile>
+LogLevel XlogLevelInfo<IsInHeaderFile>::loadLevelFull(
+ folly::StringPiece categoryName,
+ bool isOverridden) {
+ auto currentLevel = level_.load(std::memory_order_acquire);
+ if (UNLIKELY(currentLevel == ::folly::LogLevel::UNINITIALIZED)) {
+ return LoggerDB::get()->xlogInit(
+ isOverridden ? categoryName : getXlogCategoryNameForFile(categoryName),
+ &level_,
+ nullptr);
+ }
+ return currentLevel;
+}
+
+template <bool IsInHeaderFile>
+LogCategory* XlogCategoryInfo<IsInHeaderFile>::init(
+ folly::StringPiece categoryName,
+ bool isOverridden) {
+ return LoggerDB::get()->xlogInitCategory(
+ isOverridden ? categoryName : getXlogCategoryNameForFile(categoryName),
+ &category_,
+ &isInitialized_);
+}
+
+#ifdef __INCLUDE_LEVEL__
+LogLevel XlogLevelInfo<false>::loadLevelFull(
+ folly::StringPiece categoryName,
+ bool isOverridden,
+ XlogFileScopeInfo* fileScopeInfo) {
+ auto currentLevel = fileScopeInfo->level.load(std::memory_order_acquire);
+ if (UNLIKELY(currentLevel == ::folly::LogLevel::UNINITIALIZED)) {
+ return LoggerDB::get()->xlogInit(
+ isOverridden ? categoryName : getXlogCategoryNameForFile(categoryName),
+ &fileScopeInfo->level,
+ &fileScopeInfo->category);
+ }
+ return currentLevel;
+}
+#endif
+
+// Explicitly instantiations of XlogLevelInfo and XlogCategoryInfo
+// If __INCLUDE_LEVEL__ is not available only the "true" variants ever get
+// used, because we cannot determine if we are ever in the .cpp file being
+// compiled or not.
+template class XlogLevelInfo<true>;
+template class XlogCategoryInfo<true>;
}
* initialized. On all subsequent calls, disabled log statements can be
* skipped with just a single check of the LogLevel.
*/
-#define XLOG_IMPL(level, type, ...) \
- (!XLOG_IS_ON_IMPL(level)) \
- ? static_cast<void>(0) \
- : ::folly::LogStreamProcessorT<::folly::isLogLevelFatal(level)>( \
- XLOG_GET_CATEGORY_INTERNAL(), \
- (level), \
- __FILE__, \
- __LINE__, \
- (type), \
- ##__VA_ARGS__) & \
- ::folly::LogStream()
+#define XLOG_IMPL(level, type, ...) \
+ (!XLOG_IS_ON_IMPL(level)) \
+ ? static_cast<void>(0) \
+ : ::folly::LogStreamVoidify<::folly::isLogLevelFatal(level)>{} & \
+ ::folly::LogStreamProcessor( \
+ [] { \
+ static ::folly::XlogCategoryInfo<XLOG_IS_IN_HEADER_FILE> \
+ _xlogCategory_; \
+ return _xlogCategory_.getInfo( \
+ &xlog_detail::xlogFileScopeInfo); \
+ }(), \
+ (level), \
+ xlog_detail::getXlogCategoryName(__FILE__, 0), \
+ xlog_detail::isXlogCategoryOverridden(0), \
+ __FILE__, \
+ __LINE__, \
+ (type), \
+ ##__VA_ARGS__) \
+ .stream()
/**
* Check if and XLOG() statement with the given log level would be enabled.
* automatically keeps it up-to-date when the category's effective level is
* changed.
*
- * Most of this code must live in the macro definition itself, and cannot be
- * moved into a helper function: The getXlogCategoryName() call must be made as
- * part of the macro expansion in order to work correctly. We also want to
- * avoid calling it whenever possible. Therefore most of the logic must be
- * done in the macro expansion.
- *
* See XlogLevelInfo for the implementation details.
*/
-#define XLOG_IS_ON_IMPL(level) \
- ([] { \
- static ::folly::XlogLevelInfo<XLOG_IS_IN_HEADER_FILE> _xlogLevel_; \
- const auto _xlogLevelToCheck_ = (level); \
- /* \
- * Do an initial relaxed check. If this fails we know the category level \
- * is initialized and the log admittance check failed. \
- * Use LIKELY() to optimize for the case of disabled debug statements: \
- * we disabled debug statements to be cheap. If the log message is \
- * enabled then this check will still be minimal perf overhead compared to \
- * the overall cost of logging it. \
- */ \
- if (LIKELY( \
- _xlogLevelToCheck_ < \
- _xlogLevel_.getLevel( \
- &_xlogFileScopeInfo_, std::memory_order_relaxed))) { \
- return false; \
- } \
- /* \
- * Load the level value with memory_order_acquire, and check \
- * to see if it is initialized or not. \
- */ \
- auto _xlogCurrentLevel_ = \
- _xlogLevel_.getLevel(&_xlogFileScopeInfo_, std::memory_order_acquire); \
- if (UNLIKELY(_xlogCurrentLevel_ == ::folly::LogLevel::UNINITIALIZED)) { \
- _xlogCurrentLevel_ = _xlogLevel_.init( \
- getXlogCategoryName(__FILE__), &_xlogFileScopeInfo_); \
- } \
- return _xlogLevelToCheck_ >= _xlogCurrentLevel_; \
+#define XLOG_IS_ON_IMPL(level) \
+ ([] { \
+ static ::folly::XlogLevelInfo<XLOG_IS_IN_HEADER_FILE> _xlogLevel_; \
+ return _xlogLevel_.check( \
+ (level), \
+ xlog_detail::getXlogCategoryName(__FILE__, 0), \
+ xlog_detail::isXlogCategoryOverridden(0), \
+ &xlog_detail::xlogFileScopeInfo); \
}())
/**
* Get the name of the log category that will be used by XLOG() statements
* in this file.
*/
-#define XLOG_GET_CATEGORY_NAME() getXlogCategoryName(__FILE__)
+#define XLOG_GET_CATEGORY_NAME() \
+ (xlog_detail::isXlogCategoryOverridden(0) \
+ ? xlog_detail::getXlogCategoryName(__FILE__, 0) \
+ : ::folly::getXlogCategoryNameForFile(__FILE__))
/**
* Get a pointer to the LogCategory that will be used by XLOG() statements in
#define XLOG_GET_CATEGORY() \
folly::LoggerDB::get()->getCategory(XLOG_GET_CATEGORY_NAME())
-/**
- * Internal version of XLOG_GET_CATEGORY() that is used in the XLOG() macro.
- *
- * This macro is used in the XLOG() implementation, and therefore must be as
- * cheap as possible. It stores the LogCategory* pointer as a local static
- * variable. Only the first invocation has to look up the log category by
- * name. Subsequent invocations re-use the already looked-up LogCategory
- * pointer.
- *
- * This is only safe to call after XlogLevelInfo::init() has been called.
- *
- * Most of this code must live in the macro definition itself, and cannot be
- * moved into a helper function: The getXlogCategoryName() call must be made as
- * part of the macro expansion in order to work correctly. We also want to
- * avoid calling it whenever possible. Therefore most of the logic must be
- * done in the macro expansion.
- *
- * See XlogCategoryInfo for the implementation details.
- */
-#define XLOG_GET_CATEGORY_INTERNAL() \
- [] { \
- static ::folly::XlogCategoryInfo<XLOG_IS_IN_HEADER_FILE> _xlogCategory_; \
- if (!_xlogCategory_.isInitialized()) { \
- return _xlogCategory_.init(getXlogCategoryName(__FILE__)); \
- } \
- return _xlogCategory_.getCategory(&_xlogFileScopeInfo_); \
- }()
-
/**
* XLOG_SET_CATEGORY_NAME() can be used to explicitly define the log category
* name used by all XLOG() and XLOGF() calls in this translation unit.
* XLOG_SET_CATEGORY_NAME() cannot be used inside header files.
*/
#ifdef __INCLUDE_LEVEL__
-#define XLOG_SET_CATEGORY_NAME(category) \
- namespace { \
- static_assert( \
- __INCLUDE_LEVEL__ == 0, \
- "XLOG_SET_CATEGORY_NAME() should not be used in header files"); \
- inline std::string getXlogCategoryName(const char*) { \
- return category; \
- } \
- }
+#define XLOG_SET_CATEGORY_CHECK \
+ static_assert( \
+ __INCLUDE_LEVEL__ == 0, \
+ "XLOG_SET_CATEGORY_NAME() should not be used in header files");
#else
-#define XLOG_SET_CATEGORY_NAME(category) \
- namespace { \
- inline std::string getXlogCategoryName(const char*) { \
- return category; \
- } \
- }
+#define XLOG_SET_CATEGORY_CHECK
#endif
+#define XLOG_SET_CATEGORY_NAME(category) \
+ namespace { \
+ namespace xlog_detail { \
+ XLOG_SET_CATEGORY_CHECK \
+ constexpr inline folly::StringPiece getXlogCategoryName( \
+ folly::StringPiece, \
+ int) { \
+ return category; \
+ } \
+ constexpr inline bool isXlogCategoryOverridden(int) { \
+ return true; \
+ } \
+ } \
+ }
+
/**
* XLOG_IS_IN_HEADER_FILE evaluates to false if we can definitively tell if we
* are not in a header file. Otherwise, it evaluates to true.
* used during dynamic object initialization before main().
*/
template <bool IsInHeaderFile>
-struct XlogLevelInfo {
+class XlogLevelInfo {
public:
- inline LogLevel getLevel(XlogFileScopeInfo*, std::memory_order order) {
- return level_.load(order);
- }
+ bool check(
+ LogLevel levelToCheck,
+ folly::StringPiece categoryName,
+ bool isOverridden,
+ XlogFileScopeInfo*) {
+ // Do an initial relaxed check. If this fails we know the category level
+ // is initialized and the log admittance check failed.
+ // Use LIKELY() to optimize for the case of disabled debug statements:
+ // we disabled debug statements to be cheap. If the log message is
+ // enabled then this check will still be minimal perf overhead compared to
+ // the overall cost of logging it.
+ if (LIKELY(levelToCheck < level_.load(std::memory_order_relaxed))) {
+ return false;
+ }
- inline LogLevel init(folly::StringPiece categoryName, XlogFileScopeInfo*) {
- return LoggerDB::get()->xlogInit(categoryName, &level_, nullptr);
+ // If we are still here, then either:
+ // - The log level check actually passed, or
+ // - level_ has not been initialized yet, and we have to initialize it and
+ // then re-perform the check.
+ //
+ // Do this work in a separate helper method. It is intentionally defined
+ // in the xlog.cpp file to avoid inlining, to reduce the amount of code
+ // emitted for each XLOG() statement.
+ auto currentLevel = loadLevelFull(categoryName, isOverridden);
+ return levelToCheck >= currentLevel;
}
private:
+ LogLevel loadLevelFull(folly::StringPiece categoryName, bool isOverridden);
+
+ // XlogLevelInfo objects are always defined with static storage.
// This member will always be zero-initialized on program start.
std::atomic<LogLevel> level_;
};
template <bool IsInHeaderFile>
-struct XlogCategoryInfo {
+class XlogCategoryInfo {
public:
bool isInitialized() const {
return isInitialized_.load(std::memory_order_acquire);
}
- LogCategory* init(folly::StringPiece categoryName) {
- return LoggerDB::get()->xlogInitCategory(
- categoryName, &category_, &isInitialized_);
- }
+ LogCategory* init(folly::StringPiece categoryName, bool isOverridden);
LogCategory* getCategory(XlogFileScopeInfo*) {
return category_;
}
+ /**
+ * Get a pointer to pass into the LogStreamProcessor constructor,
+ * so that it is able to look up the LogCategory information.
+ */
+ XlogCategoryInfo<IsInHeaderFile>* getInfo(XlogFileScopeInfo*) {
+ return this;
+ }
+
private:
// These variables will always be zero-initialized on program start.
std::atomic<bool> isInitialized_;
* for the entire file, rather than defining one for each XLOG() statement.
*/
template <>
-struct XlogLevelInfo<false> {
+class XlogLevelInfo<false> {
public:
- inline LogLevel getLevel(
- XlogFileScopeInfo* fileScopeInfo,
- std::memory_order order) {
- return fileScopeInfo->level.load(order);
- }
-
- inline LogLevel init(
+ static bool check(
+ LogLevel levelToCheck,
folly::StringPiece categoryName,
+ bool isOverridden,
XlogFileScopeInfo* fileScopeInfo) {
- return LoggerDB::get()->xlogInit(
- categoryName, &fileScopeInfo->level, &fileScopeInfo->category);
+ // As above in the non-specialized XlogFileScopeInfo code, do a simple
+ // relaxed check first.
+ if (LIKELY(
+ levelToCheck <
+ fileScopeInfo->level.load(::std::memory_order_relaxed))) {
+ return false;
+ }
+
+ // If we are still here we the file-scope log level either needs to be
+ // initalized, or the log level check legitimately passed.
+ auto currentLevel =
+ loadLevelFull(categoryName, isOverridden, fileScopeInfo);
+ return levelToCheck >= currentLevel;
}
+
+ private:
+ static LogLevel loadLevelFull(
+ folly::StringPiece categoryName,
+ bool isOverridden,
+ XlogFileScopeInfo* fileScopeInfo);
};
/**
* statement.
*/
template <>
-struct XlogCategoryInfo<false> {
+class XlogCategoryInfo<false> {
public:
- constexpr bool isInitialized() const {
- // XlogLevelInfo<false>::init() is always called before XlogCategoryInfo
- // is used, and it will have already initialized fileScopeInfo.
- // Therefore we never have to check if it is initialized yet here.
- return true;
- }
- [[noreturn]] LogCategory* init(folly::StringPiece /* categoryName */) {
- // This method is never used given that isInitialized() always returns true
- ::std::abort();
- }
- LogCategory* getCategory(XlogFileScopeInfo* fileScopeInfo) {
- return fileScopeInfo->category;
+ /**
+ * Get a pointer to pass into the LogStreamProcessor constructor,
+ * so that it is able to look up the LogCategory information.
+ */
+ XlogFileScopeInfo* getInfo(XlogFileScopeInfo* fileScopeInfo) {
+ return fileScopeInfo;
}
};
#endif
* implementation of the following functions and variables.
*/
namespace {
+namespace xlog_detail {
+/**
+ * The default getXlogCategoryName() function.
+ *
+ * By default this simply returns the filename argument passed in.
+ * The default isXlogCategoryOverridden() function returns false, indicating
+ * that the return value from getXlogCategoryName() needs to be converted
+ * using getXlogCategoryNameForFile().
+ *
+ * These are two separate steps because getXlogCategoryName() itself needs to
+ * remain constexpr--it is always evaluated in XLOG() statements, but we only
+ * want to call getXlogCategoryNameForFile() the very first time through, when
+ * we have to initialize the LogCategory object.
+ *
+ * This is a template function purely so that XLOG_SET_CATEGORY_NAME() can
+ * define a more specific version of this function that will take precedence
+ * over this one.
+ */
+template <typename T>
+constexpr inline folly::StringPiece getXlogCategoryName(
+ folly::StringPiece filename,
+ T) {
+ return filename;
+}
+
/**
- * The default getXlogCategoryName() implementation.
- * This will be used if XLOG_SET_CATEGORY_NAME() has not been used yet.
+ * The default isXlogCategoryOverridden() function.
*
- * This is a template purely so that XLOG_SET_CATEGORY_NAME() can define a more
- * specific version if desired, allowing XLOG_SET_CATEGORY_NAME() to override
- * this implementation once it has been used.
+ * This returns false indicating that the category name has not been
+ * overridden, so getXlogCategoryName() returns a raw filename that needs
+ * to be translated with getXlogCategoryNameForFile().
+ *
+ * This is a template function purely so that XLOG_SET_CATEGORY_NAME() can
+ * define a more specific version of this function that will take precedence
+ * over this one.
*/
template <typename T>
-inline std::string getXlogCategoryName(const T* filename) {
- return ::folly::getXlogCategoryNameForFile(filename);
+constexpr inline bool isXlogCategoryOverridden(T) {
+ return false;
}
/**
* entire .cpp file, rather than needing a separate copy for each XLOG()
* statement.
*/
-::folly::XlogFileScopeInfo _xlogFileScopeInfo_;
+::folly::XlogFileScopeInfo xlogFileScopeInfo;
+}
}