Skip to main content

tracing_systemd/format/
color.rs

1//! Color handling: [`ColorMode`] (when to apply ANSI) and [`ColorTheme`]
2//! (which styles to use per element).
3
4#![cfg(feature = "colors")]
5
6use nu_ansi_term::{Color, Style};
7
8/// When to emit ANSI color escapes on stdout output.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10#[cfg_attr(docsrs, doc(cfg(feature = "colors")))]
11pub enum ColorMode {
12    /// Emit colors only when the output is a TTY *and* the `NO_COLOR`
13    /// environment variable is not set.
14    #[default]
15    Auto,
16    /// Always emit colors, regardless of TTY status or `NO_COLOR`.
17    Always,
18    /// Never emit colors.
19    Never,
20}
21
22impl ColorMode {
23    /// Resolve the mode given runtime conditions. Pure function for testability;
24    /// the layer evaluates `is_terminal` and `no_color_set` at construction time.
25    pub(crate) fn resolve(self, is_terminal: bool, no_color_set: bool) -> bool {
26        match self {
27            Self::Always => true,
28            Self::Never => false,
29            Self::Auto => is_terminal && !no_color_set,
30        }
31    }
32
33    /// Resolve using the current process's `NO_COLOR` env var.
34    pub(crate) fn resolve_now(self, is_terminal: bool) -> bool {
35        self.resolve(is_terminal, std::env::var_os("NO_COLOR").is_some())
36    }
37}
38
39/// Per-element ANSI styles applied to formatted output.
40///
41/// Construct via [`ColorTheme::default`] (matches the 0.1 palette) or
42/// [`ColorTheme::monochrome`] (no styling), then override individual
43/// fields as needed.
44///
45/// ```
46/// use tracing_systemd::ColorTheme;
47/// use nu_ansi_term::{Color, Style};
48///
49/// let theme = ColorTheme {
50///     info: Style::new().fg(Color::Cyan).bold(),
51///     ..ColorTheme::default()
52/// };
53/// # let _ = theme;
54/// ```
55#[derive(Debug, Clone, Copy)]
56#[cfg_attr(docsrs, doc(cfg(feature = "colors")))]
57#[allow(missing_docs)] // Field names are self-documenting.
58pub struct ColorTheme {
59    pub trace: Style,
60    pub debug: Style,
61    pub info: Style,
62    pub warn: Style,
63    pub error: Style,
64    pub span_name: Style,
65    pub field_name: Style,
66    pub field_value: Style,
67    pub target: Style,
68    pub thread_id: Style,
69}
70
71impl ColorTheme {
72    /// All-empty theme. Useful for users who want structure without ANSI
73    /// (e.g. when piping to a log file but still wanting the runtime
74    /// option of [`ColorMode::Always`]).
75    #[must_use]
76    pub fn monochrome() -> Self {
77        Self {
78            trace: Style::new(),
79            debug: Style::new(),
80            info: Style::new(),
81            warn: Style::new(),
82            error: Style::new(),
83            span_name: Style::new(),
84            field_name: Style::new(),
85            field_value: Style::new(),
86            target: Style::new(),
87            thread_id: Style::new(),
88        }
89    }
90
91    /// Style for a given level. Used internally by the formatter.
92    pub(crate) fn level_style(&self, level: tracing::Level) -> Style {
93        match level {
94            tracing::Level::TRACE => self.trace,
95            tracing::Level::DEBUG => self.debug,
96            tracing::Level::INFO => self.info,
97            tracing::Level::WARN => self.warn,
98            tracing::Level::ERROR => self.error,
99        }
100    }
101}
102
103impl Default for ColorTheme {
104    /// Matches the color palette used by `tracing-systemd` 0.1.
105    fn default() -> Self {
106        Self {
107            trace: Style::new().fg(Color::Magenta),
108            debug: Style::new().fg(Color::Blue),
109            info: Style::new().fg(Color::Green),
110            warn: Style::new().fg(Color::Yellow),
111            error: Style::new().fg(Color::Red),
112            span_name: Style::new(),
113            field_name: Style::new(),
114            field_value: Style::new(),
115            target: Style::new(),
116            thread_id: Style::new(),
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn auto_resolves_against_tty_and_no_color() {
127        assert!(ColorMode::Auto.resolve(true, false));
128        assert!(!ColorMode::Auto.resolve(true, true));
129        assert!(!ColorMode::Auto.resolve(false, false));
130        assert!(!ColorMode::Auto.resolve(false, true));
131    }
132
133    #[test]
134    fn always_overrides_tty_and_env() {
135        assert!(ColorMode::Always.resolve(false, true));
136        assert!(ColorMode::Always.resolve(true, true));
137    }
138
139    #[test]
140    fn never_overrides_tty_and_env() {
141        assert!(!ColorMode::Never.resolve(true, false));
142    }
143
144    #[test]
145    fn level_style_picks_correct_style() {
146        let theme = ColorTheme::default();
147        assert_eq!(theme.level_style(tracing::Level::INFO), theme.info);
148        assert_eq!(theme.level_style(tracing::Level::ERROR), theme.error);
149    }
150
151    #[test]
152    fn monochrome_has_empty_styles() {
153        let theme = ColorTheme::monochrome();
154        let plain = Style::new();
155        assert_eq!(theme.info, plain);
156        assert_eq!(theme.error, plain);
157        assert_eq!(theme.span_name, plain);
158    }
159}