/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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 #include #include #include #include #include #include #include #include #include #include #include namespace folly { template class XlogCategoryInfo; class XlogFileScopeInfo; /** * LogStreamProcessor receives a LogStream and logs it. * * This class is primarily intended to be used through the FB_LOG*() macros. * Its API is designed to support these macros, and is not designed for other * use. * * The operator&() method is used to trigger the logging. * This operator is used because it has lower precedence than <<, but higher * precedence than the ? ternary operator, allowing it to bind with the correct * precedence in the log macro implementations. */ class LogStreamProcessor { public: enum AppendType { APPEND }; enum FormatType { FORMAT }; /** * LogStreamProcessor constructor for use with a LOG() macro with no extra * arguments. * * Note that the filename argument is not copied. The caller should ensure * that it points to storage that will remain valid for the lifetime of the * LogStreamProcessor. (This is always the case for the __FILE__ * preprocessor macro.) */ LogStreamProcessor( const LogCategory* category, LogLevel level, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, AppendType) noexcept; /** * LogStreamProcessor constructor for use with a LOG() macro with arguments * to be concatenated with folly::to() * * Note that the filename argument is not copied. The caller should ensure * that it points to storage that will remain valid for the lifetime of the * LogStreamProcessor. (This is always the case for the __FILE__ * preprocessor macro.) */ template LogStreamProcessor( const LogCategory* category, LogLevel level, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, AppendType, Args&&... args) noexcept : LogStreamProcessor( category, level, filename, lineNumber, functionName, INTERNAL, createLogString(std::forward(args)...)) {} /** * LogStreamProcessor constructor for use with a LOG() macro with arguments * to be concatenated with folly::to() * * Note that the filename argument is not copied. The caller should ensure * that it points to storage that will remain valid for the lifetime of the * LogStreamProcessor. (This is always the case for the __FILE__ * preprocessor macro.) */ template LogStreamProcessor( const LogCategory* category, LogLevel level, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, FormatType, folly::StringPiece fmt, Args&&... args) noexcept : LogStreamProcessor( category, level, filename, lineNumber, functionName, INTERNAL, formatLogString(fmt, std::forward(args)...)) {} /* * Versions of the above constructors for use in XLOG() statements. * * 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* categoryInfo, LogLevel level, folly::StringPiece categoryName, bool isCategoryNameOverridden, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, AppendType) noexcept; template LogStreamProcessor( XlogCategoryInfo* categoryInfo, LogLevel level, folly::StringPiece categoryName, bool isCategoryNameOverridden, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, AppendType, Args&&... args) noexcept : LogStreamProcessor( categoryInfo, level, categoryName, isCategoryNameOverridden, filename, lineNumber, functionName, INTERNAL, createLogString(std::forward(args)...)) {} template LogStreamProcessor( XlogCategoryInfo* categoryInfo, LogLevel level, folly::StringPiece categoryName, bool isCategoryNameOverridden, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, FormatType, folly::StringPiece fmt, Args&&... args) noexcept : LogStreamProcessor( categoryInfo, level, categoryName, isCategoryNameOverridden, filename, lineNumber, functionName, INTERNAL, formatLogString(fmt, std::forward(args)...)) {} #ifdef __INCLUDE_LEVEL__ /* * Versions of the above constructors to use in XLOG() macros that appear in * .cpp files. These are only used if the compiler supports the * __INCLUDE_LEVEL__ macro, which we need to determine that the XLOG() * statement is not in a header file. * * These behave identically to the XlogCategoryInfo versions of the * APIs, but slightly more optimized, and allow the XLOG() code to avoid * storing category information at each XLOG() call site. */ LogStreamProcessor( XlogFileScopeInfo* fileScopeInfo, LogLevel level, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, AppendType) noexcept; LogStreamProcessor( XlogFileScopeInfo* fileScopeInfo, LogLevel level, folly::StringPiece /* categoryName */, bool /* isCategoryNameOverridden */, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, AppendType) noexcept : LogStreamProcessor( fileScopeInfo, level, filename, lineNumber, functionName, APPEND) {} template LogStreamProcessor( XlogFileScopeInfo* fileScopeInfo, LogLevel level, folly::StringPiece /* categoryName */, bool /* isCategoryNameOverridden */, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, AppendType, Args&&... args) noexcept : LogStreamProcessor( fileScopeInfo, level, filename, lineNumber, functionName, INTERNAL, createLogString(std::forward(args)...)) {} template LogStreamProcessor( XlogFileScopeInfo* fileScopeInfo, LogLevel level, folly::StringPiece /* categoryName */, bool /* isCategoryNameOverridden */, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, FormatType, folly::StringPiece fmt, Args&&... args) noexcept : LogStreamProcessor( fileScopeInfo, level, filename, lineNumber, functionName, INTERNAL, formatLogString(fmt, std::forward(args)...)) {} #endif ~LogStreamProcessor() noexcept; /** * This version of operator&() is typically used when the user specifies * log arguments using the << stream operator. The operator<<() generally * returns a std::ostream& */ void operator&(std::ostream& stream) noexcept; /** * This version of operator&() is used when no extra arguments are specified * with the << operator. In this case the & operator is applied directly to * the temporary LogStream object. */ void operator&(LogStream&& stream) noexcept; std::ostream& stream() noexcept { return stream_; } void logNow() noexcept; private: enum InternalType { INTERNAL }; LogStreamProcessor( const LogCategory* category, LogLevel level, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, InternalType, std::string&& msg) noexcept; LogStreamProcessor( XlogCategoryInfo* categoryInfo, LogLevel level, folly::StringPiece categoryName, bool isCategoryNameOverridden, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, InternalType, std::string&& msg) noexcept; LogStreamProcessor( XlogFileScopeInfo* fileScopeInfo, LogLevel level, folly::StringPiece filename, unsigned int lineNumber, folly::StringPiece functionName, InternalType, std::string&& msg) noexcept; std::string extractMessageString(LogStream& stream) noexcept; /** * Construct a log message string using folly::to() * * This function attempts to avoid throwing exceptions. If an error occurs * during formatting, a message including the error details is returned * instead. This is done to help ensure that log statements do not generate * exceptions, but instead just log an error string when something goes wrong. */ template FOLLY_NOINLINE std::string createLogString(Args&&... args) noexcept { return folly::catch_exception( [&] { return folly::to(std::forward(args)...); }, [&](const std::exception& ex) { // This most likely means there was some error converting the // arguments to strings. Handle the exception here, rather than // letting it propagate up, since callers generally do not expect log // statements to throw. // // Just log an error message letting indicating that something went // wrong formatting the log message. return folly::to( "error constructing log message: ", exceptionStr(ex)); }); } FOLLY_NOINLINE std::string vformatLogString( folly::StringPiece fmt, fmt::format_args args, bool& failed) noexcept { return folly::catch_exception( [&] { return fmt::vformat(fmt::string_view(fmt.data(), fmt.size()), args); }, [&](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 and as much of the arguments as we can // convert, to aid debugging. failed = true; std::string result; result.append("error formatting log message: "); result.append(exceptionStr(ex).c_str()); result.append("; format string: \""); result.append(fmt.data(), fmt.size()); result.append("\", arguments: "); return result; }); } /** * Construct a log message string using fmt::format() * * This function attempts to avoid throwing exceptions. If an error occurs * during formatting, a message including the error details is returned * instead. This is done to help ensure that log statements do not generate * exceptions, but instead just log an error string when something goes wrong. */ template FOLLY_NOINLINE std::string formatLogString( folly::StringPiece fmt, const Args&... args) noexcept { bool failed = false; std::string result = vformatLogString(fmt, fmt::make_format_args(args...), failed); if (failed) { folly::logging::appendToString(result, args...); } return result; } const LogCategory* const category_; LogLevel const level_; folly::StringPiece filename_; unsigned int lineNumber_; folly::StringPiece functionName_; std::string message_; LogStream stream_; }; /** * 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 class LogStreamVoidify { public: /** * 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 LogStreamVoidify { public: /** * A specialized noreturn version of operator&() for fatal log statements. */ [[noreturn]] void operator&(std::ostream&); }; /** * logDisabledHelper() is invoked in FB_LOG() and XLOG() statements if the log * admittance check fails. * * This function exists solely to ensure that both sides of the log check are * marked [[noreturn]] for fatal log messages. This allows the compiler to * recognize that the full statement is noreturn, preventing warnings about * missing return statements after fatal log messages. * * Unfortunately it does not appear possible to get the compiler to recognize * that the disabled side of the log statement should never be reached for * fatal messages. Even if we make the check something like * `(isLogLevelFatal(level) || realCheck)`, where isLogLevelFatal() is * constexpr, this is not sufficient for gcc or clang to recognize that the * full expression is noreturn. * * Ideally this would just be a template function specialized on a boolean * IsFatal parameter. Unfortunately this triggers a bug in clang, which does * not like differing noreturn behavior for different template instantiations. * Therefore we overload on integral_constant instead. * * clang-format also doesn't do a good job understanding this code and figuring * out how to format it. */ // clang-format off inline void logDisabledHelper(std::false_type) noexcept {} [[noreturn]] void logDisabledHelper(std::true_type) noexcept; // clang-format on } // namespace folly