summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Derek Mauro <dmauro@google.com>2023-08-04 13:54:47 -0700
committerGravatar Copybara-Service <copybara-worker@google.com>2023-08-04 13:55:54 -0700
commit70172ada858b8739ce07e8c2f1ecd8c11c8768c7 (patch)
treee59640940355eb71082261dd9f5c87ae1aa31e78
parent659b77b713fe5f1f75e6e1bb121c0eed1c8f964a (diff)
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
-rw-r--r--absl/base/CMakeLists.txt1
-rw-r--r--absl/base/log_severity.cc1
-rw-r--r--absl/base/log_severity.h12
-rw-r--r--absl/log/CMakeLists.txt1
-rw-r--r--absl/log/globals_test.cc29
-rw-r--r--absl/log/internal/conditions.h11
-rw-r--r--absl/log/log.h2
-rw-r--r--absl/log/log_streamer.h10
-rw-r--r--absl/log/log_streamer_test.cc51
-rw-r--r--absl/log/stripping_test.cc43
10 files changed, 161 insertions, 0 deletions
diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt
index 76c4ff1d..c3271a10 100644
--- a/absl/base/CMakeLists.txt
+++ b/absl/base/CMakeLists.txt
@@ -49,6 +49,7 @@ absl_cc_library(
SRCS
"log_severity.cc"
DEPS
+ absl::config
absl::core_headers
COPTS
${ABSL_DEFAULT_COPTS}
diff --git a/absl/base/log_severity.cc b/absl/base/log_severity.cc
index 60a8fc1f..8e7bbbc9 100644
--- a/absl/base/log_severity.cc
+++ b/absl/base/log_severity.cc
@@ -17,6 +17,7 @@
#include <ostream>
#include "absl/base/attributes.h"
+#include "absl/base/config.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
diff --git a/absl/base/log_severity.h b/absl/base/log_severity.h
index 8bdca38b..c8bcd2fd 100644
--- a/absl/base/log_severity.h
+++ b/absl/base/log_severity.h
@@ -64,6 +64,8 @@ ABSL_NAMESPACE_BEGIN
// --my_log_level=info
// --my_log_level=0
//
+// `DFATAL` and `kLogDebugFatal` are similarly accepted.
+//
// Unparsing a flag produces the same result as `absl::LogSeverityName()` for
// the standard levels and a base-ten integer otherwise.
enum class LogSeverity : int {
@@ -82,6 +84,16 @@ constexpr std::array<absl::LogSeverity, 4> LogSeverities() {
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()
//
// Returns the all-caps string representation (e.g. "INFO") of the specified
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt
index 9320ce59..2fc34d01 100644
--- a/absl/log/CMakeLists.txt
+++ b/absl/log/CMakeLists.txt
@@ -845,6 +845,7 @@ absl_cc_test(
absl::log_internal_test_helpers
absl::log_severity
absl::scoped_mock_log
+ GTest::gmock
GTest::gtest_main
)
diff --git a/absl/log/globals_test.cc b/absl/log/globals_test.cc
index f7af47cd..3d936cd7 100644
--- a/absl/log/globals_test.cc
+++ b/absl/log/globals_test.cc
@@ -101,4 +101,33 @@ TEST(TestGlobals, AndroidLogTag) {
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
diff --git a/absl/log/internal/conditions.h b/absl/log/internal/conditions.h
index f576d650..41f67215 100644
--- a/absl/log/internal/conditions.h
+++ b/absl/log/internal/conditions.h
@@ -137,6 +137,15 @@
? true \
: (::absl::log_internal::ExitQuietly(), 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) \
for (int log_internal_severity_loop = 1; log_internal_severity_loop; \
@@ -163,6 +172,8 @@
ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
#define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, 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) \
for (int log_internal_severity_loop = 1; log_internal_severity_loop; \
log_internal_severity_loop = 0) \
diff --git a/absl/log/log.h b/absl/log/log.h
index 602b5acf..99e78ea8 100644
--- a/absl/log/log.h
+++ b/absl/log/log.h
@@ -32,6 +32,8 @@
// * The `QFATAL` pseudo-severity level is equivalent to `FATAL` but triggers
// quieter termination messages, e.g. without a full stack trace, and skips
// 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
// 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
diff --git a/absl/log/log_streamer.h b/absl/log/log_streamer.h
index 2d41a07f..4ed2435d 100644
--- a/absl/log/log_streamer.h
+++ b/absl/log/log_streamer.h
@@ -165,6 +165,16 @@ inline LogStreamer LogFatalStreamer(absl::string_view file, int 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
} // namespace absl
diff --git a/absl/log/log_streamer_test.cc b/absl/log/log_streamer_test.cc
index 328d70d0..40c7d488 100644
--- a/absl/log/log_streamer_test.cc
+++ b/absl/log/log_streamer_test.cc
@@ -151,6 +151,57 @@ TEST(LogStreamerDeathTest, LogFatalStreamer) {
}
#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) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
diff --git a/absl/log/stripping_test.cc b/absl/log/stripping_test.cc
index aff91495..35357039 100644
--- a/absl/log/stripping_test.cc
+++ b/absl/log/stripping_test.cc
@@ -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) {
const std::string needle = absl::Base64Escape("StrippingTest.Level");
volatile auto severity = absl::LogSeverity::kWarning;