Skip to content

Commit 72bbf0f

Browse files
dmlaryhawkw
authored andcommitted
subscriber: support NO_COLOR in fmt::Layer (#2647)
It's necessary at times to be able to disable ANSI color output for rust utilities using `tracing`. The informal standard for this is the `NO_COLOR` environment variable described here: https://no-color.org/ Further details/discussion in #2388 This commit updates `fmt::Layer` to check the `NO_COLOR` environment variable when determining whether ANSI color output is enabled by default. As described in the spec, any non-empty value set for `NO_COLOR` will cause ANSI color support to be disabled by default. If the user manually overrides ANSI color support, such as by calling `with_ansi(true)`, this will still enable ANSI colors, even if `NO_COLOR` is set. The `NO_COLOR` env var only effects the default behavior. Fixes #2388
1 parent 3cde4fa commit 72bbf0f

File tree

1 file changed

+102
-6
lines changed

1 file changed

+102
-6
lines changed

tracing-subscriber/src/fmt/fmt_layer.rs

+102-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use crate::{
55
registry::{self, LookupSpan, SpanRef},
66
};
77
use format::{FmtSpan, TimingDisplay};
8-
use std::{any::TypeId, cell::RefCell, fmt, io, marker::PhantomData, ops::Deref, time::Instant};
8+
use std::{
9+
any::TypeId, cell::RefCell, env, fmt, io, marker::PhantomData, ops::Deref, time::Instant,
10+
};
911
use tracing_core::{
1012
field,
1113
span::{Attributes, Current, Id, Record},
@@ -276,6 +278,15 @@ impl<S, N, E, W> Layer<S, N, E, W> {
276278
/// Sets whether or not the formatter emits ANSI terminal escape codes
277279
/// for colors and other text formatting.
278280
///
281+
/// When the "ansi" crate feature flag is enabled, ANSI colors are enabled
282+
/// by default unless the [`NO_COLOR`] environment variable is set to
283+
/// a non-empty value. If the [`NO_COLOR`] environment variable is set to
284+
/// any non-empty value, then ANSI colors will be suppressed by default.
285+
/// The [`with_ansi`] and [`set_ansi`] methods can be used to forcibly
286+
/// enable ANSI colors, overriding any [`NO_COLOR`] environment variable.
287+
///
288+
/// [`NO_COLOR`]: https://no-color.org/
289+
///
279290
/// Enabling ANSI escapes (calling `with_ansi(true)`) requires the "ansi"
280291
/// crate feature flag. Calling `with_ansi(true)` without the "ansi"
281292
/// feature flag enabled will panic if debug assertions are enabled, or
@@ -288,6 +299,9 @@ impl<S, N, E, W> Layer<S, N, E, W> {
288299
/// ANSI escape codes can ensure that they are not used, regardless of
289300
/// whether or not other crates in the dependency graph enable the "ansi"
290301
/// feature flag.
302+
///
303+
/// [`with_ansi`]: Subscriber::with_ansi
304+
/// [`set_ansi`]: Subscriber::set_ansi
291305
pub fn with_ansi(self, ansi: bool) -> Self {
292306
#[cfg(not(feature = "ansi"))]
293307
if ansi {
@@ -311,10 +325,10 @@ impl<S, N, E, W> Layer<S, N, E, W> {
311325
/// By default, `fmt::Layer` will write any `FormatEvent`-internal errors to
312326
/// the writer. These errors are unlikely and will only occur if there is a
313327
/// bug in the `FormatEvent` implementation or its dependencies.
314-
///
328+
///
315329
/// If writing to the writer fails, the error message is printed to stderr
316330
/// as a fallback.
317-
///
331+
///
318332
/// [`FormatEvent`]: crate::fmt::FormatEvent
319333
pub fn log_internal_errors(self, log_internal_errors: bool) -> Self {
320334
Self {
@@ -677,12 +691,16 @@ impl<S, N, E, W> Layer<S, N, E, W> {
677691

678692
impl<S> Default for Layer<S> {
679693
fn default() -> Self {
694+
// only enable ANSI when the feature is enabled, and the NO_COLOR
695+
// environment variable is unset or empty.
696+
let ansi = cfg!(feature = "ansi") && env::var("NO_COLOR").map_or(true, |v| v.is_empty());
697+
680698
Layer {
681699
fmt_fields: format::DefaultFields::default(),
682700
fmt_event: format::Format::default(),
683701
fmt_span: format::FmtSpanConfig::default(),
684702
make_writer: io::stdout,
685-
is_ansi: cfg!(feature = "ansi"),
703+
is_ansi: ansi,
686704
log_internal_errors: false,
687705
_inner: PhantomData,
688706
}
@@ -1288,8 +1306,17 @@ mod test {
12881306
let actual = sanitize_timings(make_writer.get_string());
12891307

12901308
// Only assert the start because the line number and callsite may change.
1291-
let expected = concat!("Unable to format the following event. Name: event ", file!(), ":");
1292-
assert!(actual.as_str().starts_with(expected), "\nactual = {}\nshould start with expected = {}\n", actual, expected);
1309+
let expected = concat!(
1310+
"Unable to format the following event. Name: event ",
1311+
file!(),
1312+
":"
1313+
);
1314+
assert!(
1315+
actual.as_str().starts_with(expected),
1316+
"\nactual = {}\nshould start with expected = {}\n",
1317+
actual,
1318+
expected
1319+
);
12931320
}
12941321

12951322
#[test]
@@ -1491,4 +1518,73 @@ mod test {
14911518
actual.as_str()
14921519
);
14931520
}
1521+
1522+
// Because we need to modify an environment variable for these test cases,
1523+
// we do them all in a single test.
1524+
#[cfg(feature = "ansi")]
1525+
#[test]
1526+
fn layer_no_color() {
1527+
const NO_COLOR: &str = "NO_COLOR";
1528+
1529+
// Restores the previous value of the `NO_COLOR` env variable when
1530+
// dropped.
1531+
//
1532+
// This is done in a `Drop` implementation, rather than just resetting
1533+
// the value at the end of the test, so that the previous value is
1534+
// restored even if the test panics.
1535+
struct RestoreEnvVar(Result<String, env::VarError>);
1536+
impl Drop for RestoreEnvVar {
1537+
fn drop(&mut self) {
1538+
match self.0 {
1539+
Ok(ref var) => env::set_var(NO_COLOR, var),
1540+
Err(_) => env::remove_var(NO_COLOR),
1541+
}
1542+
}
1543+
}
1544+
1545+
let _saved_no_color = RestoreEnvVar(env::var(NO_COLOR));
1546+
1547+
let cases: Vec<(Option<&str>, bool)> = vec![
1548+
(Some("0"), false), // any non-empty value disables ansi
1549+
(Some("off"), false), // any non-empty value disables ansi
1550+
(Some("1"), false),
1551+
(Some(""), true), // empty value does not disable ansi
1552+
(None, true),
1553+
];
1554+
1555+
for (var, ansi) in cases {
1556+
if let Some(value) = var {
1557+
env::set_var(NO_COLOR, value);
1558+
} else {
1559+
env::remove_var(NO_COLOR);
1560+
}
1561+
1562+
let layer: Layer<()> = fmt::Layer::default();
1563+
assert_eq!(
1564+
layer.is_ansi, ansi,
1565+
"NO_COLOR={:?}; Layer::default().is_ansi should be {}",
1566+
var, ansi
1567+
);
1568+
1569+
// with_ansi should override any `NO_COLOR` value
1570+
let layer: Layer<()> = fmt::Layer::default().with_ansi(true);
1571+
assert!(
1572+
layer.is_ansi,
1573+
"NO_COLOR={:?}; Layer::default().with_ansi(true).is_ansi should be true",
1574+
var
1575+
);
1576+
1577+
// set_ansi should override any `NO_COLOR` value
1578+
let mut layer: Layer<()> = fmt::Layer::default();
1579+
layer.set_ansi(true);
1580+
assert!(
1581+
layer.is_ansi,
1582+
"NO_COLOR={:?}; layer.set_ansi(true); layer.is_ansi should be true",
1583+
var
1584+
);
1585+
}
1586+
1587+
// dropping `_saved_no_color` will restore the previous value of
1588+
// `NO_COLOR`.
1589+
}
14941590
}

0 commit comments

Comments
 (0)