diff --git a/Cargo.lock b/Cargo.lock index f695e4e..8eaeb05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,6 +172,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "arboard" version = "3.6.1" @@ -674,6 +724,46 @@ dependencies = [ "windows-link", ] +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + [[package]] name = "clipboard-win" version = "5.4.1" @@ -693,6 +783,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "com" version = "0.6.0" @@ -1515,6 +1611,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.5.2" @@ -1691,6 +1793,12 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.15" @@ -2300,6 +2408,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "orbclient" version = "0.3.49" @@ -2706,9 +2820,10 @@ dependencies = [ [[package]] name = "rlogg" -version = "0.3.1" +version = "0.4.0" dependencies = [ "chrono", + "clap", "eframe", "rayon", "regex", @@ -3007,6 +3122,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "1.0.109" @@ -3279,6 +3400,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.18.1" diff --git a/Cargo.toml b/Cargo.toml index 07224a6..7add6a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rlogg" -version = "0.3.1" +version = "0.4.0" edition = "2024" authors = ["Stanislav Pastushenko "] description = "A fast log file viewer with search, filtering, and highlighting capabilities" @@ -18,6 +18,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rayon = "1.10" chrono = "0.4" +clap = { version = "4.5", features = ["derive"] } [profile.release] debug = false \ No newline at end of file diff --git a/README.md b/README.md index 116be24..e23c1fb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,247 @@ -# rlogg +# RLogg - Fast Log File Viewer -A fast log file viewer with search, filtering, and highlighting capabilities \ No newline at end of file +A fast, native log file viewer with search, filtering, and highlighting capabilities built with Rust and egui. + +## Features + +- **Fast Performance**: Handle large log files (GB+) with efficient line indexing +- **Advanced Search**: Regex support, case-sensitive/insensitive search, date range filtering +- **Syntax Highlighting**: Custom highlight rules with regex patterns +- **Multiple Tabs**: Open and view multiple log files simultaneously +- **Export Results**: Export filtered/searched results to new files +- **Cross-Platform**: Native builds for Linux, Windows, and macOS +- **Command-Line Interface**: Open files directly from terminal or OS file associations + +## Installation + +### From Source + +1. **Clone the repository:** + ```bash + git clone https://github.com/yourusername/rlogg.git + cd rlogg + ``` + +2. **Build in release mode:** + ```bash + cargo build --release + ``` + +3. **The binary will be available at:** + ``` + target/release/rlogg + ``` + +### Linux + +#### Quick Install (User-Local) + +```bash +cargo build --release +cd packaging/linux +./install.sh +``` + +This will: +- Install `rlogg` to `~/.local/bin/` +- Set up file associations for `.log` files +- Configure desktop integration + +Make sure `~/.local/bin` is in your PATH: +```bash +export PATH="$HOME/.local/bin:$PATH" +``` + +#### System-Wide Install + +```bash +sudo cp target/release/rlogg /usr/local/bin/ +sudo cp packaging/linux/rlogg.desktop /usr/share/applications/ +sudo cp packaging/linux/text-x-log.xml /usr/share/mime/packages/ +sudo update-desktop-database /usr/share/applications +sudo update-mime-database /usr/share/mime +``` + +### Windows + +1. **Build the project:** + ```powershell + cargo build --release + ``` + +2. **Run the installation script:** + ```powershell + cd packaging\windows + powershell -ExecutionPolicy Bypass -File install.ps1 + ``` + +This will: +- Install RLogg to `%LOCALAPPDATA%\Programs\RLogg` +- Create file associations for `.log` files +- Add "Open with RLogg" to the context menu + +## Usage + +### Graphical Interface + +**Launch without arguments:** +```bash +rlogg +``` + +Then use the "Open File" button to select log files. + +### Command-Line Interface + +**Open a single file:** +```bash +rlogg /path/to/file.log +``` + +**Open multiple files (each in a separate tab):** +```bash +rlogg file1.log file2.log file3.log +``` + +**Open all log files in a directory:** +```bash +rlogg /var/log/*.log +``` + +**Get help:** +```bash +rlogg --help +``` + +**Check version:** +```bash +rlogg --version +``` + +### File Associations + +After installation, you can: +- **Double-click** `.log` files to open them in RLogg +- **Right-click** → "Open With" → RLogg +- **Drag and drop** multiple log files onto the RLogg icon + +See [docs/FILE_ASSOCIATIONS.md](docs/FILE_ASSOCIATIONS.md) for detailed setup and troubleshooting. + +## Features Guide + +### Search + +1. Enter search query in the search panel +2. Choose case-sensitive or regex mode +3. Optionally enable date range filtering +4. Results are highlighted and filtered in real-time + +### Highlighting + +1. Click "Highlight Rules" to open the editor +2. Add custom regex patterns with colors +3. Rules are applied to all open files +4. Perfect for highlighting errors, warnings, etc. + +### Tabs + +- Open multiple files simultaneously +- Each file maintains its own search/filter state +- Close tabs with the × button +- Switch between tabs with mouse or keyboard + +### Export + +- Export filtered/searched results to a new file +- Preserves line formatting +- Useful for extracting specific log entries + +## Building + +### Prerequisites + +- Rust 1.70 or later +- Cargo + +### Build Commands + +**Development build:** +```bash +cargo build +``` + +**Release build (optimized):** +```bash +cargo build --release +``` + +**Run directly:** +```bash +cargo run -- /path/to/file.log +``` + +**Run tests:** +```bash +cargo test +``` + +See [BUILD.md](BUILD.md) for detailed build instructions. + +## Packaging + +See [packaging/README.md](packaging/README.md) for instructions on creating distribution packages: +- Linux: `.deb`, `.rpm`, AppImage, tarballs +- Windows: Installers, ZIP archives + +## Configuration + +RLogg stores configuration in `rlogg_config.json` next to the executable: +- Search history +- Highlight rules +- UI preferences +- Date format settings + +## Troubleshooting + +### Linux + +**File associations not working:** +```bash +update-desktop-database ~/.local/share/applications +update-mime-database ~/.local/share/mime +``` + +**Binary not found:** +Ensure `~/.local/bin` is in your PATH. + +### Windows + +**File associations not working:** +1. Restart File Explorer +2. Check registry entries (see [docs/FILE_ASSOCIATIONS.md](docs/FILE_ASSOCIATIONS.md)) + +**Permission errors:** +Run PowerShell as Administrator or install to user directory. + +For more troubleshooting, see [docs/FILE_ASSOCIATIONS.md](docs/FILE_ASSOCIATIONS.md). + +## Contributing + +Contributions are welcome! Please: +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Run tests and formatting +5. Submit a pull request + +## License + +MIT OR Apache-2.0 + +## Acknowledgments + +Built with: +- [eframe](https://github.com/emilk/egui/tree/master/crates/eframe) - GUI framework +- [egui](https://github.com/emilk/egui) - Immediate mode GUI library +- [rfd](https://github.com/PolyMeilex/rfd) - Native file dialogs +- [clap](https://github.com/clap-rs/clap) - Command-line argument parsing \ No newline at end of file diff --git a/src/log_viewer_app.rs b/src/log_viewer_app.rs index d6794fa..4642bd6 100644 --- a/src/log_viewer_app.rs +++ b/src/log_viewer_app.rs @@ -1,10 +1,11 @@ use std::sync::Arc; +use std::path::PathBuf; use eframe::egui; use crate::config::AppConfig; use crate::file_tab::FileTab; use crate::highlight::HighlightManager; use crate::search::{add_to_history, start_search, SearchParams, SearchState}; -use crate::tab_manager::{close_tab, open_file_dialog, IndexingState}; +use crate::tab_manager::{close_tab, open_file, open_file_dialog, IndexingState}; use crate::ui::{render_highlight_editor, render_search_panel, render_tabs_panel, render_top_menu, SearchPanelState}; use crate::ui::log_panel::{render_filter_panel, render_main_log_panel}; @@ -16,6 +17,7 @@ pub struct LogViewerApp { search_state: SearchState, highlight_manager: HighlightManager, first_frame: bool, + file_open_errors: Vec, } struct KeyAction { @@ -34,9 +36,30 @@ impl KeyAction { } impl LogViewerApp { - pub fn new(config: AppConfig) -> Self { + pub fn new(config: AppConfig, initial_files: Vec) -> Self { + let indexing_state = IndexingState::new(); + let mut tabs = Vec::new(); + let mut file_open_errors = Vec::new(); + + // Open initial files from command line + for file_path in initial_files { + if file_path.exists() { + if file_path.is_file() { + if let Some(tab) = open_file(file_path.clone(), &indexing_state) { + tabs.push(tab); + } else { + file_open_errors.push(format!("Failed to open: {}", file_path.display())); + } + } else { + file_open_errors.push(format!("Not a file: {}", file_path.display())); + } + } else { + file_open_errors.push(format!("File not found: {}", file_path.display())); + } + } + Self { - tabs: Vec::new(), + tabs, active_tab_index: 0, search_panel_state: SearchPanelState { query: config.last_search_query, @@ -48,10 +71,11 @@ impl LogViewerApp { date_from: config.date_from, date_to: config.date_to, }, - indexing_state: IndexingState::new(), + indexing_state, search_state: SearchState::new(), highlight_manager: HighlightManager::new(config.highlight_rules), first_frame: true, + file_open_errors, } } @@ -246,6 +270,32 @@ impl LogViewerApp { self.first_frame = false; ctx.send_viewport_cmd(egui::ViewportCommand::Maximized(true)); } + + // Display file opening errors if any + if !self.file_open_errors.is_empty() { + egui::Window::new("File Opening Errors") + .collapsible(false) + .resizable(false) + .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) + .show(ctx, |ui| { + ui.label("The following files could not be opened:"); + ui.add_space(10.0); + + egui::ScrollArea::vertical() + .max_height(300.0) + .show(ui, |ui| { + for error in &self.file_open_errors { + ui.label(error); + } + }); + + ui.add_space(10.0); + + if ui.button("OK").clicked() { + self.file_open_errors.clear(); + } + }); + } } } diff --git a/src/main.rs b/src/main.rs index c89fc9e..98a132e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,11 +13,25 @@ mod ui; mod log_viewer_app; use eframe::egui; +use clap::Parser; +use std::path::PathBuf; use crate::log_viewer_app::LogViewerApp; use config::AppConfig; +#[derive(Parser)] +#[command(name = "rlogg")] +#[command(version, about = "A fast log file viewer with search, filtering, and highlighting")] +struct Cli { + /// Log files to open on startup + #[arg(value_name = "FILES")] + files: Vec, +} + fn main() -> eframe::Result { + // Parse command-line arguments + let cli = Cli::parse(); + let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_inner_size([1200.0, 800.0]) @@ -28,13 +42,14 @@ fn main() -> eframe::Result { }; let config = AppConfig::load(); + let initial_files = cli.files; eframe::run_native( "RLogg", options, Box::new(move |cc| { theme::apply_theme(&cc.egui_ctx); - Ok(Box::new(LogViewerApp::new(config))) + Ok(Box::new(LogViewerApp::new(config, initial_files))) }), ) } diff --git a/src/tab_manager.rs b/src/tab_manager.rs index 012ea61..8af8ebc 100644 --- a/src/tab_manager.rs +++ b/src/tab_manager.rs @@ -42,7 +42,7 @@ pub fn open_file_dialog(indexing_state: &IndexingState) -> Option { open_file(path, indexing_state) } -fn open_file(path: PathBuf, indexing_state: &IndexingState) -> Option { +pub fn open_file(path: PathBuf, indexing_state: &IndexingState) -> Option { indexing_state.start_indexing(); // Background indexing for progress indication