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