From f800d63e0260d2f6015abb28abaa2b002d5e820c Mon Sep 17 00:00:00 2001 From: thek4n Date: Sat, 21 Feb 2026 10:40:46 +0300 Subject: [PATCH] add logs --- Cargo.lock | 50 +++++++++++++++++ Cargo.toml | 1 + src/main.rs | 158 +++++++++++++++++++++++++++++++--------------------- 3 files changed, 145 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82b288c..24eb281 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "async-compression" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d67d43201f4d20c78bcda740c142ca52482d81da80681533d33bf3f0596c8e2" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -234,6 +246,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "crc32fast" version = "1.5.0" @@ -658,6 +687,7 @@ dependencies = [ "syntect", "tokio", "tokio-stream", + "tower-http", "tracing-subscriber", ] @@ -1233,6 +1263,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "async-compression", + "bitflags 2.11.0", + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index b67f473..221e7fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ notify = "6.1" tokio-stream = { version = "0.1", features = ["sync"] } futures = "0.3" clap = { version = "4.5", features = ["derive"] } +tower-http = { version = "0.6.8", features = ["trace", "compression-gzip", "cors"] } diff --git a/src/main.rs b/src/main.rs index efe88c9..cd839f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,29 @@ use axum::{ - extract::{Path, State}, - routing::get, Router, - http::{StatusCode, HeaderMap, header}, - response::{Html, Sse, IntoResponse}, + extract::{Path, State}, + http::{HeaderMap, StatusCode, header}, + response::{Html, IntoResponse, Sse}, + routing::get, }; -use std::net::{SocketAddr, ToSocketAddrs}; -use pulldown_cmark::{Options, html, Event, Tag, CodeBlockKind}; +use futures::StreamExt; +use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use pulldown_cmark; +use pulldown_cmark::{CodeBlockKind, Event, Options, Tag, html}; +use std::convert::Infallible; +use std::net::{SocketAddr, ToSocketAddrs}; use std::path::PathBuf; use std::sync::Arc; +use std::time::Duration; use tokio::fs; use tokio::sync::broadcast; use tokio_stream::wrappers::BroadcastStream; -use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher, EventKind}; -use futures::StreamExt; -use std::convert::Infallible; -use std::time::Duration; use syntect::easy::HighlightLines; use syntect::highlighting::ThemeSet; -use syntect::html::{styled_line_to_highlighted_html, IncludeBackground}; +use syntect::html::{IncludeBackground, styled_line_to_highlighted_html}; use syntect::parsing::SyntaxSet; +use tower_http::trace::TraceLayer; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use clap::Parser; use std::io; @@ -30,7 +32,6 @@ use std::io; const TEMPLATE_FILE: &str = include_str!("../templates/file.html"); const TEMPLATE_DIR: &str = include_str!("../templates/dir.html"); - #[derive(clap::Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { @@ -47,7 +48,6 @@ struct Args { root: PathBuf, } - #[derive(Clone)] struct AppState { syntax_set: Arc, @@ -65,7 +65,12 @@ async fn main() { std::process::exit(1); } - tracing_subscriber::fmt::init(); + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::new( + std::env::var("RUST_LOG").unwrap_or_else(|_| "info,tower_http=info".into()), + )) + .with(tracing_subscriber::fmt::layer()) + .init(); let ss = SyntaxSet::load_defaults_newlines(); let ts = ThemeSet::load_defaults(); @@ -88,7 +93,8 @@ async fn main() { .route("/", get(root)) .route("/*path", get(serve_file)) .route("/events/*path", get(sse_handler)) - .with_state(state); + .with_state(state) + .layer(TraceLayer::new_for_http()); let addr = resolve_addr(&args.host, args.port).unwrap(); @@ -107,9 +113,7 @@ fn resolve_addr(host: &str, port: u16) -> io::Result { .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Не удалось разрешить адрес")) } -async fn root( - State(state): State, -) ->Result, StatusCode> { +async fn root(State(state): State) -> Result, StatusCode> { render_directory_index(&PathBuf::from(state.root), "").await } @@ -118,40 +122,47 @@ async fn sse_handler( Path(full_path): Path, ) -> impl IntoResponse { let mut headers = HeaderMap::new(); - headers.insert(header::CONTENT_TYPE, header::HeaderValue::from_static("text/event-stream")); - headers.insert(header::CACHE_CONTROL, header::HeaderValue::from_static("no-cache")); - headers.insert(header::CONNECTION, header::HeaderValue::from_static("keep-alive")); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/event-stream"), + ); + headers.insert( + header::CACHE_CONTROL, + header::HeaderValue::from_static("no-cache"), + ); + headers.insert( + header::CONNECTION, + header::HeaderValue::from_static("keep-alive"), + ); let rx = state.tx.subscribe(); let requested_path = full_path.clone(); - let stream = BroadcastStream::new(rx) - .filter_map(move |res| { - let req_path = requested_path.clone(); - async move { - match res { - Ok(changed_path) => { - if changed_path.contains(&req_path) { - Some(Ok::( - axum::response::sse::Event::default() - .event("reload") - .data("") - )) - } else { - None - } + let stream = BroadcastStream::new(rx).filter_map(move |res| { + let req_path = requested_path.clone(); + async move { + match res { + Ok(changed_path) => { + if changed_path.contains(&req_path) { + Some(Ok::( + axum::response::sse::Event::default() + .event("reload") + .data(""), + )) + } else { + None } - Err(_) => None, } + Err(_) => None, } - }); + } + }); - let sse = Sse::new(stream) - .keep_alive( - axum::response::sse::KeepAlive::new() - .interval(Duration::from_secs(15)) - .text("ping") - ); + let sse = Sse::new(stream).keep_alive( + axum::response::sse::KeepAlive::new() + .interval(Duration::from_secs(15)) + .text("ping"), + ); (headers, sse) } @@ -206,7 +217,11 @@ async fn serve_file( // Логика кнопки "Назад" let back_link = if let Some(pos) = full_path.rfind('/') { let parent = &full_path[..pos]; - if parent.is_empty() { "/".to_string() } else { format!("/{}", parent) } + if parent.is_empty() { + "/".to_string() + } else { + format!("/{}", parent) + } } else { "/".to_string() }; @@ -246,7 +261,11 @@ async fn render_directory_index( let mut files: Vec<(String, String, bool)> = Vec::new(); - while let Some(entry) = entries.next_entry().await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? { + while let Some(entry) = entries + .next_entry() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + { let file_name = entry.file_name().to_string_lossy().to_string(); if file_name.starts_with('.') { @@ -264,19 +283,21 @@ async fn render_directory_index( files.push((file_name, link_path, is_dir)); } - files.sort_by(|a, b| { - match (a.2, b.2) { - (true, false) => std::cmp::Ordering::Less, - (false, true) => std::cmp::Ordering::Greater, - _ => a.0.cmp(&b.0), - } + files.sort_by(|a, b| match (a.2, b.2) { + (true, false) => std::cmp::Ordering::Less, + (false, true) => std::cmp::Ordering::Greater, + _ => a.0.cmp(&b.0), }); let mut list_html = String::from("
    "); if !request_path.is_empty() && request_path != "files" { let parent_path = request_path.rsplit_once('/').map(|(p, _)| p).unwrap_or(""); - let parent_link = if parent_path.is_empty() { "/".to_string() } else { format!("/{}", parent_path) }; + let parent_link = if parent_path.is_empty() { + "/".to_string() + } else { + format!("/{}", parent_path) + }; list_html.push_str(&format!( r#"
  • @@ -302,7 +323,11 @@ async fn render_directory_index( } list_html.push_str("
"); - let title_path = if request_path.is_empty() { "" } else { request_path.trim_start_matches('/') }; + let title_path = if request_path.is_empty() { + "" + } else { + request_path.trim_start_matches('/') + }; let final_html = TEMPLATE_DIR .replace("{{TITLE_PATH}}", title_path) @@ -326,7 +351,8 @@ async fn run_file_watcher(state: AppState) { } }, Config::default(), - ).expect("Failed to create watcher"); + ) + .expect("Failed to create watcher"); let watch_path = PathBuf::from("./notes"); if let Err(e) = watcher.watch(&watch_path, RecursiveMode::Recursive) { @@ -368,7 +394,7 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: & } else { None }; - }, + } Event::End(Tag::CodeBlock(_)) => { in_code_block = false; let is_mermaid = current_lang.as_deref() == Some("mermaid"); @@ -398,11 +424,16 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: & let line_with_newline = format!("{}\n", line); match h.highlight_line(&line_with_newline, ss) { Ok(regions) => { - let html_line = styled_line_to_highlighted_html(®ions[..], IncludeBackground::No) - .unwrap_or_else(|_| escape_html(&line_with_newline)); + let html_line = styled_line_to_highlighted_html( + ®ions[..], + IncludeBackground::No, + ) + .unwrap_or_else(|_| escape_html(&line_with_newline)); result_html.push_str(&html_line); - }, - Err(_) => result_html.push_str(&escape_html(&line_with_newline)), + } + Err(_) => { + result_html.push_str(&escape_html(&line_with_newline)) + } } } result_html @@ -421,15 +452,14 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: &
{}
"#, - lang_escaped, - highlighted_html + lang_escaped, highlighted_html ); processed_events.push(Event::Html(code_container.into())); } - }, + } Event::Text(text) if in_code_block => { current_code.push_str(&text); - }, + } _ => { if !in_code_block { processed_events.push(event);