Capture `tracing` logs; stub/placeholder tui
This commit is contained in:
parent
e214d9dce3
commit
76d8d9e907
|
@ -97,6 +97,12 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cassowary"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cast"
|
name = "cast"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
|
@ -931,6 +937,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numtoa"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -1252,6 +1264,15 @@ dependencies = [
|
||||||
"bitflags",
|
"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]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.4"
|
version = "1.5.4"
|
||||||
|
@ -1551,16 +1572,19 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"notify",
|
"notify",
|
||||||
|
"parking_lot",
|
||||||
"preserves-schema",
|
"preserves-schema",
|
||||||
"structopt",
|
"structopt",
|
||||||
"syndicate",
|
"syndicate",
|
||||||
"syndicate-macros",
|
"syndicate-macros",
|
||||||
|
"termion",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"tui",
|
||||||
"tungstenite",
|
"tungstenite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1578,6 +1602,18 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"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]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -1793,6 +1829,19 @@ dependencies = [
|
||||||
"tracing-serde",
|
"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]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
|
|
@ -22,6 +22,7 @@ chrono = "0.4"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
notify = "4.0"
|
notify = "4.0"
|
||||||
|
parking_lot = "0.11"
|
||||||
structopt = "0.3"
|
structopt = "0.3"
|
||||||
|
|
||||||
tungstenite = "0.13"
|
tungstenite = "0.13"
|
||||||
|
@ -33,3 +34,6 @@ tokio-util = "0.6"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.2"
|
tracing-subscriber = "0.2"
|
||||||
tracing-futures = "0.2"
|
tracing-futures = "0.2"
|
||||||
|
|
||||||
|
tui = "0.16"
|
||||||
|
termion = "1.5"
|
||||||
|
|
|
@ -21,6 +21,7 @@ mod language;
|
||||||
mod lifecycle;
|
mod lifecycle;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod services;
|
mod services;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
mod schemas {
|
mod schemas {
|
||||||
include!(concat!(env!("OUT_DIR"), "/src/schemas/mod.rs"));
|
include!(concat!(env!("OUT_DIR"), "/src/schemas/mod.rs"));
|
||||||
|
@ -46,6 +47,9 @@ struct ServerConfig {
|
||||||
#[structopt(short = "c", long)]
|
#[structopt(short = "c", long)]
|
||||||
config: Vec<PathBuf>,
|
config: Vec<PathBuf>,
|
||||||
|
|
||||||
|
#[structopt(short = "u", long)]
|
||||||
|
ui: bool,
|
||||||
|
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
no_banner: bool,
|
no_banner: bool,
|
||||||
}
|
}
|
||||||
|
@ -54,7 +58,11 @@ struct ServerConfig {
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let config = Arc::new(ServerConfig::from_args());
|
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 {
|
if !config.no_banner && !config.inferior {
|
||||||
const BRIGHT_GREEN: &str = "\x1b[92m";
|
const BRIGHT_GREEN: &str = "\x1b[92m";
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
Loading…
Reference in New Issue