#include <folly/experimental/logging/LogCategory.h>
#include <cstdio>
+#include <cstdlib>
#include <folly/ExceptionString.h>
+#include <folly/FileUtil.h>
#include <folly/experimental/logging/LogHandler.h>
#include <folly/experimental/logging/LogMessage.h>
#include <folly/experimental/logging/LogName.h>
parent_->firstChild_ = this;
}
+void LogCategory::admitMessage(const LogMessage& message) const {
+ processMessage(message);
+
+ // If this is a fatal message, flush the handlers to make sure the log
+ // message was written out, then crash.
+ if (isLogLevelFatal(message.getLevel())) {
+ auto numHandlers = db_->flushAllHandlers();
+ if (numHandlers == 0) {
+ // No log handlers were configured.
+ // Print the message to stderr, to make sure we always print the reason
+ // we are crashing somewhere.
+ auto msg = folly::to<std::string>(
+ "FATAL:",
+ message.getFileName(),
+ ":",
+ message.getLineNumber(),
+ ": ",
+ message.getMessage(),
+ "\n");
+ folly::writeFull(STDERR_FILENO, msg.data(), msg.size());
+ }
+ std::abort();
+ }
+}
+
void LogCategory::processMessage(const LogMessage& message) const {
// Make a copy of any attached LogHandlers, so we can release the handlers_
// lock before holding them.
/* Internal methods for use by other parts of the logging library code */
/**
- * Process a log message.
+ * Admit a message into the LogCategory hierarchy to be logged.
+ *
+ * The caller is responsible for having already performed log level
+ * admittance checks.
*
* This method generally should be invoked only through the logging macros,
* rather than calling this directly.
- *
- * This method assumes that log level admittance checks have already been
- * performed. This method unconditionally passes the message to the
- * LogHandlers attached to this LogCategory, without any additional log level
- * checks (apart from the ones done in the LogHandlers).
*/
- void processMessage(const LogMessage& message) const;
+ void admitMessage(const LogMessage& message) const;
/**
* Note: setLevelLocked() may only be called while holding the main
LogCategory(LogCategory const&) = delete;
LogCategory& operator=(LogCategory const&) = delete;
+ void processMessage(const LogMessage& message) const;
void updateEffectiveLevel(LogLevel newEffectiveLevel);
void parentLevelUpdated(LogLevel parentEffectiveLevel);
return LogLevel::ERR;
} else if (lowerName == "critical") {
return LogLevel::CRITICAL;
+ } else if (lowerName == "dfatal") {
+ return LogLevel::DFATAL;
+ } else if (lowerName == "fatal") {
+ return LogLevel::FATAL;
} else if (lowerName == "max" || lowerName == "max_level") {
return LogLevel::MAX_LEVEL;
}
return "LogLevel::ERR";
} else if (level == LogLevel::CRITICAL) {
return "LogLevel::CRITICAL";
- } else if (level == LogLevel::MAX_LEVEL) {
- return "LogLevel::MAX_LEVEL";
+ } else if (level == LogLevel::DFATAL) {
+ return "LogLevel::DFATAL";
+ } else if (level == LogLevel::FATAL) {
+ return "LogLevel::FATAL";
}
if (static_cast<uint32_t>(level) <= static_cast<uint32_t>(LogLevel::DBG0) &&
#include <cstdint>
#include <iosfwd>
#include <string>
+#include <type_traits>
+#include <folly/Portability.h>
#include <folly/Range.h>
namespace folly {
CRITICAL = 5000,
+ // DFATAL log messages crash the program on debug builds.
+ DFATAL = 0x7ffffffe,
+ // FATAL log messages always abort the program.
+ // This level is equivalent to MAX_LEVEL.
+ FATAL = 0x7fffffff,
+
// The most significant bit is used by LogCategory to store a flag value,
// so the maximum value has that bit cleared.
//
* Support adding and subtracting integers from LogLevels, to create slightly
* adjusted log level values.
*/
-inline LogLevel operator+(LogLevel level, uint32_t value) {
+inline constexpr LogLevel operator+(LogLevel level, uint32_t value) {
auto newValue = static_cast<uint32_t>(level) + value;
// Cap the result at LogLevel::MAX_LEVEL
if (newValue > static_cast<uint32_t>(LogLevel::MAX_LEVEL)) {
level = level + value;
return level;
}
-inline LogLevel operator-(LogLevel level, uint32_t value) {
+inline constexpr LogLevel operator-(LogLevel level, uint32_t value) {
return static_cast<LogLevel>(static_cast<uint32_t>(level) - value);
}
inline LogLevel& operator-=(LogLevel& level, uint32_t value) {
* Print a LogLevel in a human readable format.
*/
std::ostream& operator<<(std::ostream& os, LogLevel level);
+
+/**
+ * Returns true if and only if a LogLevel is fatal.
+ */
+inline constexpr bool isLogLevelFatal(LogLevel level) {
+ if (folly::kIsDebug) {
+ return level >= LogLevel::DFATAL;
+ } else {
+ return level >= LogLevel::FATAL;
+ }
+}
}
namespace folly {
void LogStreamProcessor::operator&(std::ostream& stream) noexcept {
- // Note that processMessage() 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
// we fail to allocate memory. We intentionally let our noexcept specifier
// crash in that case, since the program likely won't be able to continue
// Any other error here is unexpected and we also want to fail hard
// in that situation too.
auto& logStream = static_cast<LogStream&>(stream);
- category_->processMessage(LogMessage{category_,
- level_,
- filename_,
- lineNumber_,
- extractMessageString(logStream)});
+ category_->admitMessage(LogMessage{category_,
+ level_,
+ filename_,
+ lineNumber_,
+ extractMessageString(logStream)});
}
void LogStreamProcessor::operator&(LogStream&& stream) noexcept {
// and just directly use message_.
DCHECK(stream.empty());
- category_->processMessage(LogMessage{
+ category_->admitMessage(LogMessage{
category_, level_, filename_, lineNumber_, std::move(message_)});
}
#include <folly/Format.h>
#include <folly/experimental/logging/LogCategory.h>
#include <folly/experimental/logging/LogMessage.h>
+#include <cstdlib>
namespace folly {
unsigned int lineNumber_;
std::string message_;
};
+
+/*
+ * 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.
+ */
+template <bool Fatal>
+class LogStreamProcessorT : public LogStreamProcessor {
+ public:
+ using LogStreamProcessor::LogStreamProcessor;
+
+ void operator&(std::ostream& stream) noexcept {
+ LogStreamProcessor::operator&(stream);
+ }
+ void operator&(LogStream&& stream) noexcept {
+ LogStreamProcessor::operator&(std::move(stream));
+ }
+};
+
+template <>
+class LogStreamProcessorT<true> : public LogStreamProcessor {
+ 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();
+ }
+};
}
*
* 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::LogStreamProcessor{(logger).getCategory(), \
- (level), \
- __FILE__, \
- __LINE__, \
- (type), \
- ##__VA_ARGS__} & \
+#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()
/**
}
}
-void LoggerDB::flushAllHandlers() {
+size_t LoggerDB::flushAllHandlers() {
// Build a set of all LogHandlers. We use a set to avoid calling flush()
// more than once on the same handler if it is registered on multiple
// different categories.
for (const auto& handler : handlers) {
handler->flush();
}
+ return handlers.size();
}
LogLevel LoggerDB::xlogInit(
/**
* Call flush() on all LogHandler objects registered on any LogCategory in
* this LoggerDB.
+ *
+ * Returns the number of registered LogHandlers.
*/
- void flushAllHandlers();
+ size_t flushAllHandlers();
/**
* Initialize the LogCategory* and std::atomic<LogLevel> used by an XLOG()
--- /dev/null
+/*
+ * 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/Init.h>
+#include <folly/experimental/logging/xlog.h>
+#include <folly/init/Init.h>
+
+DEFINE_string(logging, "", "Logging category configuration string");
+DEFINE_string(
+ handler_style,
+ "async",
+ "Log handler style: async, immediate, or none");
+
+DEFINE_string(
+ category,
+ "",
+ "Crash with a message to this category instead of the default");
+
+using folly::LogLevel;
+
+/*
+ * This is a simple helper program to exercise the LOG(FATAL) functionality.
+ */
+int main(int argc, char* argv[]) {
+ // Call folly::init() and then initialize log levels and handlers
+ folly::init(&argc, &argv);
+
+ if (FLAGS_handler_style == "async") {
+ initLoggingGlogStyle(FLAGS_logging, LogLevel::INFO, true);
+ } else if (FLAGS_handler_style == "immediate") {
+ initLoggingGlogStyle(FLAGS_logging, LogLevel::INFO, false);
+ } else if (FLAGS_handler_style != "none") {
+ XLOGF(FATAL, "unknown log handler style \"{}\"", FLAGS_handler_style);
+ }
+
+ if (!FLAGS_category.empty()) {
+ folly::Logger logger{FLAGS_category};
+ FB_LOG(logger, FATAL, "crashing to category ", FLAGS_category);
+ }
+
+ XLOG(FATAL) << "test program crashing!";
+ // Even though main() is defined to return an integer, the compiler
+ // should be able to detect that XLOG(FATAL) never returns. It shouldn't
+ // complain that we don't return an integer here.
+}
EXPECT_EQ(LogLevel::CRITICAL, stringToLogLevel("critical"));
EXPECT_EQ(LogLevel::CRITICAL, stringToLogLevel("CRITICAL"));
+ EXPECT_EQ(LogLevel::DFATAL, stringToLogLevel("dfatal"));
+ EXPECT_EQ(LogLevel::DFATAL, stringToLogLevel("DFatal"));
+ EXPECT_EQ(LogLevel::DFATAL, stringToLogLevel("DFATAL"));
+
+ EXPECT_EQ(LogLevel::FATAL, stringToLogLevel("fatal"));
+ EXPECT_EQ(LogLevel::FATAL, stringToLogLevel("FaTaL"));
+ EXPECT_EQ(LogLevel::FATAL, stringToLogLevel("FATAL"));
+
EXPECT_EQ(LogLevel::MAX_LEVEL, stringToLogLevel("max"));
EXPECT_EQ(LogLevel::MAX_LEVEL, stringToLogLevel("Max_Level"));
EXPECT_EQ(LogLevel::MAX_LEVEL, stringToLogLevel("LogLevel::MAX"));
EXPECT_EQ("LogLevel::DEBUG", logLevelToString(LogLevel::DEBUG));
EXPECT_EQ("LogLevel::ERR", logLevelToString(LogLevel::ERR));
EXPECT_EQ("LogLevel::CRITICAL", logLevelToString(LogLevel::CRITICAL));
- EXPECT_EQ("LogLevel::MAX_LEVEL", logLevelToString(LogLevel::MAX_LEVEL));
+ EXPECT_EQ("LogLevel::DFATAL", logLevelToString(LogLevel::DFATAL));
+ EXPECT_EQ("LogLevel::FATAL", logLevelToString(LogLevel::FATAL));
+ EXPECT_EQ("LogLevel::FATAL", logLevelToString(LogLevel::MAX_LEVEL));
EXPECT_EQ("LogLevel::DBG0", logLevelToString(LogLevel::DBG0));
EXPECT_EQ("LogLevel::DBG2", logLevelToString(LogLevel::DBG2));
checkLevel(LogLevel::WARNING);
checkLevel(LogLevel::ERR);
checkLevel(LogLevel::CRITICAL);
- checkLevel(LogLevel::MAX_LEVEL);
+ checkLevel(LogLevel::DFATAL);
+ checkLevel(LogLevel::FATAL);
// Try with some random integer values
for (uint32_t numIters = 0; numIters < 10000; ++numIters) {
--- /dev/null
+#!/usr/bin/env python3
+#
+# 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.
+#
+import os
+import re
+import signal
+import subprocess
+import unittest
+
+
+class FatalTests(unittest.TestCase):
+ def setUp(self):
+ fatal_helper_env = os.environ.get('FOLLY_FATAL_HELPER')
+ if fatal_helper_env:
+ self.helper = fatal_helper_env
+ else:
+ build_dir = os.path.join(os.getcwd(), 'buck-out', 'gen')
+ self.helper = os.path.join(build_dir, 'folly', 'experimental',
+ 'logging', 'test', 'fatal_helper')
+
+ def run_helper(self, *args):
+ '''
+ Run the helper.
+ Check that it crashes with SIGABRT and prints nothing on stdout.
+ Returns the data printed to stderr.
+ '''
+ cmd = [self.helper]
+ cmd.extend(args)
+ p = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ status = p.returncode
+
+ self.assertEqual(status, -signal.SIGABRT)
+ self.assertEqual(out, b'')
+ return err
+
+ def glog_crash_regex(self):
+ return re.compile(
+ br'^C[0-9]{4} .* FatalHelper.cpp:[0-9]+\] test program crashing!$',
+ re.MULTILINE)
+
+ def test_async(self):
+ err = self.run_helper('--handler_style=async')
+ self.assertRegex(err, self.glog_crash_regex())
+
+ def test_immediate(self):
+ err = self.run_helper('--handler_style=immediate')
+ self.assertRegex(err, self.glog_crash_regex())
+
+ def test_none(self):
+ # The fatal message should be printed directly to stderr when there
+ # are no logging handlers configured.
+ err = self.run_helper('--handler_style=none')
+ return re.compile(
+ br'^FATAL:.*/FatalHelper.cpp:[0-9]+: test program crashing!$',
+ re.MULTILINE)
+ self.assertRegex(err, self.glog_crash_regex())
+
+ def test_other_category(self):
+ err = self.run_helper('--category=foo.bar',
+ '--logging', '.=FATAL')
+ regex = re.compile(
+ br'^C[0-9]{4} .* FatalHelper.cpp:[0-9]+\] '
+ br'crashing to category foo\.bar$',
+ re.MULTILINE)
+ self.assertRegex(err, regex)
* 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::LogStreamProcessor( \
- XLOG_GET_CATEGORY(), \
- (level), \
- __FILE__, \
- __LINE__, \
- (type), \
- ##__VA_ARGS__) & \
+#define XLOG_IMPL(level, type, ...) \
+ (!XLOG_IS_ON_IMPL(level)) \
+ ? static_cast<void>(0) \
+ : ::folly::LogStreamProcessorT<::folly::isLogLevelFatal(level)>( \
+ XLOG_GET_CATEGORY(), \
+ (level), \
+ __FILE__, \
+ __LINE__, \
+ (type), \
+ ##__VA_ARGS__) & \
::folly::LogStream()
/**