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]
|
[dependencies]
|
||||||
chrono = "0.4.42"
|
chrono = "0.4.42"
|
||||||
|
colog = { version = "1.4.0", optional = true }
|
||||||
|
log = { version = "0.4.29", features = ["serde"] }
|
||||||
serde = "1.0.219"
|
serde = "1.0.219"
|
||||||
slint = "1.15.1"
|
slint = "1.15.1"
|
||||||
toml = "1.1.2"
|
toml = "1.1.2"
|
||||||
|
|
@ -16,5 +18,7 @@ slint-build = "1.15.1"
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["stdout_logger"]
|
||||||
light = []
|
light = []
|
||||||
dark = []
|
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/))
|
- 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
|
### Feature flags
|
||||||
By default Aliveline compiles with theme autodetection, provided by Slint, which sometimes does not work on Linux.
|
By default Aliveline compiles with theme autodetection, provided by Slint.
|
||||||
You can use these flags to compile Aliveline with selected theme:
|
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`
|
- `light`
|
||||||
- `dark`
|
- `dark`
|
||||||
|
|
||||||
### Instructions
|
### Instructions
|
||||||
Run `cargo build --release`
|
Run `cargo build --release`
|
||||||
Pass features in build command with `-F foo` or `--feature foo`
|
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]`
|
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.
|
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
|
# 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 may be relative to config directory or absolute
|
||||||
[paths]
|
[paths]
|
||||||
logs = "logs"
|
logs = "logs"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::{collections::HashMap, hash::{DefaultHasher, Hash, Hasher}};
|
use std::{collections::HashMap, hash::{DefaultHasher, Hash, Hasher}};
|
||||||
|
use log::warn;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use slint::Color;
|
use slint::Color;
|
||||||
|
|
||||||
|
|
@ -92,7 +93,13 @@ impl Colors {
|
||||||
/// Get either the color or fallback color
|
/// Get either the color or fallback color
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_color(&self, color: &ConfigColor) -> Color {
|
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
|
/// Compute hash and choose a color for the given event string
|
||||||
|
|
@ -112,7 +119,8 @@ impl Colors {
|
||||||
chosen = new_count;
|
chosen = new_count;
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
return &self.events[id];
|
let out = &self.events[id];
|
||||||
|
return out;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use log::LevelFilter;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use color::Colors;
|
use color::Colors;
|
||||||
|
|
||||||
|
|
@ -22,7 +23,7 @@ impl Default for Paths {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration struct
|
/// Configuration struct
|
||||||
#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq, Debug)]
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Directory, where config is located
|
/// Directory, where config is located
|
||||||
|
|
@ -32,6 +33,19 @@ pub struct Config {
|
||||||
pub colors: Colors,
|
pub colors: Colors,
|
||||||
/// Config paths
|
/// Config paths
|
||||||
pub paths: 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 {
|
impl Config {
|
||||||
|
|
|
||||||
15
src/lib.rs
15
src/lib.rs
|
|
@ -1,4 +1,5 @@
|
||||||
use config::Config;
|
use config::Config;
|
||||||
|
use ::log::LevelFilter;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Configuration module
|
/// Configuration module
|
||||||
|
|
@ -39,11 +40,17 @@ pub fn load_config() -> Config {
|
||||||
];
|
];
|
||||||
for (place_var, place) in places {
|
for (place_var, place) in places {
|
||||||
if let Some(conf) = try_config_path(place_var, place.as_slice()) {
|
if let Some(conf) = try_config_path(place_var, place.as_slice()) {
|
||||||
println!("Found config at ${place_var} / {place:?}");
|
|
||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
println!("Config not found at ${place_var} / {place:?}");
|
|
||||||
}
|
}
|
||||||
println!("Using config path ./config.toml");
|
let default_path = PathBuf::from("./config.toml");
|
||||||
Config::new(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.
|
// 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")]
|
#![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 aliveline::{config::{Config, color::Colors}, load_config, log::{Event, Log}};
|
||||||
use chrono::{Datelike, Timelike};
|
use chrono::{Datelike, Timelike};
|
||||||
|
use log::{error, info};
|
||||||
use slint::{Color, Model, ModelRc, SharedString, ToSharedString, VecModel, Weak};
|
use slint::{Color, Model, ModelRc, SharedString, ToSharedString, VecModel, Weak};
|
||||||
use toml::value::{Date as TomlDate, Time};
|
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>) {
|
fn load_log(ui_weak: Weak<AppWindow>, log: Arc<Mutex<Log>>, config: Arc<Config>) {
|
||||||
let ui = ui_weak.unwrap();
|
let ui = ui_weak.unwrap();
|
||||||
let log_guard = log.lock().expect("Log shouldn't be used twice");
|
let events: Vec<TimelineEvent> = {
|
||||||
let events: Vec<TimelineEvent> = (*log_guard)
|
let log_guard = lock_mutex(&log);
|
||||||
.events
|
info!("Loading log {}", log_guard.date);
|
||||||
.iter()
|
(*log_guard)
|
||||||
.map(|event| TimelineEvent::from_event(event.clone(), &config.colors))
|
.events
|
||||||
.collect();
|
.iter()
|
||||||
|
.map(|event| TimelineEvent::from_event(event.clone(), &config.colors))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
let in_progress = events.iter().any(|event| !event.finished);
|
let in_progress = events.iter().any(|event| !event.finished);
|
||||||
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
||||||
let mut state = ui.get_record_state();
|
let mut state = ui.get_record_state();
|
||||||
|
|
@ -97,7 +107,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
year: now.year() as u16
|
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 writing_log: Arc<Mutex<Log>> = Arc::new(Mutex::new(Log::load_from(&config, date)));
|
||||||
|
|
||||||
let ui_weak = ui.as_weak();
|
let ui_weak = ui.as_weak();
|
||||||
|
|
@ -137,9 +152,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
let log = writing_log.clone();
|
let log = writing_log.clone();
|
||||||
move || {
|
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) {
|
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 log = writing_log.clone();
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
move |event_name: SharedString| {
|
move |event_name: SharedString| {
|
||||||
|
info!("Starting event \"{event_name}\"");
|
||||||
|
|
||||||
let ui = ui_weak.unwrap();
|
let ui = ui_weak.unwrap();
|
||||||
|
|
||||||
let state = ui.get_record_state();
|
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()));
|
(*log_guard).events.push(Event::from(event.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,8 +229,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let event_id = events.iter()
|
let event_id = events.iter()
|
||||||
.position(|data| !data.finished)
|
.position(|data| !data.finished)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let event = events.row_data(event_id)
|
let Some(event) = events.row_data(event_id) else {
|
||||||
.expect("stop-event called without unfinished events");
|
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());
|
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()));
|
log_guard.events.push(Event::from(new_event.clone()));
|
||||||
let index = log_guard.events.iter().position(|data| !data.finished).unwrap();
|
let index = log_guard.events.iter().position(|data| !data.finished).unwrap();
|
||||||
log_guard.events.swap_remove(index);
|
log_guard.events.swap_remove(index);
|
||||||
|
|
@ -254,13 +275,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
move || {
|
move || {
|
||||||
let ui = ui_weak.unwrap();
|
let ui = ui_weak.unwrap();
|
||||||
let prev_event_name = {
|
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) {
|
match log_guard.events.len().checked_sub(2) {
|
||||||
Some(prev_index) => {
|
Some(prev_index) => {
|
||||||
let prev_event = log_guard.events
|
let prev_event = log_guard.events[prev_index].name
|
||||||
.get(prev_index)
|
|
||||||
.expect("Index is already checked")
|
|
||||||
.name
|
|
||||||
.clone();
|
.clone();
|
||||||
Some(prev_event)
|
Some(prev_event)
|
||||||
},
|
},
|
||||||
|
|
@ -277,9 +295,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
move || {
|
move || {
|
||||||
let ui = ui_weak.unwrap();
|
let ui = ui_weak.unwrap();
|
||||||
|
info!("Starting new day");
|
||||||
let new_event: Option<Event> = {
|
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);
|
let maybe_unfinished_event = log_guard.events.iter().find(|event| !event.finished);
|
||||||
match maybe_unfinished_event {
|
match maybe_unfinished_event {
|
||||||
|
|
@ -287,11 +305,13 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
_ => None
|
_ => 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();
|
log_guard.events.clear();
|
||||||
let now = chrono::Local::now();
|
let now = chrono::Local::now();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue