|
| 1 | +//! # [Ratatui] Tracing example |
| 2 | +//! |
| 3 | +//! The latest version of this example is available in the [examples] folder in the repository. |
| 4 | +//! |
| 5 | +//! Please note that the examples are designed to be run against the `main` branch of the Github |
| 6 | +//! repository. This means that you may not be able to compile with the latest release version on |
| 7 | +//! crates.io, or the one that you have installed locally. |
| 8 | +//! |
| 9 | +//! See the [examples readme] for more information on finding examples that match the version of the |
| 10 | +//! library you are using. |
| 11 | +//! |
| 12 | +//! [Ratatui]: https://github.com/ratatui-org/ratatui |
| 13 | +//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples |
| 14 | +//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md |
| 15 | +
|
| 16 | +// A simple example demonstrating how to use the [tracing] with Ratatui to log to a file. |
| 17 | +// |
| 18 | +// This example demonstrates how to use the [tracing] crate with Ratatui to log to a file. The |
| 19 | +// example sets up a simple logger that logs to a file named `tracing.log` in the current directory. |
| 20 | +// |
| 21 | +// Run the example with `cargo run --example tracing` and then view the `tracing.log` file to see |
| 22 | +// the logs. To see more logs, you can run the example with `RUST_LOG=tracing=debug cargo run |
| 23 | +// --example` |
| 24 | +// |
| 25 | +// For a helpful widget that handles logging, see the [tui-logger] crate. |
| 26 | +// |
| 27 | +// [tracing]: https://crates.io/crates/tracing |
| 28 | +// [tui-logger]: https://crates.io/crates/tui-logger |
| 29 | + |
| 30 | +use std::{fs::File, io::stdout, panic, time::Duration}; |
| 31 | + |
| 32 | +use color_eyre::{ |
| 33 | + config::HookBuilder, |
| 34 | + eyre::{self, Context}, |
| 35 | + Result, |
| 36 | +}; |
| 37 | +use crossterm::{ |
| 38 | + event::{self, Event, KeyCode}, |
| 39 | + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, |
| 40 | + ExecutableCommand, |
| 41 | +}; |
| 42 | +use ratatui::{ |
| 43 | + backend::{Backend, CrosstermBackend}, |
| 44 | + terminal::Terminal, |
| 45 | + widgets::{Block, Paragraph}, |
| 46 | +}; |
| 47 | +use tracing::{debug, info, instrument, trace, Level}; |
| 48 | +use tracing_appender::{non_blocking, non_blocking::WorkerGuard}; |
| 49 | +use tracing_subscriber::EnvFilter; |
| 50 | + |
| 51 | +fn main() -> Result<()> { |
| 52 | + init_error_hooks()?; |
| 53 | + let _guard = init_tracing()?; |
| 54 | + info!("Starting tracing example"); |
| 55 | + |
| 56 | + let mut terminal = init_terminal()?; |
| 57 | + let mut events = vec![]; // a buffer to store the recent events to display in the UI |
| 58 | + while !should_exit(&events) { |
| 59 | + handle_events(&mut events)?; |
| 60 | + terminal.draw(|frame| ui(frame, &events))?; |
| 61 | + } |
| 62 | + restore_terminal()?; |
| 63 | + info!("Exiting tracing example"); |
| 64 | + println!("See the tracing.log file for the logs"); |
| 65 | + Ok(()) |
| 66 | +} |
| 67 | + |
| 68 | +fn should_exit(events: &[Event]) -> bool { |
| 69 | + events |
| 70 | + .iter() |
| 71 | + .any(|event| matches!(event, Event::Key(key) if key.code == KeyCode::Char('q'))) |
| 72 | +} |
| 73 | + |
| 74 | +/// Handle events and insert them into the events vector keeping only the last 10 events |
| 75 | +#[instrument(skip(events))] |
| 76 | +fn handle_events(events: &mut Vec<Event>) -> Result<()> { |
| 77 | + // Render the UI at least once every 100ms |
| 78 | + if event::poll(Duration::from_millis(100))? { |
| 79 | + let event = event::read()?; |
| 80 | + debug!(?event); |
| 81 | + events.insert(0, event); |
| 82 | + } |
| 83 | + events.truncate(10); |
| 84 | + Ok(()) |
| 85 | +} |
| 86 | + |
| 87 | +#[instrument(skip_all)] |
| 88 | +fn ui(frame: &mut ratatui::Frame, events: &[Event]) { |
| 89 | + // To view this event, run the example with `RUST_LOG=tracing=debug cargo run --example tracing` |
| 90 | + trace!(frame_count = frame.count(), event_count = events.len()); |
| 91 | + let area = frame.size(); |
| 92 | + let events = events.iter().map(|e| format!("{e:?}")).collect::<Vec<_>>(); |
| 93 | + let paragraph = Paragraph::new(events.join("\n")) |
| 94 | + .block(Block::bordered().title("Tracing example. Press 'q' to quit.")); |
| 95 | + frame.render_widget(paragraph, area); |
| 96 | +} |
| 97 | + |
| 98 | +/// Initialize the tracing subscriber to log to a file |
| 99 | +/// |
| 100 | +/// This function initializes the tracing subscriber to log to a file named `tracing.log` in the |
| 101 | +/// current directory. The function returns a [`WorkerGuard`] that must be kept alive for the |
| 102 | +/// duration of the program to ensure that logs are flushed to the file on shutdown. The logs are |
| 103 | +/// written in a non-blocking fashion to ensure that the logs do not block the main thread. |
| 104 | +fn init_tracing() -> Result<WorkerGuard> { |
| 105 | + let file = File::create("tracing.log").wrap_err("failed to create tracing.log")?; |
| 106 | + let (non_blocking, guard) = non_blocking(file); |
| 107 | + |
| 108 | + // By default, the subscriber is configured to log all events with a level of `DEBUG` or higher, |
| 109 | + // but this can be changed by setting the `RUST_LOG` environment variable. |
| 110 | + let env_filter = EnvFilter::builder() |
| 111 | + .with_default_directive(Level::DEBUG.into()) |
| 112 | + .from_env_lossy(); |
| 113 | + |
| 114 | + tracing_subscriber::fmt() |
| 115 | + .with_writer(non_blocking) |
| 116 | + .with_env_filter(env_filter) |
| 117 | + .init(); |
| 118 | + Ok(guard) |
| 119 | +} |
| 120 | + |
| 121 | +/// Initialize the error hooks to ensure that the terminal is restored to a sane state before |
| 122 | +/// exiting |
| 123 | +fn init_error_hooks() -> Result<()> { |
| 124 | + let (panic, error) = HookBuilder::default().into_hooks(); |
| 125 | + let panic = panic.into_panic_hook(); |
| 126 | + let error = error.into_eyre_hook(); |
| 127 | + eyre::set_hook(Box::new(move |e| { |
| 128 | + let _ = restore_terminal(); |
| 129 | + error(e) |
| 130 | + }))?; |
| 131 | + panic::set_hook(Box::new(move |info| { |
| 132 | + let _ = restore_terminal(); |
| 133 | + panic(info); |
| 134 | + })); |
| 135 | + Ok(()) |
| 136 | +} |
| 137 | + |
| 138 | +#[instrument] |
| 139 | +fn init_terminal() -> Result<Terminal<impl Backend>> { |
| 140 | + enable_raw_mode()?; |
| 141 | + stdout().execute(EnterAlternateScreen)?; |
| 142 | + let backend = CrosstermBackend::new(stdout()); |
| 143 | + let terminal = Terminal::new(backend)?; |
| 144 | + debug!("terminal initialized"); |
| 145 | + Ok(terminal) |
| 146 | +} |
| 147 | + |
| 148 | +#[instrument] |
| 149 | +fn restore_terminal() -> Result<()> { |
| 150 | + disable_raw_mode()?; |
| 151 | + stdout().execute(LeaveAlternateScreen)?; |
| 152 | + debug!("terminal restored"); |
| 153 | + Ok(()) |
| 154 | +} |
0 commit comments