Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add env-filter-explorer example #3233

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,22 @@ default = []
[dev-dependencies]

# tracing crates
tracing = { path = "../tracing", version = "0.2"}
tracing-core = { path = "../tracing-core", version = "0.2"}
tracing = { path = "../tracing", version = "0.2" }
tracing-core = { path = "../tracing-core", version = "0.2" }
tracing-error = { path = "../tracing-error" }
tracing-flame = { path = "../tracing-flame" }
tracing-tower = { version = "0.1.0", path = "../tracing-tower" }
tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", features = ["json", "env-filter"] }
tracing-futures = { version = "0.3", path = "../tracing-futures", features = ["futures-01"] }
tracing-attributes = { path = "../tracing-attributes", version = "0.2"}
tracing-log = { path = "../tracing-log", version = "0.2", features = ["env_logger"] }
tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", features = [
"json",
"env-filter",
] }
tracing-futures = { version = "0.3", path = "../tracing-futures", features = [
"futures-01",
] }
tracing-attributes = { path = "../tracing-attributes", version = "0.2" }
tracing-log = { path = "../tracing-log", version = "0.2", features = [
"env_logger",
] }
tracing-serde = { path = "../tracing-serde" }
tracing-appender = { path = "../tracing-appender" }
tracing-journald = { path = "../tracing-journald" }
Expand All @@ -44,12 +51,18 @@ humantime = "2.1.0"
log = "0.4.17"

# inferno example
inferno = "0.11.6"
inferno = "0.12.1"
tempfile = "3.3.0"

# fmt examples
snafu = "0.6.10"
thiserror = "1.0.31"

# env-filter-explorer example
ansi-to-tui = "7.0.0"
ratatui = "0.29.0"
crossterm = "0.28.1"
tui-textarea = "0.7.0"

[lints]
workspace = true
180 changes: 180 additions & 0 deletions examples/examples/env-filter-explorer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use std::{
fmt,
io::{self},
sync::{Arc, Mutex},
};

use ansi_to_tui::IntoText;
use crossterm::event;
use ratatui::{
layout::{Constraint, Layout},
text::Text,
widgets::Block,
DefaultTerminal, Frame,
};
use tracing_subscriber::fmt::MakeWriter;
use tui_textarea::{Input, Key, TextArea};

fn main() -> io::Result<()> {
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}

const PRESET_FILTERS: &[&str] = &[
"trace",
"debug",
"info",
"warn",
"error",
"[with_fields]",
"[with_fields{foo}]",
"[with_fields{bar}]",
"[with_fields{foo=42}]",
"[with_fields{bar=bar}]",
"[with_fields{foo=99}]",
"[with_fields{bar=nope}]",
"[with_fields{nonexistent}]",
"other_crate=info",
"other_crate=debug",
"trace,other_crate=warn",
"warn,other_crate=info",
];

fn run(mut terminal: DefaultTerminal) -> io::Result<()> {
let mut textarea = TextArea::new(vec!["trace".to_string()]);
let title = "Env Filter Explorer. <Esc> to quit, <Up>/<Down> to select preset";
textarea.set_block(Block::bordered().title(title));
let mut preset_index: usize = 0;
loop {
terminal.draw(|frame| render(frame, &textarea))?;
match event::read()?.into() {
Input {
key: Key::Enter, ..
} => {}
Input { key: Key::Esc, .. } => break Ok(()),
Input { key: Key::Up, .. } => reset_preset(&mut textarea, &mut preset_index, -1),
Input { key: Key::Down, .. } => reset_preset(&mut textarea, &mut preset_index, 1),
input => {
textarea.input(input);
}
}
}
}

fn reset_preset(textarea: &mut TextArea<'_>, preset_index: &mut usize, offset: isize) {
*preset_index = preset_index
.saturating_add_signed(offset)
.min(PRESET_FILTERS.len() - 1);
let input = PRESET_FILTERS[*preset_index];
textarea.select_all();
textarea.delete_line_by_head();
textarea.insert_str(input);
}

fn render(frame: &mut Frame, textarea: &TextArea) {
let layout = Layout::vertical([Constraint::Length(3), Constraint::Fill(1)]);
let [top, body] = layout.areas(frame.area());
frame.render_widget(textarea, top);
let filter = textarea.lines()[0].to_string();

let Ok(env_filter) = tracing_subscriber::EnvFilter::builder().parse(filter) else {
let text = Text::from("Error parsing filter");
frame.render_widget(text, body);
return;
};
let writer = StringWriter::default();
let collector = tracing_subscriber::fmt()
.with_env_filter(env_filter)
.with_writer(writer.clone())
.finish();

tracing::collect::with_default(collector, simulate_logging);
let output = writer.to_string();
let text = output
.into_text()
.unwrap_or(Text::from("Error parsing output"));
frame.render_widget(text, body);
}

#[tracing::instrument]
fn simulate_logging() {
tracing::info!("This is an info message");
tracing::error!("This is an error message");
tracing::warn!("This is a warning message");
tracing::debug!("This is a debug message");
tracing::trace!("This is a trace message");

other_crate();
trace_span();
with_fields(42, "bar");
with_fields(99, "nope");
}

#[tracing::instrument(target = "other_crate")]
fn other_crate() {
tracing::error!(
target: "other_crate",
"This is an error message from another crate"
);
tracing::warn!(
target: "other_crate",
"This is a warning message from another crate"
);
tracing::info!(
target: "other_crate",
"This is an info message from another crate"
);
tracing::debug!(
target: "other_crate",
"This is a debug message from another crate"
);
tracing::trace!(
target: "other_crate",
"This is a trace message from another crate"
);
}

#[tracing::instrument]
fn with_fields(foo: u32, bar: &'static str) {
tracing::info!(foo, bar, "This is an info message with fields");
}

#[tracing::instrument(level = "trace")]
fn trace_span() {
tracing::error!("Error message inside a span with trace level");
tracing::info!("Info message inside a span with trace level");
tracing::trace!("Trace message inside a span with trace level");
}

#[derive(Clone, Default, Debug)]
struct StringWriter {
buffer: Arc<Mutex<Vec<u8>>>,
}

impl fmt::Display for StringWriter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let buffer = self.buffer.lock().unwrap();
let string = String::from_utf8_lossy(&buffer);
write!(f, "{}", string)
}
}

impl io::Write for StringWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buffer.lock().unwrap().write(buf)
}

fn flush(&mut self) -> io::Result<()> {
self.buffer.lock().unwrap().flush()
}
}

impl<'a> MakeWriter<'a> for StringWriter {
type Writer = Self;

fn make_writer(&'a self) -> Self::Writer {
self.clone()
}
}
Loading