# Relationship Between LOG/CHECK Macros and Program Termination Behavior in Abseil C++

> Understand how Abseil C++ LOG and CHECK macros impact program termination. Learn how CHECK macros trigger immediate termination while LOGs allow continued execution.

- Repository: [Abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)
- Tags: internals
- Published: 2026-07-14

---

**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`](https://github.com/abseil/abseil-cpp/blob/main/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.

```cpp
#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`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/absl/log/check.h) and implemented in [`absl/log/internal/check_impl.h`](https://github.com/abseil/abseil-cpp/blob/main/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.

```cpp
#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`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/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

```cpp
#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

```cpp
#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

```cpp
#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`](https://github.com/abseil/abseil-cpp/blob/main/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.