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

feat(docs): add tracing example #1192

Merged
merged 2 commits into from
Jun 20, 2024
Merged
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
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ rand = "0.8.5"
rand_chacha = "0.3.1"
rstest = "0.21.0"
serde_json = "1.0.109"
tracing = "0.1.40"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

[lints.rust]
unsafe_code = "forbid"
Expand Down Expand Up @@ -353,6 +356,11 @@ name = "tabs"
required-features = ["crossterm"]
doc-scrape-examples = true

[[example]]
name = "tracing"
required-features = ["crossterm"]
doc-scrape-examples = true

[[example]]
name = "user_input"
required-features = ["crossterm"]
Expand Down
154 changes: 154 additions & 0 deletions examples/tracing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//! # [Ratatui] Tracing example
//!
//! The latest version of this example is available in the [examples] folder in the repository.
//!
//! Please note that the examples are designed to be run against the `main` branch of the Github
//! repository. This means that you may not be able to compile with the latest release version on
//! crates.io, or the one that you have installed locally.
//!
//! See the [examples readme] for more information on finding examples that match the version of the
//! library you are using.
//!
//! [Ratatui]: https://github.com/ratatui-org/ratatui
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md

// A simple example demonstrating how to use the [tracing] with Ratatui to log to a file.
//
// This example demonstrates how to use the [tracing] crate with Ratatui to log to a file. The
// example sets up a simple logger that logs to a file named `tracing.log` in the current directory.
//
// Run the example with `cargo run --example tracing` and then view the `tracing.log` file to see
// the logs. To see more logs, you can run the example with `RUST_LOG=tracing=debug cargo run
// --example`
//
// For a helpful widget that handles logging, see the [tui-logger] crate.
//
// [tracing]: https://crates.io/crates/tracing
// [tui-logger]: https://crates.io/crates/tui-logger

use std::{fs::File, io::stdout, panic, time::Duration};

use color_eyre::{
config::HookBuilder,
eyre::{self, Context},
Result,
};
use crossterm::{
event::{self, Event, KeyCode},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use ratatui::{
backend::{Backend, CrosstermBackend},
terminal::Terminal,
widgets::{Block, Paragraph},
};
use tracing::{debug, info, instrument, trace, Level};
use tracing_appender::{non_blocking, non_blocking::WorkerGuard};
use tracing_subscriber::EnvFilter;

fn main() -> Result<()> {
init_error_hooks()?;
let _guard = init_tracing()?;
info!("Starting tracing example");

let mut terminal = init_terminal()?;
let mut events = vec![]; // a buffer to store the recent events to display in the UI
while !should_exit(&events) {
handle_events(&mut events)?;
terminal.draw(|frame| ui(frame, &events))?;
}
restore_terminal()?;
info!("Exiting tracing example");
println!("See the tracing.log file for the logs");
Ok(())
}

fn should_exit(events: &[Event]) -> bool {
events
.iter()
.any(|event| matches!(event, Event::Key(key) if key.code == KeyCode::Char('q')))
}

/// Handle events and insert them into the events vector keeping only the last 10 events
#[instrument(skip(events))]
fn handle_events(events: &mut Vec<Event>) -> Result<()> {
// Render the UI at least once every 100ms
if event::poll(Duration::from_millis(100))? {
let event = event::read()?;
debug!(?event);
events.insert(0, event);
}
events.truncate(10);
Ok(())
}

#[instrument(skip_all)]
fn ui(frame: &mut ratatui::Frame, events: &[Event]) {
// To view this event, run the example with `RUST_LOG=tracing=debug cargo run --example tracing`
trace!(frame_count = frame.count(), event_count = events.len());
let area = frame.size();
let events = events.iter().map(|e| format!("{e:?}")).collect::<Vec<_>>();
let paragraph = Paragraph::new(events.join("\n"))
.block(Block::bordered().title("Tracing example. Press 'q' to quit."));
frame.render_widget(paragraph, area);
}

/// Initialize the tracing subscriber to log to a file
///
/// This function initializes the tracing subscriber to log to a file named `tracing.log` in the
/// current directory. The function returns a [`WorkerGuard`] that must be kept alive for the
/// duration of the program to ensure that logs are flushed to the file on shutdown. The logs are
/// written in a non-blocking fashion to ensure that the logs do not block the main thread.
fn init_tracing() -> Result<WorkerGuard> {
let file = File::create("tracing.log").wrap_err("failed to create tracing.log")?;
let (non_blocking, guard) = non_blocking(file);

// By default, the subscriber is configured to log all events with a level of `DEBUG` or higher,
// but this can be changed by setting the `RUST_LOG` environment variable.
let env_filter = EnvFilter::builder()
.with_default_directive(Level::DEBUG.into())
.from_env_lossy();

tracing_subscriber::fmt()
.with_writer(non_blocking)
.with_env_filter(env_filter)
.init();
Ok(guard)
}

/// Initialize the error hooks to ensure that the terminal is restored to a sane state before
/// exiting
fn init_error_hooks() -> Result<()> {
let (panic, error) = HookBuilder::default().into_hooks();
let panic = panic.into_panic_hook();
let error = error.into_eyre_hook();
eyre::set_hook(Box::new(move |e| {
let _ = restore_terminal();
error(e)
}))?;
panic::set_hook(Box::new(move |info| {
let _ = restore_terminal();
panic(info);
}));
Ok(())
}

#[instrument]
fn init_terminal() -> Result<Terminal<impl Backend>> {
enable_raw_mode()?;
stdout().execute(EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout());
let terminal = Terminal::new(backend)?;
debug!("terminal initialized");
Ok(terminal)
}

#[instrument]
fn restore_terminal() -> Result<()> {
disable_raw_mode()?;
stdout().execute(LeaveAlternateScreen)?;
debug!("terminal restored");
Ok(())
}
12 changes: 12 additions & 0 deletions examples/vhs/tracing.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# This is a vhs script. See https://github.com/charmbracelet/vhs for more info.
# To run this script, install vhs and run `vhs ./examples/barchart.tape`
Output "target/tracing.gif"
Set Theme "Aardvark Blue"
Set Width 1200
Set Height 800
Type "RUST_LOG=trace cargo run --example=tracing" Enter
Sleep 1s
Type @100ms "jjjjq"
Sleep 1s
Type "cat tracing.log" Enter
Sleep 10s