tracing_subscriber_multi/
ascii_stripper.rs

1use std::io::Write;
2
3/// Strip ANSI control sequences.
4///
5/// Note, this only supports stripping CSI sequences and is not intended for
6/// purposes other than with a logger.
7pub struct AnsiStripper<W: Write> {
8    inner: W,
9    state: AnsiStateMachine,
10}
11
12enum AnsiStateMachine {
13    ScanningForESC,
14    ExpectingType,
15    WaitingForEnd,
16}
17
18impl<W: Write> AnsiStripper<W> {
19    /// Create a new writer that strips ANSI control sequences.
20    pub fn new(writer: W) -> Self {
21        Self {
22            inner: writer,
23            state: AnsiStateMachine::ScanningForESC,
24        }
25    }
26}
27
28impl<W: Write> Write for AnsiStripper<W> {
29    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
30        let mut cleaned_buffer = vec![];
31        for &c in buf {
32            match &mut self.state {
33                AnsiStateMachine::ScanningForESC => {
34                    if c == 0x1b {
35                        // ANSI sequence start
36                        self.state = AnsiStateMachine::ExpectingType;
37                    } else {
38                        // Regular data
39                        cleaned_buffer.push(c);
40                    }
41                }
42                AnsiStateMachine::ExpectingType => {
43                    if (0x40..=0x5f).contains(&c) {
44                        // Fe type
45                        self.state = AnsiStateMachine::WaitingForEnd;
46                    } else if (0x60..=0x7e).contains(&c) {
47                        // Fs type
48                        tracing::warn!("The writer was asked to strip unsupported ANSI sequences!");
49                    } else if (0x30..=0x3f).contains(&c) {
50                        // Fp type
51                        tracing::warn!("The writer was asked to strip unsupported ANSI sequences!");
52                    } else if (0x20..=0x2f).contains(&c) {
53                        // nF type
54                        tracing::warn!("The writer was asked to strip unsupported ANSI sequences!");
55                    }
56                }
57                AnsiStateMachine::WaitingForEnd => {
58                    if (0x40..=0x7e).contains(&c) {
59                        // Fe sequence, skip 1 char
60                        self.state = AnsiStateMachine::ScanningForESC;
61                    }
62                }
63            }
64        }
65
66        self.inner.write(&cleaned_buffer)
67    }
68
69    fn flush(&mut self) -> std::io::Result<()> {
70        self.inner.flush()
71    }
72}