feat: record-daemon logs to a file
This commit is contained in:
Generated
+21
@@ -2418,6 +2418,7 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-appender",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"uuid",
|
"uuid",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
@@ -3074,6 +3075,12 @@ version = "2.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "symlink"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.117"
|
version = "2.0.117"
|
||||||
@@ -3255,6 +3262,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
|
"itoa",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -3520,6 +3528,19 @@ dependencies = [
|
|||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-appender"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"symlink",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"time",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.31"
|
version = "0.1.31"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ anyhow = "1"
|
|||||||
# Logging
|
# Logging
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
tracing-appender = "0.2"
|
||||||
|
|
||||||
# UUID for recording IDs
|
# UUID for recording IDs
|
||||||
uuid = { version = "1", features = ["v4", "serde"] }
|
uuid = { version = "1", features = ["v4", "serde"] }
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ struct Args {
|
|||||||
/// Socket path for IPC.
|
/// Socket path for IPC.
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
socket: Option<std::path::PathBuf>,
|
socket: Option<std::path::PathBuf>,
|
||||||
|
|
||||||
|
/// Path to log file. If not specified, logs to stdout/stderr.
|
||||||
|
#[arg(short, long, value_name = "PATH")]
|
||||||
|
log_file: Option<std::path::PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main daemon structure.
|
/// Main daemon structure.
|
||||||
@@ -579,15 +583,49 @@ impl Daemon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize logging.
|
/// Initialize logging with optional file output.
|
||||||
fn init_logging(level: &str) {
|
fn init_logging(level: &str, log_file: Option<&std::path::Path>) {
|
||||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(level));
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(level));
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
if let Some(log_path) = log_file {
|
||||||
.with(filter)
|
// Ensure parent directory exists
|
||||||
.with(tracing_subscriber::fmt::layer())
|
if let Some(parent) = log_path.parent() {
|
||||||
.init();
|
let _ = std::fs::create_dir_all(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use file appender for logging
|
||||||
|
let prefix = log_path
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("daemon");
|
||||||
|
|
||||||
|
let file_appender = tracing_appender::rolling::RollingFileAppender::builder()
|
||||||
|
.rotation(tracing_appender::rolling::Rotation::DAILY)
|
||||||
|
.filename_prefix(prefix)
|
||||||
|
.filename_suffix("log")
|
||||||
|
.build(log_path.parent().unwrap_or(std::path::Path::new(".")))
|
||||||
|
.expect("Failed to create log file appender");
|
||||||
|
|
||||||
|
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
|
||||||
|
|
||||||
|
// Store the guard to prevent it from being dropped
|
||||||
|
// We leak it intentionally to keep logging working for the daemon's lifetime
|
||||||
|
std::mem::forget(_guard);
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(filter)
|
||||||
|
.with(tracing_subscriber::fmt::layer().with_writer(non_blocking))
|
||||||
|
.init();
|
||||||
|
|
||||||
|
eprintln!("Logging to file: {}", log_path.display());
|
||||||
|
} else {
|
||||||
|
// Log to stdout/stderr
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(filter)
|
||||||
|
.with(tracing_subscriber::fmt::layer())
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
|
||||||
// Set up panic hook to log panics
|
// Set up panic hook to log panics
|
||||||
std::panic::set_hook(Box::new(|panic_info| {
|
std::panic::set_hook(Box::new(|panic_info| {
|
||||||
@@ -614,7 +652,7 @@ async fn main() -> Result<()> {
|
|||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
// Initialize logging
|
// Initialize logging
|
||||||
init_logging(&args.log_level);
|
init_logging(&args.log_level, args.log_file.as_deref());
|
||||||
|
|
||||||
info!("Record Daemon v{} starting", record_daemon::VERSION);
|
info!("Record Daemon v{} starting", record_daemon::VERSION);
|
||||||
|
|
||||||
|
|||||||
@@ -314,6 +314,20 @@ fn find_daemon_binary() -> Option<PathBuf> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the log file path for the daemon.
|
||||||
|
///
|
||||||
|
/// Returns a path in the app's data directory.
|
||||||
|
fn get_log_file_path() -> Result<PathBuf, String> {
|
||||||
|
let data_dir = directories::ProjectDirs::from("com", "leaguerecorder", "record-daemon")
|
||||||
|
.ok_or_else(|| "Failed to get app data directory".to_string())?;
|
||||||
|
|
||||||
|
let log_dir = data_dir.data_dir().join("logs");
|
||||||
|
std::fs::create_dir_all(&log_dir)
|
||||||
|
.map_err(|e| format!("Failed to create log directory: {}", e))?;
|
||||||
|
|
||||||
|
Ok(log_dir.join("daemon.log"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Spawn the record-daemon as a detached background process.
|
/// Spawn the record-daemon as a detached background process.
|
||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the process was successfully spawned.
|
/// Returns `Ok(())` if the process was successfully spawned.
|
||||||
@@ -321,10 +335,13 @@ fn spawn_daemon() -> Result<(), String> {
|
|||||||
let binary = find_daemon_binary()
|
let binary = find_daemon_binary()
|
||||||
.ok_or_else(|| "record-daemon binary not found".to_string())?;
|
.ok_or_else(|| "record-daemon binary not found".to_string())?;
|
||||||
|
|
||||||
|
let log_file = get_log_file_path()?;
|
||||||
eprintln!("[daemon] Spawning record-daemon: {}", binary.display());
|
eprintln!("[daemon] Spawning record-daemon: {}", binary.display());
|
||||||
|
eprintln!("[daemon] Log file: {}", log_file.display());
|
||||||
|
|
||||||
let mut cmd = std::process::Command::new(&binary);
|
let mut cmd = std::process::Command::new(&binary);
|
||||||
cmd.arg("--foreground");
|
cmd.arg("--foreground");
|
||||||
|
cmd.arg("--log-file").arg(&log_file);
|
||||||
|
|
||||||
// Set the working directory to the daemon binary's directory so it can
|
// Set the working directory to the daemon binary's directory so it can
|
||||||
// find its bundled resources (OBS plugins, etc.) relative to itself.
|
// find its bundled resources (OBS plugins, etc.) relative to itself.
|
||||||
@@ -536,3 +553,25 @@ pub async fn daemon_shutdown() -> Result<serde_json::Value, String> {
|
|||||||
Err(response.error.unwrap_or_else(|| "Unknown error".to_string()))
|
Err(response.error.unwrap_or_else(|| "Unknown error".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the daemon log file path.
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn daemon_get_log_path() -> Result<String, String> {
|
||||||
|
get_log_file_path()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the daemon log file.
|
||||||
|
///
|
||||||
|
/// Returns the last `lines` lines of the log file (default 100, max 1000).
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn daemon_read_logs(lines: Option<usize>) -> Result<String, String> {
|
||||||
|
let log_path = get_log_file_path()?;
|
||||||
|
let max_lines = lines.unwrap_or(100).min(1000);
|
||||||
|
|
||||||
|
let content = std::fs::read_to_string(&log_path)
|
||||||
|
.map_err(|e| format!("Failed to read log file: {}", e))?;
|
||||||
|
|
||||||
|
let lines: Vec<&str> = content.lines().rev().take(max_lines).collect();
|
||||||
|
Ok(lines.into_iter().rev().collect::<Vec<_>>().join("\n"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -439,6 +439,8 @@ pub fn run() {
|
|||||||
daemon_ipc::daemon_start_recording,
|
daemon_ipc::daemon_start_recording,
|
||||||
daemon_ipc::daemon_stop_recording,
|
daemon_ipc::daemon_stop_recording,
|
||||||
daemon_ipc::daemon_shutdown,
|
daemon_ipc::daemon_shutdown,
|
||||||
|
daemon_ipc::daemon_get_log_path,
|
||||||
|
daemon_ipc::daemon_read_logs,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
Reference in New Issue
Block a user