Skip to content

Fix extra carriage returns in piped ANSI output on Windows#3442

Open
LowLevelLore wants to merge 10 commits into
gabime:v1.xfrom
LowLevelLore:v1.x
Open

Fix extra carriage returns in piped ANSI output on Windows#3442
LowLevelLore wants to merge 10 commits into
gabime:v1.xfrom
LowLevelLore:v1.x

Conversation

@LowLevelLore

Copy link
Copy Markdown
Contributor

Description

This PR fixes incorrect line endings in ANSI-colored output when redirected or piped on Windows terminals. A possible fix for #3138.

Problem

When using fwrite() on Windows, the CRT applies text-mode translation, converting \n to \r\n. This results in an extra carriage return (\r) being injected, especially noticeable when ANSI-colored output is redirected or piped.

Solution

On Windows, replace fwrite() with a direct call to WriteFile() using the native file handle obtained from _get_osfhandle(_fileno(...)). This bypasses the CRT entirely, ensuring output is written exactly as intended, without automatic newline translation.

Code Change

#ifdef _WIN32
    DWORD bytes_written = 0;
    HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(target_file_)));
    // WriteFile bypasses the extra \r injection
    WriteFile(h, color_code.data(), static_cast<DWORD>(color_code.size()), &bytes_written, nullptr);
#else
    // existing fwrite() path
#endif

CMake

Also included ansicolor_sink-inl.h explicitly in the build to avoid linking issues (when linking to static library).

@LowLevelLore

Copy link
Copy Markdown
Contributor Author

@gabime any updates / recommendations on this ?

@LowLevelLore

Copy link
Copy Markdown
Contributor Author

@gabime Any progress here ?

@gabime

gabime commented Aug 7, 2025

Copy link
Copy Markdown
Owner

The ansicolor sink was never designed for windows. Why not use the wincolor sink

@flagarde

Copy link
Copy Markdown

New terminal on windows can be set to understand ANSI escape code. https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences

Comment thread CMakeLists.txt
# Static/Shared library
# ---------------------------------------------------------------------------------------
set(SPDLOG_SRCS src/spdlog.cpp src/stdout_sinks.cpp src/color_sinks.cpp src/file_sinks.cpp src/async.cpp src/cfg.cpp)
list(APPEND SPDLOG_SRCS include/spdlog/sinks/ansicolor_sink-inl.h)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#ifdef _WIN32
DWORD bytes_written = 0;
HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(target_file_)));
WriteFile(h, formatted.data() + start, static_cast<DWORD>(end - start), &bytes_written,

@gabime gabime Nov 15, 2025

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_get_osfhandle shoild be called only once at construction time and store the result in a class member

#include <spdlog/details/os.h>
#include <spdlog/pattern_formatter.h>

#if defined(_WIN32)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use #ifdef _WIN32

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants