Production grade is hard…

Real colors on real terminals

George Shuklin
journey to rust
2 min readOct 10, 2022

--

I’m writing some fun utility, but with serious implementation. I decided to add colors to errors.

What can be easier to do?

[dependencies]
colored = "2"

and

Err(e) => {
eprintln!("{}", e.to_string().red());
}

Colored support automatic detection of NO_COLOR, so did I got a production grade ‘red’ message?

Nope

foo
(colored output in stderr)

but

foo > /dev/null
(no colors in stderr)

Why? Because colored can’t see if I’m printing to stdout or stderr, and it’s checking stdout for been a tty. It’s not, therefore, message should have no colors. But! I have tty for stderr, although there is no tty for stdout.

Bum!

Who said production is simple?

The next lib (termcolor) I’m trying is much harder to use:

use std::io::{self, Write};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

fn write_green() -> io::Result<()> {
let mut stdout = StandardStream::stdout(ColorChoice::Always);
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
writeln!(&mut stdout, "green text!")
}

and it does not detect atty. Plainly in docs. Huh. No. Next is owo-colors.

foo > /dev/null

give me colors, good. But foo 2> foobar got me esc-codes in the file. Not good. Do they have any helper code to detect if colors are needed?

println!(
"{}",
"colored blue if a supported terminal".
.if_supports_color(Stream::Stdout, |text| text.bright_blue())
);

But it does not work! A direct copy-paste from examples does not even compile.

Oh, we need a feature “supports-color”. I enabled it, but it still do not compile. No Stream found.

I poked around as much as I can, nope, thanks. Next.

colour crate even don’t mentioning auto-detection…

Okay, let’s turn back. There is colored, which lacks proper detection for stderr. But there is the crate supports-color I found earlier inside owo-colors. Let’s bolt together color and supports-color:

let msg = if let Some(support) = supports_color::on(Stream::Stdout){
e.to_string().red()
}else{
e.to_string()
};

Aghr… String and ColoredString are different types. Can I make ColoredString without colors? Nope.

Okay, let’s do it straightforward way:

if supports_color::on(Stream::Stderr).is_some(){
eprintln!("{}", e.to_string().red());
}else{
eprintln!("{}", e);
};

But it fails foo >/dev/null test, because ‘red’ is thinking stdout is not a tty, therefore, no colors.

Ahrrr…

colored::control::set_override(
supports_color::on(Stream::Stderr).is_some()
);
eprintln!("{}", e.to_string().red());

Okay, usual tests passed.

All redirects works. Environment variable NO_COLOR is respected.

But can I force color? Yes, there is FORCE_COLOR !

Bingo. Now I have production-grade error message.

This is production!

It took me about one hour in Rust to write a proper colored message on the screen. Huh. Production-grade is hard…

P.S.

I found that it does not respect TERM fully. I’ve tried vt100 and it still showing colors! Uh…

--

--

George Shuklin
journey to rust

I work at Servers.com, most of my stories are about Ansible, Ceph, Python, Openstack and Linux. My hobby is Rust.