Capture `tracing` logs; stub/placeholder tui

This commit is contained in:
Tony Garnock-Jones 2021-10-05 12:42:09 +02:00
parent e214d9dce3
commit 76d8d9e907
4 changed files with 267 additions and 1 deletions

49
Cargo.lock generated
View File

@ -97,6 +97,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "cast"
version = "0.2.7"
@ -931,6 +937,12 @@ dependencies = [
"libc",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "once_cell"
version = "1.8.0"
@ -1252,6 +1264,15 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.5.4"
@ -1551,16 +1572,19 @@ dependencies = [
"futures",
"lazy_static",
"notify",
"parking_lot",
"preserves-schema",
"structopt",
"syndicate",
"syndicate-macros",
"termion",
"tokio",
"tokio-tungstenite",
"tokio-util",
"tracing",
"tracing-futures",
"tracing-subscriber",
"tui",
"tungstenite",
]
@ -1578,6 +1602,18 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "termion"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
]
[[package]]
name = "textwrap"
version = "0.11.0"
@ -1793,6 +1829,19 @@ dependencies = [
"tracing-serde",
]
[[package]]
name = "tui"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23"
dependencies = [
"bitflags",
"cassowary",
"termion",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "tungstenite"
version = "0.13.0"

View File

@ -22,6 +22,7 @@ chrono = "0.4"
futures = "0.3"
lazy_static = "1.4"
notify = "4.0"
parking_lot = "0.11"
structopt = "0.3"
tungstenite = "0.13"
@ -33,3 +34,6 @@ tokio-util = "0.6"
tracing = "0.1"
tracing-subscriber = "0.2"
tracing-futures = "0.2"
tui = "0.16"
termion = "1.5"

View File

@ -21,6 +21,7 @@ mod language;
mod lifecycle;
mod protocol;
mod services;
mod ui;
mod schemas {
include!(concat!(env!("OUT_DIR"), "/src/schemas/mod.rs"));
@ -46,6 +47,9 @@ struct ServerConfig {
#[structopt(short = "c", long)]
config: Vec<PathBuf>,
#[structopt(short = "u", long)]
ui: bool,
#[structopt(long)]
no_banner: bool,
}
@ -54,7 +58,11 @@ struct ServerConfig {
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Arc::new(ServerConfig::from_args());
syndicate::convenient_logging()?;
if config.ui {
ui::start()?;
} else {
syndicate::convenient_logging()?;
}
if !config.no_banner && !config.inferior {
const BRIGHT_GREEN: &str = "\x1b[92m";

View File

@ -0,0 +1,205 @@
use parking_lot::RwLock;
use std::collections::VecDeque;
use std::io;
use std::sync::atomic::{AtomicBool, Ordering};
use syndicate::value::IOValue;
use syndicate::value::Map;
use syndicate::value::NestedValue;
use syndicate::value::Value;
use termion::raw::IntoRawMode;
use tracing::Event;
use tracing::Level;
use tracing::Metadata;
use tracing::field::Field;
use tracing::span::Attributes;
use tracing::span::Id;
use tracing::span::Record;
use tracing_subscriber::prelude::*;
use tracing_subscriber::layer::Context;
use tui::Terminal;
use tui::backend::TermionBackend;
#[derive(Debug, Clone, Default)]
struct FieldsSnapshot(Map<&'static str, IOValue>);
#[derive(Debug, Clone)]
struct EventSnapshot {
spans: Vec<(&'static Metadata<'static>, FieldsSnapshot)>,
event_metadata: &'static Metadata<'static>,
event_fields: FieldsSnapshot,
}
struct LogCollector {
dirty: AtomicBool,
current_level: Level,
minimum_level: Level,
history: RwLock<VecDeque<EventSnapshot>>,
history_limit: usize,
}
fn write_fields(f: &mut std::fmt::Formatter, fs: &Map<&'static str, IOValue>) -> std::fmt::Result {
for (k, v) in fs.iter() {
if let Some(s) = v.value().as_string() {
write!(f, "{}={} ", k, s)?;
} else {
write!(f, "{}={:?} ", k, v)?;
}
}
Ok(())
}
impl std::fmt::Display for EventSnapshot {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut fs = self.event_fields.0.clone();
if let Some(m) = fs.remove("message") {
match m.value().as_string() {
Some(s) => write!(f, "{} ", s)?,
None => { fs.insert("message", m); }
}
}
write_fields(f, &fs)?;
write!(f, "at {}:{}\n",
self.event_metadata.file().unwrap_or("-"),
self.event_metadata.line().unwrap_or(0))?;
for s in self.spans.iter() {
let (m, fs) = s;
write!(f, " - in {} ", m.name())?;
write_fields(f, &fs.0)?;
write!(f, "at {}:{}\n", m.file().unwrap_or("-"), m.line().unwrap_or(0))?;
}
Ok(())
}
}
impl FieldsSnapshot {
fn push(&mut self, field: &Field, v: IOValue) {
let name = field.name();
match self.0.remove(name) {
None => {
self.0.insert(name, v);
}
Some(o) => match o.value().as_sequence() {
Some(vs) => {
let mut vs = vs.clone();
vs.push(v);
self.0.insert(name, IOValue::new(vs));
},
None => {
self.0.insert(name, IOValue::new(vec![o, v]));
}
}
}
}
}
impl tracing::field::Visit for FieldsSnapshot {
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
self.push(field, IOValue::new(format!("{:?}", value)));
}
fn record_f64(&mut self, field: &Field, value: f64) {
self.push(field, IOValue::new(value));
}
fn record_i64(&mut self, field: &Field, value: i64) {
self.push(field, IOValue::new(value));
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.push(field, IOValue::new(value));
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.push(field, IOValue::new(value));
}
fn record_str(&mut self, field: &Field, value: &str) {
self.push(field, IOValue::new(value));
}
fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
let mut r = Value::simple_record("error", 1);
r.fields_vec_mut().push(IOValue::new(format!("{}", value)));
let r = r.finish();
self.push(field, r.wrap());
}
}
impl LogCollector {
fn new() -> Self {
LogCollector {
dirty: AtomicBool::new(false),
current_level: Level::INFO,
minimum_level: Level::TRACE,
history: Default::default(),
history_limit: 10000,
}
}
}
impl<S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a> + std::fmt::Debug>
tracing_subscriber::Layer<S> for LogCollector
where for<'a> <S as tracing_subscriber::registry::LookupSpan<'a>>::Data: std::fmt::Debug
{
fn enabled(&self, metadata: &Metadata, _ctx: Context<S>) -> bool {
metadata.level() <= &self.minimum_level
}
fn new_span(&self, attrs: &Attributes, id: &Id, ctx: Context<S>) {
if let Some(s) = ctx.span(id) {
let mut snap = FieldsSnapshot::default();
attrs.record(&mut snap);
s.extensions_mut().insert(snap);
}
}
fn on_record(&self, span: &Id, values: &Record, ctx: Context<S>) {
if let Some(s) = ctx.span(span) {
if let Some(snap) = s.extensions_mut().get_mut::<FieldsSnapshot>() {
values.record(snap);
}
}
}
fn on_event(&self, event: &Event, ctx: Context<S>) {
if event.metadata().level() > &self.current_level {
return;
}
let mut snap = EventSnapshot {
spans: Vec::new(),
event_metadata: event.metadata(),
event_fields: Default::default(),
};
event.record(&mut snap.event_fields);
if let Some(scope) = ctx.event_scope(event) {
for s in scope.from_root() {
snap.spans.push((
s.metadata(),
s.extensions().get::<FieldsSnapshot>().cloned().unwrap_or_else(FieldsSnapshot::default)
));
}
}
println!("{}", &snap);
let mut history = self.history.write();
history.push_front(snap);
history.truncate(self.history_limit);
self.dirty.store(true, Ordering::Relaxed);
}
}
pub fn start() -> io::Result<()> {
let stdout = io::stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let subscriber = tracing_subscriber::Registry::default()
.with(LogCollector::new());
tracing::subscriber::set_global_default(subscriber)
.expect("Could not set UI global subscriber");
Ok(())
}