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 via RunOnFailure, and terminates the process by calling std::abort(). According to the implementation in absl/log/internal/log_impl.h, this severity invokes ABSL_LOG_INTERNAL_LOG_IMPL with the fatal level, ensuring the process exits without executing atexit handlers.

  • LOG(QFATAL) (Quiet Fatal): Behaves identically to LOG(FATAL) but suppresses the stack trace and other verbose output. It still calls std::abort() and terminates immediately.

  • LOG(DFATAL) (Debug Fatal): Conditional fatal behavior. In debug builds (#ifndef NDEBUG), it acts as FATAL and aborts the program. In release builds (NDEBUG defined), it degrades to ERROR severity, 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: Uses QFATAL instead of FATAL. The program still terminates via std::abort(), but without the noisy stack trace.
  • DCHECK: Debug-only checks that compile to nothing in release builds (NDEBUG). When active (debug builds), a failed DCHECK triggers DFATAL behavior, aborting the program only in debug mode.
  • DLOG: Similar to DCHECK, DLOG(FATAL) aborts in debug builds but is completely compiled out in release builds, unlike LOG(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 public LOG macro and documents that LOG(FATAL) terminates with a stack trace while other severities do not.

  • absl/log/internal/log_impl.h: Contains the low-level implementation ABSL_LOG_INTERNAL_LOG_IMPL that ultimately invokes std::abort() for fatal severities after flushing the log sink.

  • absl/log/internal/check_impl.h: Implements CHECK macros via ABSL_CHECK_IMPL, which expands to a conditional that calls ABSL_LOG_INTERNAL_LOG_IMPL(_FATAL) when the check fails.

  • absl/base/log_severity.h: Defines the absl::LogSeverity enum 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), and CHECK all terminate the program immediately via std::abort() after logging the error.
  • LOG(DFATAL) and DCHECK only abort in debug builds; in release builds, they log as errors or vanish entirely.
  • QCHECK provides the same termination guarantee as CHECK but suppresses the stack trace output.
  • Non-fatal severities (INFO, WARNING, ERROR) never terminate the program.
  • The termination path runs RunOnFailure callbacks but skips atexit handlers.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →