feat: stdout logging capabilities
- Added feature "stdout_logging" (see README) - Added config.loglevel option (see config.toml) - Logging some common things - Hopefully fix on_new_day_started
This commit is contained in:
parent
4c2ddde26f
commit
28b842b8c4
8 changed files with 669 additions and 550 deletions
1102
Cargo.lock
generated
1102
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -5,6 +5,8 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
chrono = "0.4.42"
|
||||
colog = { version = "1.4.0", optional = true }
|
||||
log = { version = "0.4.29", features = ["serde"] }
|
||||
serde = "1.0.219"
|
||||
slint = "1.15.1"
|
||||
toml = "1.1.2"
|
||||
|
|
@ -16,5 +18,7 @@ slint-build = "1.15.1"
|
|||
opt-level = 3
|
||||
|
||||
[features]
|
||||
default = ["stdout_logger"]
|
||||
light = []
|
||||
dark = []
|
||||
stdout_logger = ["colog"]
|
||||
|
|
|
|||
|
|
@ -40,14 +40,17 @@ _Note: if event is not finished yet, it may have_ `end = start`.
|
|||
- Slint dependencies (see [Platforms](https://docs.slint.dev/latest/docs/slint/guide/platforms/desktop/) and [Backends & Renderers](https://docs.slint.dev/latest/docs/slint/guide/backends-and-renderers/backends_and_renderers/))
|
||||
|
||||
### Feature flags
|
||||
By default Aliveline compiles with theme autodetection, provided by Slint, which sometimes does not work on Linux.
|
||||
You can use these flags to compile Aliveline with selected theme:
|
||||
By default Aliveline compiles with theme autodetection, provided by Slint.
|
||||
On Linux, it depends on [org.freedesktop.portal.Settings](https://wiki.archlinux.org/title/XDG_Desktop_Portal).
|
||||
On Windows it should work out of the box
|
||||
You can also use one of these flags to compile Aliveline with selected theme:
|
||||
- `light`
|
||||
- `dark`
|
||||
|
||||
### Instructions
|
||||
Run `cargo build --release`
|
||||
Pass features in build command with `-F foo` or `--feature foo`
|
||||
Pass `--no-default-features` to disable stdout logging
|
||||
Resulting binary will be located at `target/release/aliveline[.exe]`
|
||||
|
||||
If compilation fails, double check that you have all required dependencies. If it still fails, file an issue on [Codeberg](https://codeberg.org/2ndbeam/aliveline/issues), including logs and system info.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
# Default Aliveline config
|
||||
|
||||
# Set stdout logging level from 0 to 5, higher is more verbose
|
||||
loglevel = 3
|
||||
|
||||
# Paths may be relative to config directory or absolute
|
||||
[paths]
|
||||
logs = "logs"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::{collections::HashMap, hash::{DefaultHasher, Hash, Hasher}};
|
||||
use log::warn;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use slint::Color;
|
||||
|
||||
|
|
@ -92,7 +93,13 @@ impl Colors {
|
|||
/// Get either the color or fallback color
|
||||
#[inline(always)]
|
||||
pub fn get_color(&self, color: &ConfigColor) -> Color {
|
||||
self.try_get_color(color).unwrap_or_else(|| self.fallback_color())
|
||||
self.try_get_color(color)
|
||||
.unwrap_or_else(|| {
|
||||
// raw color can't return none so it's an alias
|
||||
let ConfigColor::Alias(alias) = color else { unreachable!() };
|
||||
warn!("Color alias \"{alias}\" is not defined, using fallback color");
|
||||
self.fallback_color()
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute hash and choose a color for the given event string
|
||||
|
|
@ -112,7 +119,8 @@ impl Colors {
|
|||
chosen = new_count;
|
||||
},
|
||||
None => {
|
||||
return &self.events[id];
|
||||
let out = &self.events[id];
|
||||
return out;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
use log::LevelFilter;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use color::Colors;
|
||||
|
||||
|
|
@ -22,7 +23,7 @@ impl Default for Paths {
|
|||
}
|
||||
|
||||
/// Configuration struct
|
||||
#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
/// Directory, where config is located
|
||||
|
|
@ -32,6 +33,19 @@ pub struct Config {
|
|||
pub colors: Colors,
|
||||
/// Config paths
|
||||
pub paths: Paths,
|
||||
/// Logging level (0 to 5, greater is more verbose)
|
||||
pub loglevel: LevelFilter,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
conf_path: PathBuf::default(),
|
||||
colors: Colors::default(),
|
||||
paths: Paths::default(),
|
||||
loglevel: LevelFilter::Info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
|
|
|||
15
src/lib.rs
15
src/lib.rs
|
|
@ -1,4 +1,5 @@
|
|||
use config::Config;
|
||||
use ::log::LevelFilter;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Configuration module
|
||||
|
|
@ -39,11 +40,17 @@ pub fn load_config() -> Config {
|
|||
];
|
||||
for (place_var, place) in places {
|
||||
if let Some(conf) = try_config_path(place_var, place.as_slice()) {
|
||||
println!("Found config at ${place_var} / {place:?}");
|
||||
return conf;
|
||||
}
|
||||
println!("Config not found at ${place_var} / {place:?}");
|
||||
}
|
||||
println!("Using config path ./config.toml");
|
||||
Config::new(PathBuf::from("./config.toml"))
|
||||
let default_path = PathBuf::from("./config.toml");
|
||||
Config::load(default_path.clone()).unwrap_or_else(|| { Config::new(default_path) })
|
||||
}
|
||||
|
||||
/// [colog::init] with given log level filter
|
||||
#[cfg(feature = "stdout_logger")]
|
||||
pub fn init_stdout_logger(filter: LevelFilter) {
|
||||
colog::default_builder()
|
||||
.filter(None, filter)
|
||||
.init();
|
||||
}
|
||||
|
|
|
|||
68
src/main.rs
68
src/main.rs
|
|
@ -1,10 +1,11 @@
|
|||
// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms.
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use std::{error::Error, rc::Rc, sync::{Arc, Mutex}};
|
||||
use std::{error::Error, rc::Rc, sync::{Arc, Mutex, MutexGuard}};
|
||||
|
||||
use aliveline::{config::{Config, color::Colors}, load_config, log::{Event, Log}};
|
||||
use chrono::{Datelike, Timelike};
|
||||
use log::{error, info};
|
||||
use slint::{Color, Model, ModelRc, SharedString, ToSharedString, VecModel, Weak};
|
||||
use toml::value::{Date as TomlDate, Time};
|
||||
|
||||
|
|
@ -60,14 +61,23 @@ impl From<TimelineEvent> for Event {
|
|||
}
|
||||
}
|
||||
|
||||
fn lock_mutex<'a, T: Send + Sync>(mutex: &'a Arc<Mutex<T>>) -> MutexGuard<'a, T> {
|
||||
mutex.lock().unwrap_or_else(|e| {
|
||||
panic!("Couldn't acquire mutex lock: {e}");
|
||||
})
|
||||
}
|
||||
|
||||
fn load_log(ui_weak: Weak<AppWindow>, log: Arc<Mutex<Log>>, config: Arc<Config>) {
|
||||
let ui = ui_weak.unwrap();
|
||||
let log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
let events: Vec<TimelineEvent> = (*log_guard)
|
||||
.events
|
||||
.iter()
|
||||
.map(|event| TimelineEvent::from_event(event.clone(), &config.colors))
|
||||
.collect();
|
||||
let events: Vec<TimelineEvent> = {
|
||||
let log_guard = lock_mutex(&log);
|
||||
info!("Loading log {}", log_guard.date);
|
||||
(*log_guard)
|
||||
.events
|
||||
.iter()
|
||||
.map(|event| TimelineEvent::from_event(event.clone(), &config.colors))
|
||||
.collect()
|
||||
};
|
||||
let in_progress = events.iter().any(|event| !event.finished);
|
||||
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
||||
let mut state = ui.get_record_state();
|
||||
|
|
@ -97,7 +107,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
year: now.year() as u16
|
||||
};
|
||||
|
||||
let config: Arc<Config> = Arc::new(load_config());
|
||||
let config = load_config();
|
||||
|
||||
#[cfg(feature = "stdout_logger")]
|
||||
aliveline::init_stdout_logger(config.loglevel);
|
||||
|
||||
let config: Arc<Config> = Arc::new(config);
|
||||
let writing_log: Arc<Mutex<Log>> = Arc::new(Mutex::new(Log::load_from(&config, date)));
|
||||
|
||||
let ui_weak = ui.as_weak();
|
||||
|
|
@ -137,9 +152,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let config = config.clone();
|
||||
let log = writing_log.clone();
|
||||
move || {
|
||||
let log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
let log_guard = lock_mutex(&log);
|
||||
if let Err(error) = (*log_guard).save(&config) {
|
||||
eprintln!("Error occured while saving log: {error}");
|
||||
error!("Couldn't save log: {error}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -169,6 +184,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let log = writing_log.clone();
|
||||
let config = config.clone();
|
||||
move |event_name: SharedString| {
|
||||
info!("Starting event \"{event_name}\"");
|
||||
|
||||
let ui = ui_weak.unwrap();
|
||||
|
||||
let state = ui.get_record_state();
|
||||
|
|
@ -188,7 +205,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
};
|
||||
|
||||
{
|
||||
let mut log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
let mut log_guard = lock_mutex(&log);
|
||||
(*log_guard).events.push(Event::from(event.clone()));
|
||||
}
|
||||
|
||||
|
|
@ -212,8 +229,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let event_id = events.iter()
|
||||
.position(|data| !data.finished)
|
||||
.unwrap();
|
||||
let event = events.row_data(event_id)
|
||||
.expect("stop-event called without unfinished events");
|
||||
let Some(event) = events.row_data(event_id) else {
|
||||
error!("Couldn't find event to stop");
|
||||
return;
|
||||
};
|
||||
|
||||
info!("Stopping event \"{}\"", event.label);
|
||||
|
||||
let (background_color, text_color) = TimelineEvent::colors(&config.colors, event.label.as_str());
|
||||
|
||||
|
|
@ -227,7 +248,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
};
|
||||
|
||||
{
|
||||
let mut log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
let mut log_guard = lock_mutex(&log);
|
||||
log_guard.events.push(Event::from(new_event.clone()));
|
||||
let index = log_guard.events.iter().position(|data| !data.finished).unwrap();
|
||||
log_guard.events.swap_remove(index);
|
||||
|
|
@ -254,13 +275,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
move || {
|
||||
let ui = ui_weak.unwrap();
|
||||
let prev_event_name = {
|
||||
let log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
let log_guard = lock_mutex(&log);
|
||||
match log_guard.events.len().checked_sub(2) {
|
||||
Some(prev_index) => {
|
||||
let prev_event = log_guard.events
|
||||
.get(prev_index)
|
||||
.expect("Index is already checked")
|
||||
.name
|
||||
let prev_event = log_guard.events[prev_index].name
|
||||
.clone();
|
||||
Some(prev_event)
|
||||
},
|
||||
|
|
@ -277,9 +295,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let config = config.clone();
|
||||
move || {
|
||||
let ui = ui_weak.unwrap();
|
||||
|
||||
info!("Starting new day");
|
||||
let new_event: Option<Event> = {
|
||||
let log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
let log_guard = lock_mutex(&log);
|
||||
|
||||
let maybe_unfinished_event = log_guard.events.iter().find(|event| !event.finished);
|
||||
match maybe_unfinished_event {
|
||||
|
|
@ -287,11 +305,13 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
_ => None
|
||||
}
|
||||
};
|
||||
|
||||
ui.invoke_stop_event();
|
||||
|
||||
if new_event.is_some() {
|
||||
ui.invoke_stop_event();
|
||||
}
|
||||
|
||||
{
|
||||
let mut log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
let mut log_guard = lock_mutex(&log);
|
||||
log_guard.events.clear();
|
||||
let now = chrono::Local::now();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue