# How spdlog Handles Colored Output on Different Terminals

> Discover how spdlog manages colored terminal output across platforms using ANSI codes and WinAPI. Learn about automatic detection and color modes for vibrant logs.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: internals
- Published: 2026-07-19

---

**spdlog abstracts terminal differences through platform-specific sinks—using ANSI escape codes on POSIX systems and WinAPI console attributes on Windows—while supporting automatic terminal capability detection and configurable color modes.**

The `gabime/spdlog` library provides robust **spdlog colored output** capabilities that work seamlessly across Linux, macOS, and Windows without requiring conditional compilation in your application code. By implementing separate sink classes for each platform's color model, spdlog ensures that log levels appear in distinct colors whether you're running in a terminal that supports ANSI codes or the legacy Windows console.

## Platform-Specific Color Sink Architecture

spdlog implements colored output through two distinct sink templates that share a common interface but handle color application differently based on the underlying operating system APIs.

### ANSI Color Sink for POSIX Systems

For Unix-like systems, spdlog uses `ansicolor_sink`, implemented in [`include/spdlog/sinks/ansicolor_sink-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/ansicolor_sink-inl.h). This sink inserts standard ANSI escape sequences (such as `\x1b[32m` for green) directly into the output stream around formatted log messages. When color mode is enabled, the sink wraps log content with the appropriate escape codes defined in its internal `colors_` array, then appends a reset sequence to clear formatting.

### Windows Console Sink

On Windows platforms, spdlog employs `wincolor_sink` from [`include/spdlog/sinks/wincolor_sink-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/wincolor_sink-inl.h). Instead of emitting escape sequences, this sink calls the Windows API functions `SetConsoleTextAttribute` and `WriteConsoleA/W` to modify the console's foreground color attributes dynamically. The implementation stores the original console attributes before setting the color, writes the message fragment, then restores the original attributes to prevent color bleeding into subsequent output.

## Automatic Terminal Detection and Color Modes

Both color sinks accept a `color_mode` argument that controls when colors are applied, allowing you to override automatic detection based on your deployment environment.

### Supported Color Modes

- **`automatic`** (default): The sink detects at runtime whether the output stream is attached to a color-capable terminal
- **`always`**: Forces color output regardless of whether the stream is a terminal or redirected to a file
- **`never`**: Disables color output entirely, emitting plain text only

### Runtime Detection Logic

In `automatic` mode, each sink performs platform-specific checks:

- **POSIX**: `ansicolor_sink` queries `details::os::in_terminal(target_file_)` and `details::os::is_color_terminal()` (defined in [`include/spdlog/details/os.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/os.h)) to verify that the file descriptor refers to a TTY and supports color sequences
- **Windows**: `wincolor_sink` calls `GetConsoleMode` on the output handle; if the call succeeds, the handle represents a real console window rather than a redirected pipe or file

## Mapping Log Levels to Colors

Each color sink maintains a `colors_` array that maps `spdlog::level::level_enum` values to platform-specific color representations.

### ANSI Color Codes

The ANSI sink stores string literals representing escape sequences:

- **Trace**: `white`
- **Debug**: `cyan`  
- **Info**: `green`
- **Warn**: `yellow_bold`
- **Error**: `red_bold`
- **Critical**: `bold_on_red`
- **Reset**: `reset` (clears all formatting)

These strings are emitted by the private `print_ccode_` method before and after the message content.

### Windows Color Attributes

The Windows sink uses `FOREGROUND_*` bit-mask constants from the Win32 API:

- **Trace**: `FOREGROUND_RED | FOREGROUND_GREEN | FOREROUND_BLUE` (white)
- **Debug**: `FOREGROUND_GREEN | FOREGROUND_BLUE` (cyan)
- **Info**: `FOREGROUND_GREEN` (green)
- **Warn**: `FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY` (yellow bold)
- **Error**: `FOREGROUND_RED | FOREGROUND_INTENSITY` (red bold)
- **Critical**: Background red with white text

The sink saves the original console attributes via `GetConsoleScreenBufferInfo`, applies the level-specific attributes, writes the output, then restores the original state.

## Pattern-Based Color Ranges

spdlog supports selective coloring through pattern formatters using the `%^` (start color) and `%$` (end color) markers. When parsing the pattern string, the formatter records byte positions in `msg.color_range_start` and `msg.color_range_end`. During the `log()` operation, the sink prints the message prefix, injects the color code (or sets the console attribute), prints the colored range, then resets the color before emitting the remainder of the message.

This allows precise control over which portions of a log line receive color treatment, such as highlighting only the log level or message text while leaving timestamps and source locations in the default terminal color.

## Creating Color Loggers with Factory Functions

The public API in [`src/color_sinks.cpp`](https://github.com/gabime/spdlog/blob/main/src/color_sinks.cpp) provides convenience factory functions that instantiate the appropriate sink type based on your compilation platform. These functions automatically forward the `color_mode` parameter to the underlying implementation.

```cpp
// Automatic detection (default behavior)
auto logger = spdlog::stdout_color_mt("console");
logger->info("Info message");      // green on most terminals
logger->warn("Warning message");   // yellow (bold) on most terminals

// Force colors even when output is redirected to a file
auto forced_logger = spdlog::stdout_color_mt("always", spdlog::color_mode::always);
forced_logger->error("Error – always coloured");

// Disable colors entirely
auto plain_logger = spdlog::stdout_color_mt("no_color", spdlog::color_mode::never);
plain_logger->debug("Debug – plain text");

// Custom color range in pattern
auto custom_logger = spdlog::stdout_color_mt("custom");
custom_logger->set_pattern("%^[%L] %v%$");   // brackets and message colored, timestamp plain
custom_logger->info("Custom coloured line");

```

The `stdout_color_mt` and `stderr_color_st` functions (along with their `_mt` and `_st` variants for thread-safe and single-threaded modes) include [`stdout_color_sinks.h`](https://github.com/gabime/spdlog/blob/main/stdout_color_sinks.h) and delegate platform selection to the preprocessor, ensuring your code remains portable without `#ifdef` blocks.

## Summary

- **spdlog colored output** relies on two platform-specific implementations: `ansicolor_sink` for POSIX systems and `wincolor_sink` for Windows
- Automatic terminal detection occurs at runtime using `is_color_terminal()` on POSIX and `GetConsoleMode` on Windows
- Color modes (`automatic`, `always`, `never`) provide explicit control over color emission regardless of terminal capabilities
- Log levels map to ANSI escape strings or `FOREGROUND_*` bit-masks depending on the platform
- Pattern markers `%^` and `%$` enable selective coloring of specific message ranges
- Factory functions in [`color_sinks.cpp`](https://github.com/gabime/spdlog/blob/main/color_sinks.cpp) abstract platform differences behind a unified API

## Frequently Asked Questions

### How do I force colors when redirecting output to a file?

Pass `spdlog::color_mode::always` as the second argument to `stdout_color_mt` or `stderr_color_st`. This bypasses the automatic terminal detection in [`include/spdlog/details/os.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/os.h) and forces the sink to emit ANSI codes or set console attributes regardless of whether the stream is a TTY.

### What terminal capabilities does spdlog check on POSIX systems?

According to [`include/spdlog/details/os.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/os.h), spdlog verifies two conditions: first, that the file descriptor is a terminal using `isatty()`, and second, that the terminal supports color by checking the `TERM` environment variable against a whitelist of color-capable terminals (including `xterm`, `screen`, `vt100`, and others supporting ANSI sequences).

### How does Windows color handling differ from ANSI terminals?

Windows uses console API calls rather than escape sequences. The `wincolor_sink` in [`include/spdlog/sinks/wincolor_sink-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/wincolor_sink-inl.h) manipulates console attributes directly through `SetConsoleTextAttribute`, whereas POSIX systems receive byte sequences like `\x1b[31m` that the terminal emulator interprets. Windows 10 and later do support ANSI escape codes, but spdlog maintains the WinAPI approach for backward compatibility with older Windows versions.

### Can I disable colors entirely without changing pattern strings?

Yes. Construct your logger with `spdlog::color_mode::never` to suppress all color output while preserving your existing format patterns. This setting prevents the sink from emitting ANSI codes or modifying console attributes, outputting plain text suitable for log files or environments where color codes would appear as garbled text.