diff options
| author | Stefan Boberg <[email protected]> | 2026-03-09 12:03:25 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-03-09 12:03:25 +0100 |
| commit | 07649104761ee910b667adb2b865c4e2fd0979c9 (patch) | |
| tree | 16445986fd35ecb79e8f2417ae4426d1f2ea3c3d /src | |
| parent | Eliminate spdlog dependency (#773) (diff) | |
| download | zen-07649104761ee910b667adb2b865c4e2fd0979c9.tar.xz zen-07649104761ee910b667adb2b865c4e2fd0979c9.zip | |
added auto-detection logic for console colour output (#817)
Add auto-detection of colour support to `AnsicolourStdoutSink`.
**New `colorMode` enum** (`On`, `Off`, `Auto`) added to the header, accepted by the `AnsicolorStdoutSink` constructor. Defaults to `Auto`, so all existing call sites are unaffected.
**`Auto` mode detection logic** (in `IscolourTerminal()`):
1. **TTY check** -- if stdout is not a terminal, colour is disabled.
2. **`NO_COLOR`** -- respects the no-colour.org convention. If set, colour is disabled.
3. **`COLORTERM`** -- if set (e.g. `truecolour`, `24bit`), colour is enabled.
4. **`TERM`** -- rejects `dumb`; accepts known colour-capable terminals via substring match:
`alacritty`, `ansi`, `colour`, `console`, `cygwin`, `gnome`, `konsole`, `kterm`, `linux`, `msys`, `putty`, `rxvt`, `screen`, `tmux`, `vt100`, `vt102`, `xterm`.
Substring matching covers variants like `xterm-256color` and `rxvt-unicode`.
5. **Fallback** -- Windows defaults to colour enabled (modern console supports ANSI natively); other platforms default to disabled.
When colour is disabled, ANSI escape sequences are omitted entirely from the output.
NOTE: this doesn't currently apply to all paths which do logging in zen as they may be determining their colour output mode separately from `AnsicolorStdoutSink`.
Diffstat (limited to 'src')
| -rw-r--r-- | src/zencore/include/zencore/logging/ansicolorsink.h | 9 | ||||
| -rw-r--r-- | src/zencore/logging/ansicolorsink.cpp | 103 |
2 files changed, 107 insertions, 5 deletions
diff --git a/src/zencore/include/zencore/logging/ansicolorsink.h b/src/zencore/include/zencore/logging/ansicolorsink.h index 9f859e8d7..5060a8393 100644 --- a/src/zencore/include/zencore/logging/ansicolorsink.h +++ b/src/zencore/include/zencore/logging/ansicolorsink.h @@ -8,10 +8,17 @@ namespace zen::logging { +enum class ColorMode +{ + On, + Off, + Auto +}; + class AnsiColorStdoutSink : public Sink { public: - AnsiColorStdoutSink(); + explicit AnsiColorStdoutSink(ColorMode Mode = ColorMode::Auto); ~AnsiColorStdoutSink() override; void Log(const LogMessage& Msg) override; diff --git a/src/zencore/logging/ansicolorsink.cpp b/src/zencore/logging/ansicolorsink.cpp index 9b9959862..540d22359 100644 --- a/src/zencore/logging/ansicolorsink.cpp +++ b/src/zencore/logging/ansicolorsink.cpp @@ -5,8 +5,19 @@ #include <zencore/logging/messageonlyformatter.h> #include <cstdio> +#include <cstdlib> #include <mutex> +#if defined(_WIN32) +# include <io.h> +# define ZEN_ISATTY _isatty +# define ZEN_FILENO _fileno +#else +# include <unistd.h> +# define ZEN_ISATTY isatty +# define ZEN_FILENO fileno +#endif + namespace zen::logging { // Default formatter replicating spdlog's %+ pattern: @@ -98,7 +109,90 @@ GetColorForLevel(LogLevel InLevel) struct AnsiColorStdoutSink::Impl { - Impl() : m_Formatter(std::make_unique<DefaultConsoleFormatter>()) {} + explicit Impl(ColorMode Mode) : m_Formatter(std::make_unique<DefaultConsoleFormatter>()), m_UseColor(ResolveColorMode(Mode)) {} + + static bool IsColorTerminal() + { + // If stdout is not a TTY, no color + if (ZEN_ISATTY(ZEN_FILENO(stdout)) == 0) + { + return false; + } + + // NO_COLOR convention (https://no-color.org/) + if (std::getenv("NO_COLOR") != nullptr) + { + return false; + } + + // COLORTERM is set by terminals that support color (e.g. "truecolor", "24bit") + if (std::getenv("COLORTERM") != nullptr) + { + return true; + } + + // Check TERM for known color-capable values + const char* Term = std::getenv("TERM"); + if (Term != nullptr) + { + std::string_view TermView(Term); + // "dumb" terminals do not support color + if (TermView == "dumb") + { + return false; + } + // Match against known color-capable terminal types. + // TERM often includes suffixes like "-256color", so we use substring matching. + constexpr std::string_view ColorTerms[] = { + "alacritty", + "ansi", + "color", + "console", + "cygwin", + "gnome", + "konsole", + "kterm", + "linux", + "msys", + "putty", + "rxvt", + "screen", + "tmux", + "vt100", + "vt102", + "xterm", + }; + for (std::string_view Candidate : ColorTerms) + { + if (TermView.find(Candidate) != std::string_view::npos) + { + return true; + } + } + } + +#if defined(_WIN32) + // Windows console supports ANSI color by default in modern versions + return true; +#else + // Unknown terminal — be conservative + return false; +#endif + } + + static bool ResolveColorMode(ColorMode Mode) + { + switch (Mode) + { + case ColorMode::On: + return true; + case ColorMode::Off: + return false; + case ColorMode::Auto: + default: + return IsColorTerminal(); + } + } void Log(const LogMessage& Msg) { @@ -107,7 +201,7 @@ struct AnsiColorStdoutSink::Impl MemoryBuffer Formatted; m_Formatter->Format(Msg, Formatted); - if (Msg.ColorRangeEnd > Msg.ColorRangeStart) + if (m_UseColor && Msg.ColorRangeEnd > Msg.ColorRangeStart) { // Print pre-color range fwrite(Formatted.data(), 1, Msg.ColorRangeStart, m_File); @@ -148,10 +242,11 @@ struct AnsiColorStdoutSink::Impl private: std::mutex m_Mutex; std::unique_ptr<Formatter> m_Formatter; - FILE* m_File = stdout; + FILE* m_File = stdout; + bool m_UseColor = true; }; -AnsiColorStdoutSink::AnsiColorStdoutSink() : m_Impl(std::make_unique<Impl>()) +AnsiColorStdoutSink::AnsiColorStdoutSink(ColorMode Mode) : m_Impl(std::make_unique<Impl>(Mode)) { } |