feat: add logging for tui

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2024-05-01 22:17:39 +02:00
parent 5327aff217
commit 1885a200c4
12 changed files with 600 additions and 11 deletions

View File

@@ -13,7 +13,13 @@ dotenv.workspace = true
axum.workspace = true
serde = { version = "1.0.197", features = ["derive"] }
sqlx = { version = "0.7.3", features = ["runtime-tokio", "tls-rustls", "postgres", "uuid", "time"] }
sqlx = { version = "0.7.3", features = [
"runtime-tokio",
"tls-rustls",
"postgres",
"uuid",
"time",
] }
uuid = { version = "1.7.0", features = ["v4"] }
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
serde_json = "1.0.116"

View File

@@ -14,9 +14,18 @@ impl Querier {
root: &str,
path: impl IntoIterator<Item = impl Into<String>>,
) -> Option<GraphItem> {
let path = path.into_iter().map(|i| i.into()).collect::<Vec<String>>();
let path = path
.into_iter()
.map(|i| i.into())
.filter(|i| !i.is_empty())
.collect::<Vec<String>>();
tracing::debug!("quering: {}, len: ({}))", path.join("."), path.len());
tracing::debug!(
"quering: root:({}), path:({}), len: ({}))",
root,
path.join("."),
path.len()
);
self.engine
.get(root, &path.iter().map(|i| i.as_str()).collect::<Vec<_>>())

View File

@@ -10,6 +10,7 @@ pub struct LockFile(PathBuf);
impl Drop for LockFile {
fn drop(&mut self) {
tracing::debug!("removing lockfile");
std::fs::remove_file(&self.0).expect("to be able to delete lockfile")
}
}
@@ -76,6 +77,14 @@ impl Storage {
Ok(())
}
pub fn clear_lock_file(self) {
let mut lock_file = self.lock_file.lock().unwrap();
if lock_file.is_some() {
*lock_file = None;
}
}
fn state(&self) -> anyhow::Result<PathBuf> {
self.cache().map(|c| c.join("graph.json"))
}

View File

@@ -0,0 +1,16 @@
[package]
name = "hyperlog-tui"
version = "0.1.0"
edition = "2021"
[dependencies]
hyperlog-core.workspace = true
anyhow.workspace = true
tokio.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
ratatui = "0.26.2"
crossterm = { version = "0.27.0", features = ["event-stream"] }
directories = "5.0.1"

View File

@@ -0,0 +1,138 @@
use std::{
io::{self, Stdout},
ops::{Deref, DerefMut},
time::Duration,
};
use anyhow::{Context, Result};
use crossterm::{
event::{self, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use hyperlog_core::state::State;
use ratatui::{backend::CrosstermBackend, prelude::*, widgets::*, Frame, Terminal};
struct TerminalInstance {
terminal: Terminal<CrosstermBackend<Stdout>>,
}
impl TerminalInstance {
fn new() -> Result<Self> {
Ok(Self {
terminal: setup_terminal().context("setup failed")?,
})
}
}
impl Deref for TerminalInstance {
type Target = Terminal<CrosstermBackend<Stdout>>;
fn deref(&self) -> &Self::Target {
&self.terminal
}
}
impl DerefMut for TerminalInstance {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.terminal
}
}
impl Drop for TerminalInstance {
fn drop(&mut self) {
if let Err(e) = restore_terminal(&mut self.terminal).context("restore terminal failed") {
tracing::error!("failed to restore terminal: {}", e);
}
}
}
pub async fn execute(state: &State) -> Result<()> {
tracing::debug!("starting hyperlog tui");
logging::initialize_panic_handler()?;
logging::initialize_logging()?;
let mut terminal = TerminalInstance::new()?;
run(&mut terminal, state).context("app loop failed")?;
Ok(())
}
fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, state: &State) -> Result<()> {
loop {
terminal.draw(|f| crate::render_app(f, &state))?;
if should_quit()? {
break;
}
}
Ok(())
}
fn render_app(frame: &mut Frame, state: &State) {
let chunks =
Layout::vertical(vec![Constraint::Length(2), Constraint::Min(0)]).split(frame.size());
let heading = Paragraph::new(text::Line::from(
Span::styled("hyperlog", Style::default()).fg(Color::Green),
));
let block_heading = Block::default().borders(Borders::BOTTOM);
frame.render_widget(heading.block(block_heading), chunks[0]);
let Rect { width, height, .. } = chunks[1];
let height = height as usize;
let width = width as usize;
let mut lines = Vec::new();
for y in 0..height {
if !y % 2 == 0 {
lines.push(text::Line::default());
} else {
lines.push(text::Line::raw(" ~ ".repeat(width / 3)));
}
}
let background = Paragraph::new(lines);
let bg_block = Block::default()
.fg(Color::DarkGray)
.bold()
.padding(Padding {
left: 4,
right: 4,
top: 2,
bottom: 2,
});
if let Some(graph) = state.querier.get("something", Vec::<String>::new()) {}
frame.render_widget(background.block(bg_block), chunks[1]);
}
fn should_quit() -> Result<bool> {
if event::poll(Duration::from_millis(250)).context("event poll failed")? {
if let Event::Key(key) = event::read().context("event read failed")? {
return Ok(KeyCode::Char('q') == key.code);
}
}
Ok(false)
}
fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
let mut stdout = io::stdout();
enable_raw_mode().context("failed to enable raw mode")?;
execute!(stdout, EnterAlternateScreen).context("unable to enter alternate screen")?;
Terminal::new(CrosstermBackend::new(stdout)).context("creating terminal failed")
}
/// Restore the terminal. This is where you disable raw mode, leave the alternate screen, and show
/// the cursor.
fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
disable_raw_mode().context("failed to disable raw mode")?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)
.context("unable to switch to main screen")?;
terminal.show_cursor().context("unable to show cursor")
}
mod logging;

View File

@@ -0,0 +1,77 @@
use std::path::PathBuf;
use directories::ProjectDirs;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
pub fn initialize_logging() -> anyhow::Result<()> {
let project = match ProjectDirs::from("io", "kjuulh", env!("CARGO_PKG_NAME")) {
Some(p) => p.data_local_dir().to_path_buf(),
None => PathBuf::from(".").join(".data"),
};
std::fs::create_dir_all(&project)?;
let log_path = project.join("hyperlog.log");
//println!("logging to: {}", log_path.display());
let log_file = std::fs::File::create(log_path)?;
std::env::set_var(
"RUST_LOG",
std::env::var("RUST_LOG")
.or_else(|_| std::env::var("HYPERLOG_LOG_LEVEL"))
.unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))),
);
let file_subscriber = tracing_subscriber::fmt::layer()
.with_file(true)
.with_line_number(true)
.with_writer(log_file)
.with_target(false)
.with_ansi(false)
.with_filter(tracing_subscriber::filter::EnvFilter::from_default_env());
tracing_subscriber::registry()
.with(file_subscriber)
//.with(ErrorLayer::default())
.init();
Ok(())
}
pub fn initialize_panic_handler() -> anyhow::Result<()> {
std::panic::set_hook(Box::new(move |panic_info| {
// if let Ok(mut t) = crate::tui::Tui::new() {
// if let Err(r) = t.exit() {
// tracing::error!("Unable to exit Terminal: {:?}", r);
// }
// }
#[cfg(not(debug_assertions))]
{
use human_panic::{handle_dump, print_msg, Metadata};
let meta = Metadata {
version: env!("CARGO_PKG_VERSION").into(),
name: env!("CARGO_PKG_NAME").into(),
authors: env!("CARGO_PKG_AUTHORS").replace(':', ", ").into(),
homepage: env!("CARGO_PKG_HOMEPAGE").into(),
};
let file_path = handle_dump(&meta, panic_info);
// prints human-panic message
print_msg(file_path, &meta)
.expect("human-panic: printing error message to console failed");
//eprintln!("{}", panic_hook.panic_report(panic_info)); // prints color-eyre stack trace to stderr
}
let msg = format!("{}", panic_info);
tracing::error!("Error: {}", msg);
// #[cfg(debug_assertions)]
// {
// // Better Panic stacktrace that is only enabled when debugging.
// better_panic::Settings::auto()
// .most_recent_first(false)
// .lineno_suffix(true)
// .verbosity(better_panic::Verbosity::Full)
// .create_panic_handler()(panic_info);
// }
}));
Ok(())
}

View File

@@ -5,6 +5,7 @@ edition = "2021"
[dependencies]
hyperlog-core.workspace = true
hyperlog-tui.workspace = true
anyhow.workspace = true
tokio.workspace = true
@@ -15,7 +16,13 @@ dotenv.workspace = true
axum.workspace = true
serde = { version = "1.0.197", features = ["derive"] }
sqlx = { version = "0.7.3", features = ["runtime-tokio", "tls-rustls", "postgres", "uuid", "time"] }
sqlx = { version = "0.7.3", features = [
"runtime-tokio",
"tls-rustls",
"postgres",
"uuid",
"time",
] }
uuid = { version = "1.7.0", features = ["v4"] }
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
serde_json = "1.0.116"

View File

@@ -0,0 +1,77 @@
hyperlog
--------
kjuulh:
- (summary: items(10)) -> projects/**
- [ ] wash the dishes
...
- project A
- sub project B (items: 10)
...
- sub project C (items: 0)
- project B
- sub project A
- project C
- sub project A
- project D
- sub project A
---
Traversing into the nested section
hyperlog
--------
kjuulh:
- (summary: items(10)) -> projects/**
- **project A**
- sub project B (items: 10)
- [ ] Something
- [ ] Something B (High priority, due wednesday)
- [ ] Something C
...
- sub project C (items: 0)
- [ ] Something
- [ ] Something B (High priority, due wednesday)
- [ ] Something C
- [ ] Something D
- [ ] Something E
...
- project B
---
Traversing into the final section
hyperlog
--------
- **project A**
- **sub project B (items: 10)**
- [ ] Something
- [ ] Something B (High priority, due wednesday)
- [ ] Something C
- [ ] Something E
- [ ] Something D
- sub project C (items: 0)
...

View File

@@ -6,7 +6,7 @@ use hyperlog_core::{commander, state};
use crate::server::serve;
#[derive(Parser)]
#[command(author, version, about, long_about = None, subcommand_required = true)]
#[command(author, version, about, long_about = None)]
struct Command {
#[command(subcommand)]
command: Option<Commands>,
@@ -27,6 +27,8 @@ enum Commands {
commands: QueryCommands,
},
Info {},
ClearLock {},
}
#[derive(Subcommand)]
@@ -59,6 +61,10 @@ enum QueryCommands {
pub async fn execute() -> anyhow::Result<()> {
let cli = Command::parse();
if cli.command.is_some() {
tracing_subscriber::fmt::init();
}
let state = state::State::new()?;
match cli.command {
@@ -100,8 +106,13 @@ pub async fn execute() -> anyhow::Result<()> {
Some(Commands::Info {}) => {
println!("graph stored at: {}", state.storage.info()?)
}
None => {}
Some(Commands::ClearLock {}) => {
state.storage.clear_lock_file();
println!("cleared lock file");
}
None => {
hyperlog_tui::execute(&state).await?;
}
}
Ok(())

View File

@@ -5,7 +5,6 @@ pub(crate) mod state;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenv::dotenv().ok();
tracing_subscriber::fmt::init();
cli::execute().await?;