Relationship Between LOG/CHECK Macros and Program Termination Behavior in Abseil C++
In the Abseil C++ logging library, CHECK macros expand to LOG(FATAL) calls, causing immediate program termination via std::abort() when assertions fail, while non-fatal LOG severities (INFO, WARNING, ERROR) allow execution to continue after emitting diagnostic output.
The Abseil logging system provides a unified interface for diagnostic logging and runtime assertions through the LOG and CHECK macro families. Understanding how these macros interact with program termination is essential for robust error handling, as specific severity levels trigger immediate process aborts while others permit graceful continuation.
LOG Severity Levels and Termination Behavior
The LOG macro accepts severity levels defined in absl/base/log_severity.h. The termination behavior depends entirely on which severity level you pass to the macro.
Non-Fatal Severities: INFO, WARNING, and ERROR
When you use LOG(INFO), LOG(WARNING), or LOG(ERROR), the library writes the message to the configured log sink and returns control to the caller. These severities never terminate the program.
#include "absl/log/log.h"
void ProcessData() {
LOG(INFO) << "Processing started";
LOG(WARNING) << "Deprecated API used";
LOG(ERROR) << "Recoverable I/O error, retrying...";
// Execution continues normally here
}
Fatal Severities: FATAL, QFATAL, and DFATAL
Three severity levels trigger program termination:
-
LOG(FATAL): Writes the log message, prints a stack trace to stderr, runs error handlers registered viaRunOnFailure, and terminates the process by callingstd::abort(). According to the implementation inabsl/log/internal/log_impl.h, this severity invokesABSL_LOG_INTERNAL_LOG_IMPLwith the fatal level, ensuring the process exits without executingatexithandlers. -
LOG(QFATAL)(Quiet Fatal): Behaves identically toLOG(FATAL)but suppresses the stack trace and other verbose output. It still callsstd::abort()and terminates immediately. -
LOG(DFATAL)(Debug Fatal): Conditional fatal behavior. In debug builds (#ifndef NDEBUG), it acts asFATALand aborts the program. In release builds (NDEBUGdefined), it degrades toERRORseverity, logging the message but allowing execution to continue.
How CHECK Macros Trigger Program Termination
The CHECK macros defined in absl/log/check.h and implemented in absl/log/internal/check_impl.h are assertion wrappers that evaluate conditions and terminate the program on failure.
Standard CHECK and CHECK_OP Macros
The CHECK(expr) macro evaluates the expression. If the result is false, it expands to a LOG(FATAL) call that includes the failed expression text and any streamed message content. This guarantees process termination via the same path as explicit LOG(FATAL) calls.
Binary comparison macros like CHECK_EQ, CHECK_NE, CHECK_LT, CHECK_LE, CHECK_GT, and CHECK_GE (collectively CHECK_OP) follow the same pattern. When the comparison fails, they generate a fatal log entry and abort.
#include "absl/log/check.h"
int Divide(int numerator, int denominator) {
CHECK_NE(denominator, 0) << "Division by zero";
CHECK_GE(numerator, 0) << "Numerator must be non-negative";
return numerator / denominator;
}
Quiet and Debug Variants
Abseil provides variants that modify the termination output:
QCHECK: UsesQFATALinstead ofFATAL. The program still terminates viastd::abort(), but without the noisy stack trace.DCHECK: Debug-only checks that compile to nothing in release builds (NDEBUG). When active (debug builds), a failedDCHECKtriggersDFATALbehavior, aborting the program only in debug mode.DLOG: Similar toDCHECK,DLOG(FATAL)aborts in debug builds but is completely compiled out in release builds, unlikeLOG(FATAL)which always terminates.
Implementation Details in Abseil Source Code
The termination behavior is implemented through several layers in the Abseil repository:
-
absl/log/log.h: Declares the publicLOGmacro and documents thatLOG(FATAL)terminates with a stack trace while other severities do not. -
absl/log/internal/log_impl.h: Contains the low-level implementationABSL_LOG_INTERNAL_LOG_IMPLthat ultimately invokesstd::abort()for fatal severities after flushing the log sink. -
absl/log/internal/check_impl.h: ImplementsCHECKmacros viaABSL_CHECK_IMPL, which expands to a conditional that callsABSL_LOG_INTERNAL_LOG_IMPL(_FATAL)when the check fails. -
absl/base/log_severity.h: Defines theabsl::LogSeverityenum values (kInfo,kWarning,kError,kFatal) that determine the termination path.
The fatal path explicitly calls std::abort() rather than std::terminate(), ensuring immediate process exit even if exception handling is active. Error handlers registered via RunOnFailure are executed before the abort, but atexit handlers are skipped.
Code Examples
Fatal vs. Non-Fatal Logging
#include "absl/log/log.h"
int main() {
LOG(INFO) << "Application startup";
LOG(ERROR) << "Non-critical initialization issue";
// This terminates the process; code below is unreachable
LOG(FATAL) << "Unrecoverable configuration error";
return 0; // Never executed
}
CHECK Failure Handling
#include "absl/log/check.h"
#include <vector>
int AccessElement(const std::vector<int>& data, size_t index) {
// Triggers LOG(FATAL) and aborts if index is out of bounds
CHECK(index < data.size())
<< "Index " << index << " out of bounds for size " << data.size();
return data[index];
}
Debug-Only Assertions
#include "absl/log/check.h"
void ExperimentalFeature() {
#ifndef NDEBUG
// Only checked in debug builds; compiles to nothing in release
DCHECK(absl::GetFlag(FLAGS_enable_experimental))
<< "Experimental feature flag must be enabled";
#endif
// Feature implementation...
}
Summary
LOG(FATAL),LOG(QFATAL), andCHECKall terminate the program immediately viastd::abort()after logging the error.LOG(DFATAL)andDCHECKonly abort in debug builds; in release builds, they log as errors or vanish entirely.QCHECKprovides the same termination guarantee asCHECKbut suppresses the stack trace output.- Non-fatal severities (
INFO,WARNING,ERROR) never terminate the program. - The termination path runs
RunOnFailurecallbacks but skipsatexithandlers.
Frequently Asked Questions
Does LOG(FATAL) call std::terminate or std::abort?
LOG(FATAL) calls std::abort(), not std::terminate(). As implemented in absl/log/internal/log_impl.h, the fatal severity handler explicitly invokes std::abort() after flushing logs and running failure handlers, ensuring immediate process termination without unwinding the stack or executing atexit registered functions.
What is the difference between CHECK and DCHECK?
CHECK is always active and always fatal, terminating the program in both debug and release builds if the condition fails. DCHECK is debug-only—it compiles to nothing when NDEBUG is defined (release builds), and in debug builds it behaves like CHECK but uses DFATAL severity, meaning it only aborts in debug mode.
Are destructors for local objects called when CHECK fails?
No, destructors for local objects are not guaranteed to be called. Because CHECK failure triggers std::abort() via the LOG(FATAL) path, the program terminates immediately without stack unwinding. This differs from exception-based error handling where stack unwinding would destroy local objects.
When should I use QCHECK instead of CHECK?
Use QCHECK when you need program termination without the verbose stack trace. Both macros abort the process via std::abort(), but QCHECK maps to QFATAL severity, which suppresses the full stack trace and additional error metadata that CHECK (via FATAL) would print. This is useful in production environments where you want minimal output on assertion failures.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →