blob: c636b293c6b93184212e8601351605533d3b6279 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::terminal::Terminal,
anyhow::Error,
fuchsia_async::{self as fasync, OnSignals},
fuchsia_zircon::{self as zx, AsHandleRef},
std::io::sink,
term_model::{ansi::Processor, event::EventListener},
};
pub trait LogClient: 'static + Clone {
type Listener;
fn create_terminal(&self, id: u32, title: String) -> Result<Terminal<Self::Listener>, Error>;
fn request_update(&self, id: u32);
}
pub struct Log;
impl Log {
pub fn start<T: LogClient>(
read_only_debuglog: zx::DebugLog,
client: &T,
id: u32,
) -> Result<(), Error>
where
<T as LogClient>::Listener: EventListener,
{
let client = client.clone();
let terminal =
client.create_terminal(id, "debuglog".to_string()).expect("failed to create terminal");
let term = terminal.clone_term();
// Get our process koid so we can filter out our own debug messages from the log.
let proc_koid =
fuchsia_runtime::process_self().get_koid().expect("failed to get koid for process");
fasync::Task::local(async move {
let mut sink = sink();
let mut parser = Processor::new();
loop {
let on_signal = OnSignals::new(&read_only_debuglog, zx::Signals::LOG_READABLE);
on_signal.await.expect("failed to wait for log readable");
loop {
match read_only_debuglog.read() {
Ok(record) => {
// Don't print log messages from ourself.
if record.pid == proc_koid.raw_koid() {
continue;
}
let mut term = term.borrow_mut();
// Write prefix with time stamps and ids.
let prefix = format!(
"\u{001b}[32m{:05}.{:03}\u{001b}[39m] \u{001b}[31m{:05}.\u{001b}[36m{:05}\u{001b}[39m> ",
record.timestamp / 1_000_000_000,
(record.timestamp / 1_000_000) % 1_000,
record.pid,
record.tid,
);
for byte in prefix.as_bytes() {
parser.advance(&mut *term, *byte, &mut sink);
}
// Ignore any trailing newline character.
let mut datalen = record.datalen as usize;
if datalen > 0 && record.data[datalen - 1] == '\n' as u8 {
datalen -= 1;
}
// Write record data.
for byte in &record.data[0..datalen] {
parser.advance(&mut *term, *byte, &mut sink);
}
// Write carriage return and newline.
for byte in "\r\n".as_bytes() {
parser.advance(&mut *term, *byte, &mut sink);
}
// Request terminal update.
client.request_update(id);
}
Err(status) if status == zx::Status::SHOULD_WAIT => {
break;
}
Err(_) => {
let mut term = term.borrow_mut();
for byte in "\r\n<<LOG ERROR>>".as_bytes() {
parser.advance(&mut *term, *byte, &mut sink);
}
// Request terminal update.
client.request_update(id);
break;
}
}
}
}
})
.detach();
Ok(())
}
}
#[cfg(test)]
mod tests {
use {super::*, crate::colors::ColorScheme, fuchsia_async as fasync, term_model::event::Event};
#[derive(Default)]
struct TestListener;
impl EventListener for TestListener {
fn send_event(&self, _event: Event) {}
}
#[derive(Default, Clone)]
struct TestLogClient;
impl LogClient for TestLogClient {
type Listener = TestListener;
fn create_terminal(
&self,
_id: u32,
title: String,
) -> Result<Terminal<Self::Listener>, Error> {
Ok(Terminal::new(TestListener::default(), title, ColorScheme::default(), 1024, None))
}
fn request_update(&self, _id: u32) {}
}
#[fasync::run_singlethreaded(test)]
async fn can_start_log() -> Result<(), Error> {
let resource = zx::Resource::from(zx::Handle::invalid());
let debuglog = zx::DebugLog::create(&resource, zx::DebugLogOpts::empty()).unwrap();
let client = TestLogClient::default();
let _ = Log::start(debuglog, &client, 0)?;
Ok(())
}
}