aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-09 12:03:25 +0100
committerGitHub Enterprise <[email protected]>2026-03-09 12:03:25 +0100
commit07649104761ee910b667adb2b865c4e2fd0979c9 (patch)
tree16445986fd35ecb79e8f2417ae4426d1f2ea3c3d /src
parentEliminate spdlog dependency (#773) (diff)
downloadzen-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.h9
-rw-r--r--src/zencore/logging/ansicolorsink.cpp103
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))
{
}