feat: add logging for tui

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

241
Cargo.lock generated
View File

@ -15,6 +15,15 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
@ -211,7 +220,7 @@ checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
"regex-automata 0.1.10",
]
[[package]]
@ -237,6 +246,21 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
dependencies = [
"rustversion",
]
[[package]]
name = "cc"
version = "1.0.79"
@ -297,6 +321,19 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "compact_str"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"ryu",
"static_assertions",
]
[[package]]
name = "console"
version = "0.15.8"
@ -363,6 +400,32 @@ version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.4.2",
"crossterm_winapi",
"futures-core",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -405,6 +468,15 @@ dependencies = [
"subtle",
]
[[package]]
name = "directories"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "5.0.1"
@ -804,6 +876,7 @@ dependencies = [
"dirs",
"dotenv",
"hyperlog-core",
"hyperlog-tui",
"serde",
"serde_json",
"similar-asserts",
@ -838,6 +911,20 @@ dependencies = [
"uuid",
]
[[package]]
name = "hyperlog-tui"
version = "0.1.0"
dependencies = [
"anyhow",
"crossterm",
"directories",
"hyperlog-core",
"ratatui",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "idna"
version = "0.4.0"
@ -858,6 +945,12 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "indoc"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "io-lifetimes"
version = "1.0.11"
@ -966,6 +1059,24 @@ version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "lru"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc"
dependencies = [
"hashbrown",
]
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "matchit"
version = "0.7.0"
@ -1007,6 +1118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
@ -1281,6 +1393,26 @@ dependencies = [
"getrandom",
]
[[package]]
name = "ratatui"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80"
dependencies = [
"bitflags 2.4.2",
"cassowary",
"compact_str",
"crossterm",
"indoc",
"itertools",
"lru",
"paste",
"stability",
"strum",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "redox_syscall"
version = "0.3.5"
@ -1301,11 +1433,49 @@ dependencies = [
"thiserror",
]
[[package]]
name = "regex"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.3.7",
"regex-syntax 0.7.5",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-automata"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.7.5",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "ring"
@ -1509,6 +1679,27 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
@ -1827,6 +2018,22 @@ dependencies = [
"uuid",
]
[[package]]
name = "stability"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a"
dependencies = [
"quote",
"syn 2.0.48",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stringprep"
version = "0.1.4"
@ -1844,6 +2051,28 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.48",
]
[[package]]
name = "subtle"
version = "2.5.0"
@ -2115,10 +2344,14 @@ version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
@ -2156,6 +2389,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
[[package]]
name = "unicode_categories"
version = "0.1.1"

View File

@ -4,11 +4,12 @@ resolver = "2"
[workspace.dependencies]
hyperlog-core = { path = "crates/hyperlog-core" }
hyperlog-tui = { path = "crates/hyperlog-tui" }
anyhow = { version = "1" }
tokio = { version = "1", features = ["full"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3.18" }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
clap = { version = "4", features = ["derive", "env"] }
dotenv = { version = "0.15" }
axum = { version = "0.7" }

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?;