feat: switch to systemd-journal-logger, add debug logging (v0.4.0)
Replace env_logger file-based logging with systemd-journal-logger for consistency with moonlock and native journalctl integration. Add debug-level logging at all decision points: config loading, user/session detection, avatar resolution, locale detection, IPC messages, login flow, and persistence. No credentials are ever logged.
This commit is contained in:
+29
-13
@@ -40,27 +40,39 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
|
||||
|
||||
let mut merged = Config::default();
|
||||
for path in paths {
|
||||
if let Ok(content) = fs::read_to_string(path) {
|
||||
if let Ok(parsed) = toml::from_str::<TomlConfig>(&content) {
|
||||
if let Some(appearance) = parsed.appearance {
|
||||
if let Some(bg) = appearance.background {
|
||||
// Resolve relative paths against config file directory
|
||||
let bg_path = PathBuf::from(&bg);
|
||||
if bg_path.is_absolute() {
|
||||
merged.background_path = Some(bg);
|
||||
} else if let Some(parent) = path.parent() {
|
||||
merged.background_path =
|
||||
Some(parent.join(&bg).to_string_lossy().to_string());
|
||||
match fs::read_to_string(path) {
|
||||
Ok(content) => {
|
||||
match toml::from_str::<TomlConfig>(&content) {
|
||||
Ok(parsed) => {
|
||||
log::debug!("Config loaded: {}", path.display());
|
||||
if let Some(appearance) = parsed.appearance {
|
||||
if let Some(bg) = appearance.background {
|
||||
// Resolve relative paths against config file directory
|
||||
let bg_path = PathBuf::from(&bg);
|
||||
if bg_path.is_absolute() {
|
||||
merged.background_path = Some(bg);
|
||||
} else if let Some(parent) = path.parent() {
|
||||
merged.background_path =
|
||||
Some(parent.join(&bg).to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
if appearance.gtk_theme.is_some() {
|
||||
merged.gtk_theme = appearance.gtk_theme;
|
||||
}
|
||||
}
|
||||
}
|
||||
if appearance.gtk_theme.is_some() {
|
||||
merged.gtk_theme = appearance.gtk_theme;
|
||||
Err(e) => {
|
||||
log::warn!("Config parse error in {}: {e}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
log::debug!("Config not found: {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("Config result: background={:?}, gtk_theme={:?}", merged.background_path, merged.gtk_theme);
|
||||
merged
|
||||
}
|
||||
|
||||
@@ -77,16 +89,20 @@ pub fn resolve_background_path_with(config: &Config, moonarch_wallpaper: &Path)
|
||||
if let Some(ref bg) = config.background_path {
|
||||
let path = PathBuf::from(bg);
|
||||
if path.is_file() {
|
||||
log::debug!("Wallpaper: using config path {}", path.display());
|
||||
return path;
|
||||
}
|
||||
log::debug!("Wallpaper: config path {} not found, trying fallbacks", path.display());
|
||||
}
|
||||
|
||||
// Moonarch ecosystem default
|
||||
if moonarch_wallpaper.is_file() {
|
||||
log::debug!("Wallpaper: using moonarch default {}", moonarch_wallpaper.display());
|
||||
return moonarch_wallpaper.to_path_buf();
|
||||
}
|
||||
|
||||
// GResource fallback path (loaded from compiled resources at runtime)
|
||||
log::debug!("Wallpaper: using GResource fallback");
|
||||
PathBuf::from(format!("{GRESOURCE_PREFIX}/wallpaper.jpg"))
|
||||
}
|
||||
|
||||
|
||||
+29
-1
@@ -163,6 +163,10 @@ pub fn create_greeter_window(
|
||||
let strings = load_strings(None);
|
||||
let all_users = users::get_users(None);
|
||||
let all_sessions = sessions::get_sessions(None, None);
|
||||
log::debug!("Greeter window: {} user(s), {} session(s)", all_users.len(), all_sessions.len());
|
||||
if let Some(ref theme) = config.gtk_theme {
|
||||
log::debug!("GTK theme: {theme}");
|
||||
}
|
||||
|
||||
let state = Rc::new(RefCell::new(GreeterState {
|
||||
selected_user: None,
|
||||
@@ -489,6 +493,7 @@ fn select_initial_user(
|
||||
.as_ref()
|
||||
.and_then(|name| users.iter().find(|u| &u.username == name))
|
||||
.unwrap_or(&users[0]);
|
||||
log::debug!("Initial user: {} (last_user={:?})", target.username, last_username);
|
||||
|
||||
switch_to_user(
|
||||
target,
|
||||
@@ -515,6 +520,7 @@ fn switch_to_user(
|
||||
sessions: &[Session],
|
||||
window: >k::ApplicationWindow,
|
||||
) {
|
||||
log::debug!("Switching to user: {}", user.username);
|
||||
{
|
||||
let mut s = state.borrow_mut();
|
||||
s.selected_user = Some(user.clone());
|
||||
@@ -531,8 +537,10 @@ fn switch_to_user(
|
||||
};
|
||||
|
||||
if let Some(texture) = cached {
|
||||
log::debug!("Avatar cache hit for {}", user.username);
|
||||
avatar_image.set_paintable(Some(&texture));
|
||||
} else {
|
||||
log::debug!("Avatar cache miss for {}", user.username);
|
||||
let avatar_path = users::get_avatar_path(&user.username, &user.home);
|
||||
if let Some(path) = avatar_path {
|
||||
// get_avatar_path already checks existence — go straight to loading
|
||||
@@ -558,6 +566,7 @@ fn set_avatar_from_file(
|
||||
// Reject oversized files
|
||||
if let Ok(meta) = std::fs::metadata(path) {
|
||||
if meta.len() > MAX_AVATAR_FILE_SIZE {
|
||||
log::debug!("Avatar file too large ({} bytes): {}", meta.len(), path.display());
|
||||
image.set_icon_name(Some("avatar-default-symbolic"));
|
||||
return;
|
||||
}
|
||||
@@ -574,7 +583,8 @@ fn set_avatar_from_file(
|
||||
}
|
||||
image.set_paintable(Some(&texture));
|
||||
}
|
||||
Err(_) => {
|
||||
Err(e) => {
|
||||
log::debug!("Failed to load avatar {}: {e}", path.display());
|
||||
image.set_icon_name(Some("avatar-default-symbolic"));
|
||||
}
|
||||
}
|
||||
@@ -590,10 +600,12 @@ fn set_default_avatar(
|
||||
{
|
||||
let s = state.borrow();
|
||||
if let Some(ref texture) = s.default_avatar_texture {
|
||||
log::debug!("Default avatar: using cached texture");
|
||||
image.set_paintable(Some(texture));
|
||||
return;
|
||||
}
|
||||
}
|
||||
log::debug!("Default avatar: tinting SVG from GResource");
|
||||
|
||||
let resource_path = users::get_default_avatar_path();
|
||||
if let Ok(bytes) =
|
||||
@@ -698,6 +710,7 @@ fn show_greetd_error(
|
||||
|
||||
/// Cancel any in-progress greetd session.
|
||||
fn cancel_pending_session(state: &Rc<RefCell<GreeterState>>) {
|
||||
log::debug!("Cancelling pending greetd session");
|
||||
let s = state.borrow();
|
||||
s.login_cancelled
|
||||
.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
@@ -731,6 +744,7 @@ fn attempt_login(
|
||||
password_entry: >k::PasswordEntry,
|
||||
session_dropdown: >k::DropDown,
|
||||
) {
|
||||
log::debug!("Login attempt for user: {}", user.username);
|
||||
let sock_path = match std::env::var("GREETD_SOCK") {
|
||||
Ok(p) if !p.is_empty() => p,
|
||||
_ => {
|
||||
@@ -740,6 +754,7 @@ fn attempt_login(
|
||||
};
|
||||
|
||||
// Validate socket path
|
||||
log::debug!("GREETD_SOCK: {sock_path}");
|
||||
let sock_pathbuf = PathBuf::from(&sock_path);
|
||||
if !sock_pathbuf.is_absolute() {
|
||||
show_error(
|
||||
@@ -886,9 +901,11 @@ fn login_worker(
|
||||
strings: &Strings,
|
||||
) -> Result<LoginResult, String> {
|
||||
if login_cancelled.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
log::debug!("Login cancelled before connect");
|
||||
return Ok(LoginResult::Cancelled);
|
||||
}
|
||||
|
||||
log::debug!("Connecting to greetd socket: {sock_path}");
|
||||
let mut sock = UnixStream::connect(sock_path).map_err(|e| e.to_string())?;
|
||||
if let Err(e) = sock.set_read_timeout(Some(std::time::Duration::from_secs(10))) {
|
||||
log::warn!("Failed to set read timeout: {e}");
|
||||
@@ -902,6 +919,7 @@ fn login_worker(
|
||||
}
|
||||
|
||||
// Step 1: Create session — if a stale session exists, cancel it and retry
|
||||
log::debug!("Creating greetd session for {username}");
|
||||
let mut response = ipc::create_session(&mut sock, username).map_err(|e| e.to_string())?;
|
||||
|
||||
if login_cancelled.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
@@ -909,6 +927,7 @@ fn login_worker(
|
||||
}
|
||||
|
||||
if response.get("type").and_then(|v| v.as_str()) == Some("error") {
|
||||
log::debug!("Stale session detected, cancelling and retrying");
|
||||
let _ = ipc::cancel_session(&mut sock);
|
||||
response = ipc::create_session(&mut sock, username).map_err(|e| e.to_string())?;
|
||||
if login_cancelled.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
@@ -927,6 +946,7 @@ fn login_worker(
|
||||
|
||||
// Step 2: Send password if auth message received
|
||||
if response.get("type").and_then(|v| v.as_str()) == Some("auth_message") {
|
||||
log::debug!("Sending auth response for {username}");
|
||||
response =
|
||||
ipc::post_auth_response(&mut sock, Some(password)).map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -953,6 +973,7 @@ fn login_worker(
|
||||
|
||||
// Step 3: Start session
|
||||
if response.get("type").and_then(|v| v.as_str()) == Some("success") {
|
||||
log::debug!("Auth successful, starting session: {exec_cmd}");
|
||||
let cmd = match split_shell_words(exec_cmd) {
|
||||
Some(words) if !words.is_empty() => words,
|
||||
_ => {
|
||||
@@ -981,6 +1002,7 @@ fn login_worker(
|
||||
}
|
||||
|
||||
if response.get("type").and_then(|v| v.as_str()) == Some("success") {
|
||||
log::info!("Login successful for {username}");
|
||||
return Ok(LoginResult::Success {
|
||||
username: username.to_string(),
|
||||
});
|
||||
@@ -1039,8 +1061,10 @@ fn load_last_user_from(path: &Path) -> Option<String> {
|
||||
let content = std::fs::read_to_string(path).ok()?;
|
||||
let username = content.trim();
|
||||
if is_valid_username(username) {
|
||||
log::debug!("Loaded last user: {username}");
|
||||
Some(username.to_string())
|
||||
} else {
|
||||
log::debug!("Invalid last user in {}", path.display());
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1050,6 +1074,7 @@ fn save_last_user(username: &str) {
|
||||
}
|
||||
|
||||
fn save_last_user_to(path: &Path, username: &str) {
|
||||
log::debug!("Saving last user: {username}");
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
@@ -1072,8 +1097,10 @@ fn load_last_session_from(path: &Path) -> Option<String> {
|
||||
let content = std::fs::read_to_string(path).ok()?;
|
||||
let name = content.trim();
|
||||
if is_valid_session_name(name) {
|
||||
log::debug!("Loaded last session: {name}");
|
||||
Some(name.to_string())
|
||||
} else {
|
||||
log::debug!("Invalid last session in {}", path.display());
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1101,6 +1128,7 @@ fn save_last_session(username: &str, session_name: &str) {
|
||||
}
|
||||
|
||||
fn save_last_session_to(path: &Path, session_name: &str) {
|
||||
log::debug!("Saving last session: {session_name}");
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::io::Write;
|
||||
let _ = std::fs::OpenOptions::new()
|
||||
|
||||
+8
-3
@@ -127,10 +127,15 @@ pub fn detect_locale() -> String {
|
||||
.filter(|s| !s.is_empty())
|
||||
.or_else(|| read_lang_from_conf(Path::new(DEFAULT_LOCALE_CONF)));
|
||||
|
||||
match lang {
|
||||
Some(l) => parse_lang_prefix(&l),
|
||||
let result = match lang {
|
||||
Some(ref l) => parse_lang_prefix(l),
|
||||
None => "en".to_string(),
|
||||
}
|
||||
};
|
||||
log::debug!("Detected locale: {result} (source: {})", match lang {
|
||||
Some(_) => "LANG env or locale.conf",
|
||||
None => "default",
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
/// Return the string table for the given locale, defaulting to English.
|
||||
|
||||
@@ -84,6 +84,9 @@ pub fn send_message(
|
||||
return Err(IpcError::PayloadTooLarge(payload.len()));
|
||||
}
|
||||
|
||||
let msg_type = msg.get("type").and_then(|v| v.as_str()).unwrap_or("unknown");
|
||||
log::debug!("IPC send: type={msg_type}, size={} bytes", payload.len());
|
||||
|
||||
let header = (payload.len() as u32).to_le_bytes();
|
||||
stream.write_all(&header)?;
|
||||
stream.write_all(&payload)?;
|
||||
@@ -101,6 +104,8 @@ pub fn recv_message(stream: &mut UnixStream) -> Result<serde_json::Value, IpcErr
|
||||
|
||||
let payload = recv_payload(stream, length)?;
|
||||
let value: serde_json::Value = serde_json::from_slice(&payload)?;
|
||||
let msg_type = value.get("type").and_then(|v| v.as_str()).unwrap_or("unknown");
|
||||
log::debug!("IPC recv: type={msg_type}, size={length} bytes");
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
|
||||
+9
-21
@@ -13,8 +13,6 @@ use gdk4 as gdk;
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::{self as gtk, gio};
|
||||
use gtk4_layer_shell::LayerShell;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn load_css(display: &gdk::Display) {
|
||||
let css_provider = gtk::CssProvider::new();
|
||||
css_provider.load_from_resource("/dev/moonarch/moongreet/style.css");
|
||||
@@ -48,13 +46,16 @@ fn activate(app: >k::Application) {
|
||||
}
|
||||
};
|
||||
|
||||
log::debug!("Display: {:?}", display);
|
||||
load_css(&display);
|
||||
|
||||
// Load config and resolve wallpaper
|
||||
let config = config::load_config(None);
|
||||
let bg_path = config::resolve_background_path(&config);
|
||||
log::debug!("Background path: {}", bg_path.display());
|
||||
|
||||
let use_layer_shell = std::env::var("MOONGREET_NO_LAYER_SHELL").is_err();
|
||||
log::debug!("Layer shell: {use_layer_shell}");
|
||||
|
||||
// Main greeter window (login UI) — compositor picks focused monitor
|
||||
let greeter_window = greeter::create_greeter_window(&bg_path, &config, app);
|
||||
@@ -66,6 +67,7 @@ fn activate(app: >k::Application) {
|
||||
// Wallpaper-only windows on all monitors (only with layer shell)
|
||||
if use_layer_shell {
|
||||
let monitors = display.monitors();
|
||||
log::debug!("Monitor count: {}", monitors.n_items());
|
||||
for i in 0..monitors.n_items() {
|
||||
if let Some(monitor) = monitors
|
||||
.item(i)
|
||||
@@ -81,25 +83,11 @@ fn activate(app: >k::Application) {
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut builder = env_logger::Builder::from_default_env();
|
||||
builder.filter_level(log::LevelFilter::Info);
|
||||
|
||||
// Try file logging to /var/cache/moongreet/ — fall back to stderr
|
||||
let log_dir = PathBuf::from("/var/cache/moongreet");
|
||||
if log_dir.is_dir() {
|
||||
let log_file = log_dir.join("moongreet.log");
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
if let Ok(file) = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.mode(0o640)
|
||||
.open(&log_file)
|
||||
{
|
||||
builder.target(env_logger::Target::Pipe(Box::new(file)));
|
||||
}
|
||||
}
|
||||
|
||||
builder.init();
|
||||
systemd_journal_logger::JournalLog::new()
|
||||
.unwrap()
|
||||
.install()
|
||||
.unwrap();
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -23,6 +23,7 @@ impl std::error::Error for PowerError {}
|
||||
|
||||
/// Run a command and return a PowerError on failure.
|
||||
fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), PowerError> {
|
||||
log::debug!("Power action: {action} ({program} {args:?})");
|
||||
let child = Command::new(program)
|
||||
.args(args)
|
||||
.spawn()
|
||||
@@ -38,6 +39,9 @@ fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(),
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
|
||||
if output.status.success() {
|
||||
log::debug!("Power action {action} completed successfully");
|
||||
}
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(PowerError::CommandFailed {
|
||||
|
||||
+16
-3
@@ -46,8 +46,17 @@ fn parse_desktop_file(path: &Path, session_type: &str) -> Option<Session> {
|
||||
}
|
||||
}
|
||||
|
||||
let name = name.filter(|s| !s.is_empty())?;
|
||||
let exec_cmd = exec_cmd.filter(|s| !s.is_empty())?;
|
||||
let name = name.filter(|s| !s.is_empty());
|
||||
let exec_cmd = exec_cmd.filter(|s| !s.is_empty());
|
||||
|
||||
if name.is_none() || exec_cmd.is_none() {
|
||||
log::debug!("Skipping {}: missing Name={} Exec={}", path.display(),
|
||||
name.is_some(), exec_cmd.is_some());
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = name?;
|
||||
let exec_cmd = exec_cmd?;
|
||||
|
||||
Some(Session {
|
||||
name,
|
||||
@@ -74,7 +83,10 @@ pub fn get_sessions(
|
||||
for (dirs, session_type) in [(wayland, "wayland"), (xsession, "x11")] {
|
||||
for directory in dirs {
|
||||
let entries = match fs::read_dir(directory) {
|
||||
Ok(e) => e,
|
||||
Ok(e) => {
|
||||
log::debug!("Scanning session directory: {}", directory.display());
|
||||
e
|
||||
}
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
@@ -93,6 +105,7 @@ pub fn get_sessions(
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("Found {} session(s)", sessions.len());
|
||||
sessions
|
||||
}
|
||||
|
||||
|
||||
+20
-9
@@ -46,7 +46,10 @@ pub fn get_users(passwd_path: Option<&Path>) -> Vec<User> {
|
||||
|
||||
let content = match fs::read_to_string(path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Vec::new(),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to read passwd file {}: {e}", path.display());
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
let mut users = Vec::new();
|
||||
@@ -88,6 +91,7 @@ pub fn get_users(passwd_path: Option<&Path>) -> Vec<User> {
|
||||
});
|
||||
}
|
||||
|
||||
log::debug!("Found {} login user(s)", users.len());
|
||||
users
|
||||
}
|
||||
|
||||
@@ -106,21 +110,28 @@ pub fn get_avatar_path_with(
|
||||
// AccountsService icon takes priority
|
||||
if accountsservice_dir.exists() {
|
||||
let icon = accountsservice_dir.join(username);
|
||||
if let Ok(meta) = icon.symlink_metadata()
|
||||
&& !meta.file_type().is_symlink()
|
||||
{
|
||||
return Some(icon);
|
||||
if let Ok(meta) = icon.symlink_metadata() {
|
||||
if meta.file_type().is_symlink() {
|
||||
log::warn!("Rejecting symlink avatar for {username}: {}", icon.display());
|
||||
} else {
|
||||
log::debug!("Avatar for {username}: AccountsService {}", icon.display());
|
||||
return Some(icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ~/.face fallback
|
||||
let face = home.join(".face");
|
||||
if let Ok(meta) = face.symlink_metadata()
|
||||
&& !meta.file_type().is_symlink()
|
||||
{
|
||||
return Some(face);
|
||||
if let Ok(meta) = face.symlink_metadata() {
|
||||
if meta.file_type().is_symlink() {
|
||||
log::warn!("Rejecting symlink avatar for {username}: {}", face.display());
|
||||
} else {
|
||||
log::debug!("Avatar for {username}: ~/.face {}", face.display());
|
||||
return Some(face);
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("No avatar found for {username}");
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user