Commit 70172ada by Derek Mauro Committed by Copybara-Service

Release the `DFATAL` pseudo-LogSeverity level

`DFATAL` is defined as `FATAL` in debug mode, and as `ERROR`
when `NDEBUG` is defined.

Closes #1279

PiperOrigin-RevId: 553904244
Change-Id: Iaa207ee65b2a39b4b7f5da241208c3d39cd5da0e
parent 659b77b7
...@@ -49,6 +49,7 @@ absl_cc_library( ...@@ -49,6 +49,7 @@ absl_cc_library(
SRCS SRCS
"log_severity.cc" "log_severity.cc"
DEPS DEPS
absl::config
absl::core_headers absl::core_headers
COPTS COPTS
${ABSL_DEFAULT_COPTS} ${ABSL_DEFAULT_COPTS}
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <ostream> #include <ostream>
#include "absl/base/attributes.h" #include "absl/base/attributes.h"
#include "absl/base/config.h"
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
......
...@@ -64,6 +64,8 @@ ABSL_NAMESPACE_BEGIN ...@@ -64,6 +64,8 @@ ABSL_NAMESPACE_BEGIN
// --my_log_level=info // --my_log_level=info
// --my_log_level=0 // --my_log_level=0
// //
// `DFATAL` and `kLogDebugFatal` are similarly accepted.
//
// Unparsing a flag produces the same result as `absl::LogSeverityName()` for // Unparsing a flag produces the same result as `absl::LogSeverityName()` for
// the standard levels and a base-ten integer otherwise. // the standard levels and a base-ten integer otherwise.
enum class LogSeverity : int { enum class LogSeverity : int {
...@@ -82,6 +84,16 @@ constexpr std::array<absl::LogSeverity, 4> LogSeverities() { ...@@ -82,6 +84,16 @@ constexpr std::array<absl::LogSeverity, 4> LogSeverities() {
absl::LogSeverity::kError, absl::LogSeverity::kFatal}}; absl::LogSeverity::kError, absl::LogSeverity::kFatal}};
} }
// `absl::kLogDebugFatal` equals `absl::LogSeverity::kFatal` in debug builds
// (i.e. when `NDEBUG` is not defined) and `absl::LogSeverity::kError`
// otherwise. Avoid ODR-using this variable as it has internal linkage and thus
// distinct storage in different TUs.
#ifdef NDEBUG
static constexpr absl::LogSeverity kLogDebugFatal = absl::LogSeverity::kError;
#else
static constexpr absl::LogSeverity kLogDebugFatal = absl::LogSeverity::kFatal;
#endif
// LogSeverityName() // LogSeverityName()
// //
// Returns the all-caps string representation (e.g. "INFO") of the specified // Returns the all-caps string representation (e.g. "INFO") of the specified
......
...@@ -845,6 +845,7 @@ absl_cc_test( ...@@ -845,6 +845,7 @@ absl_cc_test(
absl::log_internal_test_helpers absl::log_internal_test_helpers
absl::log_severity absl::log_severity
absl::scoped_mock_log absl::scoped_mock_log
GTest::gmock
GTest::gtest_main GTest::gtest_main
) )
......
...@@ -101,4 +101,33 @@ TEST(TestGlobals, AndroidLogTag) { ...@@ -101,4 +101,33 @@ TEST(TestGlobals, AndroidLogTag) {
EXPECT_DEATH_IF_SUPPORTED(absl::SetAndroidNativeTag("test_tag_fail"), ".*"); EXPECT_DEATH_IF_SUPPORTED(absl::SetAndroidNativeTag("test_tag_fail"), ".*");
} }
TEST(TestExitOnDFatal, OffTest) {
// Turn off...
absl::log_internal::SetExitOnDFatal(false);
EXPECT_FALSE(absl::log_internal::ExitOnDFatal());
// We don't die.
{
absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
// LOG(DFATAL) has severity FATAL if debugging, but is
// downgraded to ERROR if not debugging.
EXPECT_CALL(log, Log(absl::kLogDebugFatal, _, "This should not be fatal"));
log.StartCapturingLogs();
LOG(DFATAL) << "This should not be fatal";
}
}
#if GTEST_HAS_DEATH_TEST
TEST(TestDeathWhileExitOnDFatal, OnTest) {
absl::log_internal::SetExitOnDFatal(true);
EXPECT_TRUE(absl::log_internal::ExitOnDFatal());
// Death comes on little cats' feet.
EXPECT_DEBUG_DEATH({ LOG(DFATAL) << "This should be fatal in debug mode"; },
"This should be fatal in debug mode");
}
#endif
} // namespace } // namespace
...@@ -137,6 +137,15 @@ ...@@ -137,6 +137,15 @@
? true \ ? true \
: (::absl::log_internal::ExitQuietly(), false)) \ : (::absl::log_internal::ExitQuietly(), false)) \
: false)) : false))
#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition) \
ABSL_LOG_INTERNAL_##type##_CONDITION( \
(ABSL_ASSUME(absl::kLogDebugFatal == absl::LogSeverity::kError || \
absl::kLogDebugFatal == absl::LogSeverity::kFatal), \
(condition) && \
(::absl::kLogDebugFatal >= \
static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \
(::absl::kLogDebugFatal == ::absl::LogSeverity::kFatal && \
(::absl::log_internal::AbortQuietly(), false)))))
#define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ #define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \
for (int log_internal_severity_loop = 1; log_internal_severity_loop; \ for (int log_internal_severity_loop = 1; log_internal_severity_loop; \
...@@ -163,6 +172,8 @@ ...@@ -163,6 +172,8 @@
ABSL_LOG_INTERNAL_##type##_CONDITION(condition) ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
#define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \ #define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \
ABSL_LOG_INTERNAL_##type##_CONDITION(condition) ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition) \
ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
#define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ #define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \
for (int log_internal_severity_loop = 1; log_internal_severity_loop; \ for (int log_internal_severity_loop = 1; log_internal_severity_loop; \
log_internal_severity_loop = 0) \ log_internal_severity_loop = 0) \
......
...@@ -32,6 +32,8 @@ ...@@ -32,6 +32,8 @@
// * The `QFATAL` pseudo-severity level is equivalent to `FATAL` but triggers // * The `QFATAL` pseudo-severity level is equivalent to `FATAL` but triggers
// quieter termination messages, e.g. without a full stack trace, and skips // quieter termination messages, e.g. without a full stack trace, and skips
// running registered error handlers. // running registered error handlers.
// * The `DFATAL` pseudo-severity level is defined as `FATAL` in debug mode and
// as `ERROR` otherwise.
// Some preprocessor shenanigans are used to ensure that e.g. `LOG(INFO)` has // Some preprocessor shenanigans are used to ensure that e.g. `LOG(INFO)` has
// the same meaning even if a local symbol or preprocessor macro named `INFO` is // the same meaning even if a local symbol or preprocessor macro named `INFO` is
// defined. To specify a severity level using an expression instead of a // defined. To specify a severity level using an expression instead of a
......
...@@ -165,6 +165,16 @@ inline LogStreamer LogFatalStreamer(absl::string_view file, int line) { ...@@ -165,6 +165,16 @@ inline LogStreamer LogFatalStreamer(absl::string_view file, int line) {
return absl::LogStreamer(absl::LogSeverity::kFatal, file, line); return absl::LogStreamer(absl::LogSeverity::kFatal, file, line);
} }
// LogDebugFatalStreamer()
//
// Returns a LogStreamer that writes at level LogSeverity::kLogDebugFatal.
//
// In debug mode, the program will be terminated when this `LogStreamer` is
// destroyed, regardless of whether any data were streamed in.
inline LogStreamer LogDebugFatalStreamer(absl::string_view file, int line) {
return absl::LogStreamer(absl::kLogDebugFatal, file, line);
}
ABSL_NAMESPACE_END ABSL_NAMESPACE_END
} // namespace absl } // namespace absl
......
...@@ -151,6 +151,57 @@ TEST(LogStreamerDeathTest, LogFatalStreamer) { ...@@ -151,6 +151,57 @@ TEST(LogStreamerDeathTest, LogFatalStreamer) {
} }
#endif #endif
#ifdef NDEBUG
TEST(LogStreamerTest, LogDebugFatalStreamer) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
EXPECT_CALL(
test_sink,
Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)),
TimestampInMatchWindow(),
ThreadID(Eq(absl::base_internal::GetTID())),
TextMessage(Eq("WriteToStream: foo")),
ENCODED_MESSAGE(EqualsProto(R"pb(value {
str: "WriteToStream: foo"
})pb")),
Stacktrace(IsEmpty()))));
test_sink.StartCapturingLogs();
WriteToStream("foo",
&absl::LogDebugFatalStreamer("path/file.cc", 1234).stream());
}
#elif GTEST_HAS_DEATH_TEST
TEST(LogStreamerDeathTest, LogDebugFatalStreamer) {
EXPECT_EXIT(
{
absl::ScopedMockLog test_sink;
EXPECT_CALL(test_sink, Send)
.Times(AnyNumber())
.WillRepeatedly(DeathTestUnexpectedLogging());
EXPECT_CALL(
test_sink,
Send(AllOf(
SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)),
TimestampInMatchWindow(),
ThreadID(Eq(absl::base_internal::GetTID())),
TextMessage(Eq("WriteToStream: foo")),
ENCODED_MESSAGE(EqualsProto(R"pb(value {
str: "WriteToStream: foo"
})pb")))))
.WillOnce(DeathTestExpectedLogging());
test_sink.StartCapturingLogs();
WriteToStream(
"foo", &absl::LogDebugFatalStreamer("path/file.cc", 1234).stream());
},
DiedOfFatal, DeathTestValidateExpectations());
}
#endif
TEST(LogStreamerTest, LogStreamer) { TEST(LogStreamerTest, LogStreamer) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
......
...@@ -322,6 +322,49 @@ TEST_F(StrippingTest, Fatal) { ...@@ -322,6 +322,49 @@ TEST_F(StrippingTest, Fatal) {
} }
} }
TEST_F(StrippingTest, DFatal) {
// We need to load a copy of the needle string into memory (so we can search
// for it) without leaving it lying around in plaintext in the executable file
// as would happen if we used a literal. We might (or might not) leave it
// lying around later; that's what the tests are for!
const std::string needle = absl::Base64Escape("StrippingTest.DFatal");
// We don't care if the LOG statement is actually executed, we're just
// checking that it's stripped.
if (kReallyDie) LOG(DFATAL) << "U3RyaXBwaW5nVGVzdC5ERmF0YWw=";
std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
ASSERT_THAT(exe, NotNull());
// `DFATAL` can be `ERROR` or `FATAL`, and a compile-time optimizer doesn't
// know which, because `absl::kLogDebugFatal` is declared `extern` and defined
// in another TU. Link-time optimization might do better. We have six cases:
// | `AMLL` is-> | `<=ERROR` | `FATAL` | `>FATAL` |
// | ------------------- | --------- | ------- | -------- |
// | `DFATAL` is `ERROR` | present | ? | stripped |
// | `DFATAL` is `FATAL` | present | present | stripped |
// These constexpr variables are used to suppress unreachable code warnings
// in the if-else statements below.
// "present" in the table above: `DFATAL` exceeds `ABSL_MIN_LOG_LEVEL`, so
// `DFATAL` statements should not be stripped (and they should be logged
// when executed, but that's a different testsuite).
constexpr bool kExpectPresent = absl::kLogDebugFatal >= kAbslMinLogLevel;
// "stripped" in the table above: even though the compiler may not know
// which value `DFATAL` has, it should be able to strip it since both
// possible values ought to be stripped.
constexpr bool kExpectStripped = kAbslMinLogLevel > absl::LogSeverity::kFatal;
if (kExpectPresent) {
EXPECT_THAT(exe.get(), FileHasSubstr(needle));
} else if (kExpectStripped) {
EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
} else {
// "?" in the table above; may or may not be stripped depending on whether
// any link-time optimization is done. Either outcome is ok.
}
}
TEST_F(StrippingTest, Level) { TEST_F(StrippingTest, Level) {
const std::string needle = absl::Base64Escape("StrippingTest.Level"); const std::string needle = absl::Base64Escape("StrippingTest.Level");
volatile auto severity = absl::LogSeverity::kWarning; volatile auto severity = absl::LogSeverity::kWarning;
......
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