From 5eaefd9aab1997d3bfd0dfa6252ead8faf8b3581 Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Wed, 25 Oct 2023 03:05:53 +0200 Subject: [PATCH] log: logging to file, console and stdout --- Cargo.lock | 92 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + TODO.md | 4 +- src/importer/obj.rs | 10 +++-- src/importer/stl.rs | 4 +- src/ipc.rs | 3 +- src/lib.rs | 2 +- src/log.rs | 64 ---------------------------- src/logger.rs | 100 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 43 +++++++++++++++++-- src/shader.rs | 9 ++-- src/ui/ui.rs | 40 +++++++----------- src/utils.rs | 18 ++++---- 13 files changed, 276 insertions(+), 115 deletions(-) delete mode 100644 src/log.rs create mode 100644 src/logger.rs diff --git a/Cargo.lock b/Cargo.lock index 67f3213..87e1c72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,6 +255,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + [[package]] name = "directories" version = "4.0.1" @@ -677,6 +686,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + [[package]] name = "jpeg-decoder" version = "0.3.0" @@ -892,6 +907,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "objc" version = "0.2.7" @@ -1015,6 +1039,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.69" @@ -1285,6 +1315,17 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simplelog" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" +dependencies = [ + "log 0.4.20", + "termcolor", + "time", +] + [[package]] name = "slab" version = "0.4.9" @@ -1349,6 +1390,15 @@ version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -1401,9 +1451,11 @@ dependencies = [ "glm", "image", "imgui", + "log 0.4.20", "mint", "rfd", "serde", + "simplelog", ] [[package]] @@ -1417,6 +1469,37 @@ dependencies = [ "weezl", ] +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "libc", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + [[package]] name = "toml" version = "0.8.2" @@ -1591,6 +1674,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 7574bcd..cdd0696 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ anyhow = "1.0.71" rfd = "0.11.3" bincode = "1.3.3" fs4 = "0.7.0" +simplelog = "0.12.1" +log = "0.4.20" [dependencies.imgui] version = "0.11.0" diff --git a/TODO.md b/TODO.md index 4ca86c0..e7f6256 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,8 @@ 3dobs TODOs: - [x] Help menu - [x] About window - - [ ] File logger - - [ ] Logger should log to file, console and stdout + - [x] File logger + - [x] Logger should log to file, console and stdout - [x] Move console to be under objects - [x] Don't have the viewport window be scrollable - [x] Object rotation with left click + mouse movement (very rudimentary implementation, but works) diff --git a/src/importer/obj.rs b/src/importer/obj.rs index 3f7a16b..c8e10c3 100644 --- a/src/importer/obj.rs +++ b/src/importer/obj.rs @@ -1,5 +1,7 @@ use std::{io::{BufReader, BufRead}, collections::HashMap, path::PathBuf}; +use log::{error, warn, trace}; + use crate::{mesh::Vertex, aabb::AABB, importer::ObjMesh, importer::Object, importer::Material}; const BUF_CAP: usize = 1024 * 128; // 128 Kilobytes @@ -140,7 +142,7 @@ fn parse_mtl(path: &PathBuf) -> Result, Box { // TODO: textures } - _ => { println!("Unhandled material token: {}", token) }, + _ => { warn!("Unhandled material token: {}", token) }, } } } @@ -327,7 +329,7 @@ pub fn load_obj(obj_path: &PathBuf, file: std::fs::File) -> Result { - eprintln!("Failed to parse mtl file {:?}: {}", material_path, e); + error!("Failed to parse mtl file {:?}: {}", material_path, e); } } } @@ -360,12 +362,12 @@ pub fn load_obj(obj_path: &PathBuf, file: std::fs::File) -> Result { // we don't handle lines or points } - _ => { println!("Unhandled obj token: {}", token) }, + _ => { warn!("Unhandled obj token: {}", token) }, } } } let elapsed = now.elapsed(); - println!("Loaded in {}ms", elapsed.as_millis()); + trace!("Loaded in {}ms", elapsed.as_millis()); let mesh_name = { if current_mesh_name.is_empty() && !object_name.is_empty() { diff --git a/src/importer/stl.rs b/src/importer/stl.rs index 6a848ea..563ccd0 100644 --- a/src/importer/stl.rs +++ b/src/importer/stl.rs @@ -1,5 +1,7 @@ use std::io::{Read, Seek, BufReader, BufRead}; +use log::trace; + use crate::{importer::ObjMesh, importer::Object, importer::Material, aabb::AABB, mesh::Vertex}; const STL_HEADER_SIZE: u64 = 80; @@ -229,7 +231,7 @@ pub fn load_stl(mut file: std::fs::File) -> Result UnixListener { if pipe_path.exists() { @@ -58,7 +59,7 @@ pub fn init(lock_file: &File, args_paths: Vec, one_instance: bool) -> O ipc_tx.send(paths).unwrap(); } Err(err) => { - println!("Error: {:?}", err); + error!("Error: {:?}", err); break; } } diff --git a/src/lib.rs b/src/lib.rs index 152380d..e941393 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ pub mod utils; pub mod mesh; pub mod model; pub mod ui; -pub mod log; +pub mod logger; pub mod aabb; pub mod importer; pub mod ipc; diff --git a/src/log.rs b/src/log.rs deleted file mode 100644 index d67c18d..0000000 --- a/src/log.rs +++ /dev/null @@ -1,64 +0,0 @@ - -#[derive(Clone)] -pub enum LogLevel { - Debug, - Info, - Warning, - Error, -} - -impl From for mint::Vector4 { - fn from(level: LogLevel) -> Self { - match level { - LogLevel::Debug => [0.5, 0.5, 1.0, 1.0].into(), - LogLevel::Info => [0.5, 0.5, 0.5, 1.0].into(), - LogLevel::Warning => [1.0, 0.64, 0.0, 1.0].into(), - LogLevel::Error => [1.0, 0.0, 0.0, 1.0].into(), - } - } -} - -#[derive(Clone)] -pub struct LogMessage { - pub level: LogLevel, - pub message: String, -} - -impl LogMessage { - pub fn new(level: LogLevel, message: &str) -> Self { - let severity = match level { - LogLevel::Debug => "DEBUG", - LogLevel::Info => "INFO", - LogLevel::Warning => "WARNING", - LogLevel::Error => "ERROR", - }; - - Self { - level, - message: format!("[{}]: {}", severity, message), - } - } -} - -pub struct Log { - pub history: Vec, -} - -impl Log { - pub fn clear(&mut self) { - self.history.clear(); - } - - pub fn log(&mut self, message: &str, level: LogLevel) { - // TODO: also write to a log file - self.history.push(LogMessage::new(level, message)); - } -} - -impl Default for Log { - fn default() -> Self { - Self { - history: Vec::new(), - } - } -} diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..a0919d3 --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,100 @@ +use std::{io::Write, sync::{Arc, RwLock}}; + +#[derive(Copy, Clone)] +pub enum LogLevel { + Error, + Warn, + Info, + Debug, +} + +impl From for mint::Vector4 { + fn from(level: LogLevel) -> Self { + match level { + LogLevel::Debug => [0.5, 0.5, 1.0, 1.0].into(), + LogLevel::Info => [0.5, 0.5, 0.5, 1.0].into(), + LogLevel::Warn => [1.0, 0.64, 0.0, 1.0].into(), + LogLevel::Error => [1.0, 0.0, 0.0, 1.0].into(), + } + } +} + +impl From for LogLevel { + fn from(level: log::LevelFilter) -> Self { + match level { + log::LevelFilter::Debug => LogLevel::Debug, + log::LevelFilter::Info => LogLevel::Info, + log::LevelFilter::Warn => LogLevel::Warn, + log::LevelFilter::Error => LogLevel::Error, + _ => LogLevel::Debug, + } + } +} + +pub struct LogMessage { + pub level: LogLevel, + pub message: String, +} + +impl LogMessage { + pub fn new(level: LogLevel, message: &str) -> Self { + Self { + level, + message: format!("{}", message), + } + } +} + +#[derive(Default)] +pub struct Log { + pub history: Vec, +} + +impl Log { + pub fn clear(&mut self) { + self.history.clear(); + } + + pub fn log(&mut self, message: &str, level: LogLevel) { + self.history.push(LogMessage::new(level, message)); + } +} + +#[derive(Clone)] +pub struct WritableLog { + pub arc: Arc>, + buf: String +} + +impl Default for WritableLog { + fn default() -> Self { + Self{arc: Arc::new(RwLock::new(Log::default())), buf: String::with_capacity(1024)} + } +} + +impl Write for WritableLog { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.buf.push_str(std::str::from_utf8(buf).unwrap()); + if buf.contains(&b'\n') { + self.flush()?; + } + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + // match the log level found at index 0 till space to LogLevel + let level = match &self.buf[0..self.buf.find(' ').unwrap()] { + "[ERROR]" => LogLevel::Error, + "[WARN]" => LogLevel::Warn, + "[INFO]" => LogLevel::Info, + "[DEBUG]" => LogLevel::Debug, + _ => LogLevel::Info, + }; + + let mut logger = self.arc.write().unwrap(); + logger.log(&self.buf, level); + self.buf.clear(); + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 40fb81b..4c8de64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,49 @@ -use glfw::{Action, Context, Key, Modifiers}; -use glad_gl::gl; -use anyhow; use std::fs::File; use std::env; use std::path::PathBuf; +use glfw::{Action, Context, Key, Modifiers}; +use glad_gl::gl; +use anyhow; +use simplelog::*; + use threedobs::{shader, ui::ui, utils, ipc}; fn main() -> anyhow::Result<(), Box> { + let logger = threedobs::logger::WritableLog::default(); + + let log_conf = ConfigBuilder::default() + .set_target_level(LevelFilter::Error) + .set_thread_level(LevelFilter::Off) + .set_level_color(Level::Error, Some(Color::Red)) + .set_level_color(Level::Debug, Some(Color::Rgb(128, 128, 255))) + .set_level_color(Level::Warn, Some(Color::Rgb(255, 163, 0))) + .set_level_color(Level::Info, Some(Color::Rgb(128, 128, 128))) + .build(); + let in_program_log_conf = ConfigBuilder::default() + .set_target_level(LevelFilter::Off) + .set_time_level(LevelFilter::Off) + .set_thread_level(LevelFilter::Off) + .set_level_color(Level::Error, Some(Color::Red)) + .set_level_color(Level::Debug, Some(Color::Rgb(128, 128, 255))) + .set_level_color(Level::Warn, Some(Color::Rgb(255, 255, 0))) + .set_level_color(Level::Info, Some(Color::Rgb(128, 128, 128))) + .build(); + + let log_level = if cfg!(debug_assertions) { + LevelFilter::Debug + } else { + LevelFilter::Info + }; + + CombinedLogger::init( + vec![ + TermLogger::new(log_level, log_conf.clone(), TerminalMode::Mixed, ColorChoice::Auto), + WriteLogger::new(log_level, log_conf, File::create("3dobs.log")?), + WriteLogger::new(log_level, in_program_log_conf, logger.clone()) + ] + ).unwrap(); + let settings: ui::Settings = confy::load("3dobs", "settings")?; let args: Vec = env::args().collect(); @@ -36,6 +72,7 @@ fn main() -> anyhow::Result<(), Box> { let mut state = ui::State{ settings, + logger, ..Default::default() }; diff --git a/src/shader.rs b/src/shader.rs index 8559a74..e160af0 100644 --- a/src/shader.rs +++ b/src/shader.rs @@ -1,5 +1,6 @@ use glad_gl::gl; use anyhow::Result; +use log::{debug, error}; pub struct ShaderSource { pub name: String, @@ -26,7 +27,7 @@ impl Shader { let mut success1 = 0; gl::GetShaderiv(vertex_shader, gl::COMPILE_STATUS, &mut success1); #[cfg(debug_assertions)] - println!("vertex shader {:?} compiled with status: {}", + debug!("vertex shader {:?} compiled with status: {}", vertex_obj.name, success1 ); @@ -34,7 +35,7 @@ impl Shader { let info_buf = [0u8; 512]; gl::GetShaderInfoLog(vertex_shader as u32, 512, std::ptr::null_mut(), info_buf.as_ptr() as *mut i8); #[cfg(debug_assertions)] - println!("vertex shader info: {}", std::str::from_utf8(&info_buf).unwrap()); + error!("vertex shader info: {}", std::str::from_utf8(&info_buf).unwrap()); } let frag_shader = gl::CreateShader(gl::FRAGMENT_SHADER); @@ -45,7 +46,7 @@ impl Shader { let mut success2 = 0; gl::GetShaderiv(frag_shader, gl::COMPILE_STATUS, &mut success2); #[cfg(debug_assertions)] - println!("frag shader {:?} compiled with status: {}", + debug!("frag shader {:?} compiled with status: {}", frag_obj.name, success2 ); @@ -53,7 +54,7 @@ impl Shader { let info_buf2 = [0u8; 512]; gl::GetShaderInfoLog(frag_shader as u32, 512, std::ptr::null_mut(), info_buf2.as_ptr() as *mut i8); #[cfg(debug_assertions)] - println!("frag shader info: {}", std::str::from_utf8(&info_buf2).unwrap()); + error!("frag shader info: {}", std::str::from_utf8(&info_buf2).unwrap()); } let shader_program = gl::CreateProgram(); diff --git a/src/ui/ui.rs b/src/ui/ui.rs index 6b1df6a..df6d318 100644 --- a/src/ui/ui.rs +++ b/src/ui/ui.rs @@ -1,9 +1,10 @@ use std::time::{SystemTime, UNIX_EPOCH}; use glad_gl::gl; +use log::{info, debug}; use serde::{Serialize, Deserialize}; -use crate::{camera::Camera, model, imgui_glfw_support, imgui_opengl_renderer, mesh, ui, log, utils}; +use crate::{camera::Camera, model, imgui_glfw_support, imgui_opengl_renderer, mesh, ui, logger, utils}; #[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct Settings { @@ -26,7 +27,7 @@ pub struct State { pub camera: Camera, pub objects: Vec, pub viewport_size: [f32; 2], - pub logger: log::Log, + pub logger: logger::WritableLog, pub settings: Settings, } @@ -48,7 +49,7 @@ impl Default for State { camera: Camera::new(), objects: vec![], viewport_size: [0.0, 0.0], - logger: log::Log::default(), + logger: logger::WritableLog::default(), settings: Settings::default(), } } @@ -210,10 +211,7 @@ fn draw_object_hierarchy(ui: &imgui::Ui, state: &mut State, idx: usize) -> bool ui.table_next_column(); if ui.small_button(format!("X###{}-{}", object.name.as_str(), idx)) { - let output = format!("Removing object {}", object.name); - println!("{}", output); - - state.logger.log(&output, log::LogLevel::Info); + info!("Removing object {}", object.name); return true; } if ui.checkbox("Selected", &mut (Some(state.objects[idx].id) == state.active_model)) { @@ -256,8 +254,9 @@ fn draw_console(ui: &imgui::Ui, state: &mut State) { ui.child_window("###ConsoleHistory") .size([0.0, -27.0]) .build(|| { - for line in state.logger.history.iter() { - let style = ui.push_style_color(imgui::StyleColor::Text, line.level.clone()); + for line in state.logger.arc.read().unwrap().history.iter() { + let style = ui.push_style_color(imgui::StyleColor::Text, line.level); + ui.text_wrapped(line.message.clone()); style.pop(); } @@ -268,7 +267,8 @@ fn draw_console(ui: &imgui::Ui, state: &mut State) { ui.separator(); if ui.button("Clear") { - state.logger.clear(); + let mut logger = state.logger.arc.write().unwrap(); + logger.clear(); } }); } @@ -369,21 +369,13 @@ fn draw_viewport(ui: &imgui::Ui, state: &mut State, texture: u32) { let _ = capture.save(save_path); let elapsed = now.elapsed(); - state.logger.log( - format!("Scene capture saved to: {} successfully", - save_path - .canonicalize() - .expect("Capture path to be canonicalized") - .to_str() - .expect("Capture path to be valid unicode")) - .as_str(), - log::LogLevel::Info); + info!("Scene capture saved to: {} successfully", save_path + .canonicalize() + .expect("Capture path to be canonicalized") + .to_str() + .expect("Capture path to be valid unicode")); - state.logger.log( - format!("Scene capture took: {}ms", - elapsed.as_millis()) - .as_str(), - log::LogLevel::Debug); + debug!("Scene capture took: {}ms", elapsed.as_millis()); unsafe { gl::BindFramebuffer(gl::FRAMEBUFFER, 0); diff --git a/src/utils.rs b/src/utils.rs index 180d72c..7c7c382 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,8 +3,9 @@ use std::path::PathBuf; use glad_gl::gl; use glm; use anyhow::{Result, Context}; +use ::log::{info, error}; -use crate::{log, model, ui, importer}; +use crate::{model, ui, importer}; pub enum SupportedFileExtensions { OBJ, @@ -66,18 +67,18 @@ pub fn import_models_from_paths(paths: &Vec, state: &mut ui::ui::State) for model_path in paths { let filename = model_path.file_name(); if model_path.is_dir() { - state.logger.log(&format!("Skipping directory \"{}\"", filename.unwrap().to_str().unwrap()), log::LogLevel::Info); + info!("Skipping directory \"{}\"", filename.unwrap().to_str().unwrap()); continue; } match model_path.extension() { Some(ext) => { if SupportedFileExtensions::from_str(ext.to_str().unwrap()).is_none() { - state.logger.log(&format!("Skipping file \"{}\" because it is not an OBJ or STL file", filename.unwrap().to_str().unwrap()), log::LogLevel::Info); + info!("Skipping file \"{}\" because it is not an OBJ or STL file", filename.unwrap().to_str().unwrap()); continue; } }, None => { - state.logger.log(&format!("Skipping file \"{}\" because it has no extension", filename.unwrap().to_str().unwrap()), log::LogLevel::Info); + info!("Skipping file \"{}\" because it has no extension", filename.unwrap().to_str().unwrap()); continue; } } @@ -88,19 +89,14 @@ pub fn import_models_from_paths(paths: &Vec, state: &mut ui::ui::State) state.active_model = Some(m.id); if let Some(model_name) = filename { - let msg = format!("Loaded model \"{}\"", model_name.to_str().unwrap()); - println!("{}", msg); - state.logger.log(&msg, log::LogLevel::Info); + info!("Loaded model \"{}\"", model_name.to_str().unwrap()); m.name = model_name.to_str().unwrap().to_string(); } state.objects.push(m); state.camera.focus_on_selected_model(state.active_model, &state.objects); }, Err(e) => { - let error = format!("Error loading model \"{}\": {}", model_path.to_str().unwrap(), e); - println!("{}", error); - - state.logger.log(&error, log::LogLevel::Error); + error!("Error loading model \"{}\": {}", model_path.to_str().unwrap(), e); } } }