virtual_console_lib/
log.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::terminal::Terminal;
6use anyhow::Error;
7use fuchsia_async::{self as fasync, OnSignals};
8use std::io::sink;
9use term_model::ansi::Processor;
10use term_model::event::EventListener;
11
12pub trait LogClient: 'static + Clone {
13    type Listener;
14
15    fn create_terminal(&self, id: u32, title: String) -> Result<Terminal<Self::Listener>, Error>;
16    fn request_update(&self, id: u32);
17}
18
19pub struct Log;
20
21impl Log {
22    pub fn start<T: LogClient>(
23        read_only_debuglog: zx::DebugLog,
24        client: &T,
25        id: u32,
26    ) -> Result<(), Error>
27    where
28        <T as LogClient>::Listener: EventListener,
29    {
30        let client = client.clone();
31        let terminal =
32            client.create_terminal(id, "debuglog".to_string()).expect("failed to create terminal");
33        let term = terminal.clone_term();
34
35        // Get our process koid so we can filter out our own debug messages from the log.
36        let proc_koid =
37            fuchsia_runtime::process_self().koid().expect("failed to get koid for process");
38
39        fasync::Task::local(async move {
40            let mut sink = sink();
41            let mut parser = Processor::new();
42
43            loop {
44                let on_signal = OnSignals::new(&read_only_debuglog, zx::Signals::LOG_READABLE);
45                on_signal.await.expect("failed to wait for log readable");
46
47                loop {
48                    match read_only_debuglog.read() {
49                        Ok(record) => {
50                            // Don't print log messages from ourself.
51                            if record.pid == proc_koid {
52                                continue;
53                            }
54
55                            let mut term = term.borrow_mut();
56
57                            // Write prefix with time stamps and ids.
58                            let prefix = format!(
59                                "\u{001b}[32m{:05}.{:03}\u{001b}[39m] \u{001b}[31m{:05}.\u{001b}[36m{:05}\u{001b}[39m> ",
60                                record.timestamp.into_nanos() / 1_000_000_000,
61                                (record.timestamp.into_nanos() / 1_000_000) % 1_000,
62                                record.pid.raw_koid(),
63                                record.tid.raw_koid(),
64                            );
65                            for byte in prefix.as_bytes() {
66                                parser.advance(&mut *term, *byte, &mut sink);
67                            }
68
69                            // Ignore any trailing newline character.
70                            let mut record_data = record.data();
71                            if record_data.last() == Some(&b'\n') {
72                                record_data = &record_data[..record_data.len() - 1];
73                            }
74
75                            // Write record data.
76                            for byte in record_data.iter() {
77                                parser.advance(&mut *term, *byte, &mut sink);
78                            }
79
80                            // Write carriage return and newline.
81                            for byte in "\r\n".as_bytes() {
82                                parser.advance(&mut *term, *byte, &mut sink);
83                            }
84
85                            // Request terminal update.
86                            client.request_update(id);
87                        }
88                        Err(status) if status == zx::Status::SHOULD_WAIT => {
89                            break;
90                        }
91                        Err(_) => {
92                            let mut term = term.borrow_mut();
93                            for byte in "\r\n<<LOG ERROR>>".as_bytes() {
94                                parser.advance(&mut *term, *byte, &mut sink);
95                            }
96
97                            // Request terminal update.
98                            client.request_update(id);
99                            break;
100                        }
101                    }
102                }
103            }
104        })
105        .detach();
106
107        Ok(())
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use crate::colors::ColorScheme;
115    use fuchsia_async as fasync;
116    use term_model::event::Event;
117
118    #[derive(Default)]
119    struct TestListener;
120
121    impl EventListener for TestListener {
122        fn send_event(&self, _event: Event) {}
123    }
124
125    #[derive(Default, Clone)]
126    struct TestLogClient;
127
128    impl LogClient for TestLogClient {
129        type Listener = TestListener;
130
131        fn create_terminal(
132            &self,
133            _id: u32,
134            title: String,
135        ) -> Result<Terminal<Self::Listener>, Error> {
136            Ok(Terminal::new(TestListener::default(), title, ColorScheme::default(), 1024, None))
137        }
138        fn request_update(&self, _id: u32) {}
139    }
140
141    #[fasync::run_singlethreaded(test)]
142    async fn can_start_log() -> Result<(), Error> {
143        let resource = zx::Resource::from(zx::NullableHandle::invalid());
144        let debuglog = zx::DebugLog::create(&resource, zx::DebugLogOpts::empty()).unwrap();
145        let client = TestLogClient::default();
146        let _ = Log::start(debuglog, &client, 0)?;
147        Ok(())
148    }
149}