log: logging to file, console and stdout

This commit is contained in:
2023-10-25 03:05:53 +02:00
parent 23e127e908
commit 5eaefd9aab
13 changed files with 276 additions and 115 deletions
+6 -4
View File
@@ -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<HashMap<String, Material>, Box<dyn std::e
Some(MtlToken::DiffuseTexture) => {
// 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<Object, Box<d
materials.extend(m);
},
Err(e) => {
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<Object, Box<d
Some(ObjToken::Line) | Some(ObjToken::Point) => {
// 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() {
+3 -1
View File
@@ -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<Object, Box<dyn std::error::E
parse_binary_stl(file)?
};
let elapsed = now.elapsed();
println!("Loaded in {} ms", elapsed.as_millis());
trace!("Loaded in {} ms", elapsed.as_millis());
Ok(obj)
}
+2 -1
View File
@@ -1,6 +1,7 @@
use std::{os::unix::net::{UnixListener, UnixStream}, path::PathBuf, io::{Write, Read}, fs::File, process::exit, thread, sync::mpsc::Receiver};
use fs4::FileExt;
use log::error;
fn create_named_pipe(pipe_path: PathBuf) -> UnixListener {
if pipe_path.exists() {
@@ -58,7 +59,7 @@ pub fn init(lock_file: &File, args_paths: Vec<PathBuf>, one_instance: bool) -> O
ipc_tx.send(paths).unwrap();
}
Err(err) => {
println!("Error: {:?}", err);
error!("Error: {:?}", err);
break;
}
}
+1 -1
View File
@@ -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;
-64
View File
@@ -1,64 +0,0 @@
#[derive(Clone)]
pub enum LogLevel {
Debug,
Info,
Warning,
Error,
}
impl From<LogLevel> for mint::Vector4<f32> {
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<LogMessage>,
}
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(),
}
}
}
+100
View File
@@ -0,0 +1,100 @@
use std::{io::Write, sync::{Arc, RwLock}};
#[derive(Copy, Clone)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
}
impl From<LogLevel> for mint::Vector4<f32> {
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<log::LevelFilter> 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<LogMessage>,
}
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<RwLock<Log>>,
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<usize> {
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(())
}
}
+40 -3
View File
@@ -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<dyn std::error::Error>> {
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<String> = env::args().collect();
@@ -36,6 +72,7 @@ fn main() -> anyhow::Result<(), Box<dyn std::error::Error>> {
let mut state = ui::State{
settings,
logger,
..Default::default()
};
+5 -4
View File
@@ -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();
+16 -24
View File
@@ -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<model::Model>,
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);
+7 -11
View File
@@ -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<PathBuf>, 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<PathBuf>, 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);
}
}
}