diff --git a/Cargo.toml b/Cargo.toml index b8907e7..936806c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,12 @@ description = "A process manager in Rust" license = "MIT" [dependencies] -clap = { version = "4.0", features = ["derive"] } -sysinfo = "0.33.1" -tabled = "0.17.0" -serde = { version = "1.0", features = ["derive"] } -dirs = "5.0" -serde_json = "1.0" +clap = { version = "4.4.11", features = ["derive"] } +sysinfo = "0.29.11" +tabled = "0.14.0" +serde = { version = "1.0.193", features = ["derive"] } +dirs = "5.0.1" +serde_json = "1.0.108" crossterm = "0.28.1" once_cell = "1.18.0" +ctrlc = "3.4.1" diff --git a/src/commands/list.rs b/src/commands/list.rs index e64e62b..018ea92 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -1,6 +1,6 @@ use crate::config::dump::DumpConfig; use serde::{Deserialize, Serialize}; -use sysinfo::{System, Users}; +use sysinfo::{ProcessExt, System, SystemExt, UserExt}; use tabled::{Table, Tabled}; #[derive(Serialize, Deserialize)] @@ -83,8 +83,7 @@ pub fn read_pmr_processes() -> Vec { } pub fn list_processes(system: bool) { - let mut sys = System::new_all(); - let users = Users::new_with_refreshed_list(); + let mut sys = System::new(); sys.refresh_all(); if system { @@ -93,7 +92,7 @@ pub fn list_processes(system: bool) { .iter() .map(|(&pid, process)| ProcessInfo { id: "0".to_string(), - name: process.name().to_string_lossy().to_string(), + name: process.name().to_string(), namespace: "default".to_string(), version: "N/A".to_string(), pid: pid.to_string(), @@ -104,7 +103,7 @@ pub fn list_processes(system: bool) { mem: format!("{:.1} MB", process.memory() as f64 / 1024.0 / 1024.0), user: process .user_id() - .and_then(|uid| users.get_user_by_id(uid)) + .and_then(|uid| sys.get_user_by_id(uid)) .map_or("N/A".to_string(), |u| u.name().to_string()), }) .collect(); @@ -133,7 +132,7 @@ pub fn list_processes(system: bool) { mem: format!("{:.1} MB", sys_proc.memory() as f64 / 1024.0 / 1024.0), user: sys_proc .user_id() - .and_then(|uid| users.get_user_by_id(uid)) + .and_then(|uid| sys.get_user_by_id(uid)) .map_or("N/A".to_string(), |u| u.name().to_string()), } } else { diff --git a/src/commands/log.rs b/src/commands/log.rs new file mode 100644 index 0000000..44a450b --- /dev/null +++ b/src/commands/log.rs @@ -0,0 +1,89 @@ +use super::super::config::dump::DumpConfig; +use super::super::config::log_path; +use std::fs::File; +use std::io::{self, BufRead, BufReader, Seek, SeekFrom}; +use std::thread; +use std::time::Duration; + +pub fn tail_log(target: String) -> io::Result<()> { + let dump_config = DumpConfig::get_instance(); + + // 解析目标ID + let pmr_id = match target.parse::() { + Ok(id) => id, + Err(_) => { + // 如果不是数字,尝试按名称查找 + match dump_config.list_processes() { + Ok(processes) => { + if let Some(process) = processes.iter().find(|p| p.name == target) { + process.pmr_id + } else { + eprintln!("找不到进程: {}", target); + return Ok(()); + } + } + Err(e) => { + eprintln!("无法获取进程列表: {}", e); + return Ok(()); + } + } + } + }; + + // 获取日志文件路径 + let log_path = match log_path::get_log_path(pmr_id) { + Ok(path) => path, + Err(e) => { + eprintln!("无法获取日志文件路径: {}", e); + return Ok(()); + } + }; + + // 检查日志文件是否存在 + if !log_path.exists() { + eprintln!("日志文件不存在: {:?}", log_path); + return Ok(()); + } + + println!("正在查看日志文件: {:?}", log_path); + println!("按 Ctrl+C 退出日志查看..."); + + // 打开日志文件 + let mut file = match File::open(&log_path) { + Ok(file) => file, + Err(e) => { + eprintln!("无法打开日志文件: {}", e); + return Ok(()); + } + }; + + // 移动到文件末尾 + if let Err(e) = file.seek(SeekFrom::End(0)) { + eprintln!("无法定位到文件末尾: {}", e); + return Ok(()); + } + + let mut reader = BufReader::new(file); + let mut buffer = String::new(); + + // 持续读取新的日志内容 + loop { + match reader.read_line(&mut buffer) { + Ok(0) => { + // 没有新的内容,等待一下 + thread::sleep(Duration::from_millis(100)); + } + Ok(_) => { + // 打印新的内容 + print!("{}", buffer); + buffer.clear(); + } + Err(e) => { + eprintln!("读取日志时出错: {}", e); + break; + } + } + } + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 7439e90..92f1ac5 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,8 @@ pub mod delete; pub mod list; +pub mod log; pub mod restart; pub mod start; pub mod stop; + +pub use log::tail_log; diff --git a/src/commands/restart.rs b/src/commands/restart.rs index e7343a6..f160960 100644 --- a/src/commands/restart.rs +++ b/src/commands/restart.rs @@ -1,16 +1,20 @@ use super::super::base::process::PmrProcessInfo; use super::super::config::dump::DumpConfig; +use super::super::config::log_path; use super::list::list_processes; use super::start::start_process; use super::stop::stop_process; +use std::fs::OpenOptions; +use std::io; use std::path::PathBuf; +use std::process::Stdio; pub fn restart_process( config: Option, namespace: Option, target: Option, args: Vec, -) { +) -> io::Result<()> { let dump_config = DumpConfig::get_instance(); // 如果指定了target,先检查是否是已存在的进程 @@ -19,34 +23,49 @@ pub fn restart_process( // 尝试将target解析为pmr_id if let Ok(pmr_id) = target_str.parse::() { if let Some(process) = processes.iter().find(|p| p.pmr_id == pmr_id) { - restart_existing_process(process); - return; + restart_existing_process(process)?; + return Ok(()); } } // 按名称查找进程 if let Some(process) = processes.iter().find(|p| p.name == *target_str) { - restart_existing_process(process); - return; + restart_existing_process(process)?; + return Ok(()); } } } // 如果不是重启已存在的进程,就当作普通的启动处理 - start_process(config, namespace, "default".to_string(), target, args); + start_process(config, namespace, "default".to_string(), target, args)?; + Ok(()) } -fn restart_existing_process(process: &PmrProcessInfo) { + +fn restart_existing_process(process: &PmrProcessInfo) -> io::Result<()> { println!("正在重启进程 '{}'...", process.name); // 先停止进程 stop_process(&process.pmr_id.to_string(), false); + // 获取日志文件路径 + let log_path = log_path::get_log_path(process.pmr_id)?; + + // 打开日志文件(追加模式) + let log_file = OpenOptions::new() + .create(true) + .append(true) + .open(&log_path)?; + + // 使用同一个文件句柄来重定向标准输出和标准错误 + let stdout_log = log_file.try_clone()?; + let stderr_log = log_file.try_clone()?; + // 重新启动进程 let mut cmd = std::process::Command::new(&process.program); cmd.args(&process.args) - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()); + .stdout(Stdio::from(stdout_log)) + .stderr(Stdio::from(stderr_log)); match cmd.spawn() { Ok(child) => { @@ -65,9 +84,14 @@ fn restart_existing_process(process: &PmrProcessInfo) { // 显示进程列表 list_processes(false); + Ok(()) } Err(e) => { eprintln!("重启进程失败: {}", e); + Err(io::Error::new( + io::ErrorKind::Other, + format!("重启进程失败: {}", e), + )) } } } diff --git a/src/commands/start.rs b/src/commands/start.rs index 8a711cc..1d0d065 100644 --- a/src/commands/start.rs +++ b/src/commands/start.rs @@ -1,10 +1,12 @@ use super::super::base::process::PmrProcessInfo; +use super::super::config::log_path; use super::super::config::dump::DumpConfig; use super::list::list_processes; +use super::stop::stop_process; use serde::{Deserialize, Serialize}; use std::env; -use std::fs::File; -use std::io::Read; +use std::fs::{File, OpenOptions}; +use std::io::{self, Read}; use std::path::PathBuf; use std::process::{Command, Stdio}; @@ -21,77 +23,84 @@ pub fn start_process( namespace: String, target: Option, args: Vec, -) { +) -> io::Result<()> { let dump_config = DumpConfig::get_instance(); // 获取当前工作目录 let workdir = env::current_dir() .unwrap_or_else(|_| PathBuf::from(".")) .to_string_lossy() - .into_owned(); + .to_string(); + + // 获取进程名称 + let process_name = name.unwrap_or_else(|| { + target + .as_ref() + .map(|s| s.split('/').last().unwrap_or(s)) + .unwrap_or("unknown") + .to_string() + }); // 如果指定了target,先检查是否是已存在的进程 if let Some(ref target_str) = target { if let Ok(processes) = dump_config.list_processes() { - // 按pmr_id查找进程 + // 尝试将target解析为pmr_id if let Ok(pmr_id) = target_str.parse::() { if let Some(process) = processes.iter().find(|p| p.pmr_id == pmr_id) { - start_existing_process(process); - return; + start_existing_process(process)?; + return Ok(()); } } - // 按name查找进程 + // 按名称查找进程 if let Some(process) = processes.iter().find(|p| p.name == *target_str) { - start_existing_process(process); - return; + start_existing_process(process)?; + return Ok(()); } } } - // 检查是否已存在同名进程在同一namespace中 - let process_name = name.clone().unwrap_or_else(|| { - target.clone().unwrap_or_else(|| { - if let Some(ref config_path) = config { - let mut file = File::open(config_path).expect("Failed to open config file"); - let mut contents = String::new(); - file.read_to_string(&mut contents) - .expect("Failed to read config file"); - let config: Config = - serde_json::from_str(&contents).expect("Failed to parse config file"); - config.name - } else { - "unnamed".to_string() - } - }) - }); - - if let Ok(processes) = dump_config.list_processes() { - if let Some(_existing) = processes - .iter() - .find(|p| p.name == process_name && p.namespace == namespace) - { - println!( - "\n进程 '{}' 在命名空间 '{}' 中已经存在:", - process_name, namespace - ); - list_processes(false); - return; - } - } - + // 如果指定了配置文件,从配置文件启动 if let Some(config_path) = config { - // 从配置文件启动 - let mut file = File::open(config_path).expect("无法打开配置文件"); + if !config_path.exists() { + eprintln!("配置文件不存在: {:?}", config_path); + return Ok(()); + } + + // 读取配置文件 + let mut file = File::open(&config_path)?; let mut contents = String::new(); - file.read_to_string(&mut contents) - .expect("无法读取配置文件"); + file.read_to_string(&mut contents)?; let config: Config = serde_json::from_str(&contents).expect("无法解析配置文件"); + let new_id = dump_config.add_process( + process_name.clone(), + namespace.clone(), + workdir.clone(), + config.program.clone(), + 0, + "starting".to_string(), + config.args.clone(), + )?; + + // 获取日志文件路径 + let log_path = log_path::get_log_path(new_id)?; + + // 打开日志文件(追加模式) + let log_file = OpenOptions::new() + .create(true) + .append(true) + .open(&log_path)?; + + // 使用同一个文件句柄来重定向标准输出和标准错误 + let stdout_log = log_file.try_clone()?; + let stderr_log = log_file.try_clone()?; + + // 启动进程 let mut cmd = Command::new(&config.program); cmd.args(&config.args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()); + .stdout(Stdio::from(stdout_log)) + .stderr(Stdio::from(stderr_log)); match cmd.spawn() { Ok(child) => { @@ -99,74 +108,102 @@ pub fn start_process( println!("启动进程 '{}' PID: {}", process_name, pid); dump_config - .add_process( - process_name, - namespace, - workdir, - config.program, - pid, - "running".to_string(), - config.args, - ) - .expect("无法将进程添加到配置文件"); - } - Err(e) => { - eprintln!("启动进程失败: {}", e); - } - } - } else if let Some(target_program) = target { - // 直接启动程序 - let mut cmd = Command::new(&target_program); - cmd.args(&args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()); + .update_process_status(new_id, pid, "running".to_string()) + .expect("无法更新进程状态"); - match cmd.spawn() { - Ok(child) => { - let pid = child.id(); - println!("启动进程 '{}' PID: {}", process_name, pid); - - dump_config - .add_process( - process_name, - namespace, - workdir, - target_program, - pid, - "running".to_string(), - args, - ) - .expect("无法将进程添加到配置文件"); list_processes(false); } Err(e) => { eprintln!("启动进程失败: {}", e); + return Err(io::Error::new( + io::ErrorKind::Other, + format!("启动进程失败: {}", e), + )); + } + } + } else if let Some(target_program) = target { + // 直接启动程序 + let new_id = dump_config.add_process( + process_name.clone(), + namespace.clone(), + workdir.clone(), + target_program.clone(), + 0, + "starting".to_string(), + args.clone(), + )?; + + // 获取日志文件路径 + let log_path = log_path::get_log_path(new_id)?; + + // 打开日志文件(追加模式) + let log_file = OpenOptions::new() + .create(true) + .append(true) + .open(&log_path)?; + + // 使用同一个文件句柄来重定向标准输出和标准错误 + let stdout_log = log_file.try_clone()?; + let stderr_log = log_file.try_clone()?; + + // 启动进程 + let mut cmd = Command::new(&target_program); + cmd.args(&args) + .stdout(Stdio::from(stdout_log)) + .stderr(Stdio::from(stderr_log)); + + match cmd.spawn() { + Ok(child) => { + let pid = child.id(); + println!("启动进程 '{}' PID: {}", process_name, pid); + + dump_config + .update_process_status(new_id, pid, "running".to_string()) + .expect("无法更新进程状态"); + list_processes(false); + } + Err(e) => { + eprintln!("启动进程失败: {}", e); + return Err(io::Error::new( + io::ErrorKind::Other, + format!("启动进程失败: {}", e), + )); } } } else { eprintln!("错误: 必须指定 --config 或 target"); } + + Ok(()) } -fn start_existing_process(process: &PmrProcessInfo) { +fn start_existing_process(process: &PmrProcessInfo) -> io::Result<()> { if process.status == "running" { println!("进程 '{}' 已经在运行中,PID: {}", process.name, process.pid); - return; + return Ok(()); } - // 保存当前工作目录 - let original_dir = env::current_dir().expect("无法获取当前工作目录"); + // 先停止进程 + stop_process(&process.pmr_id.to_string(), false); - // 切换到进程的工作目录 - if let Err(e) = env::set_current_dir(&process.workdir) { - eprintln!("无法切换到工作目录 {}: {}", process.workdir, e); - return; - } + // 获取日志文件路径 + let log_path = log_path::get_log_path(process.pmr_id)?; + // 打开日志文件(追加模式) + let log_file = OpenOptions::new() + .create(true) + .append(true) + .open(&log_path)?; + + // 使用同一个文件句柄来重定向标准输出和标准错误 + let stdout_log = log_file.try_clone()?; + let stderr_log = log_file.try_clone()?; + + // 重新启动进程 let mut cmd = Command::new(&process.program); cmd.args(&process.args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()); + .stdout(Stdio::from(stdout_log)) + .stderr(Stdio::from(stderr_log)); match cmd.spawn() { Ok(child) => { @@ -180,14 +217,14 @@ fn start_existing_process(process: &PmrProcessInfo) { // 显示进程列表 list_processes(false); + Ok(()) } Err(e) => { eprintln!("启动进程失败: {}", e); + Err(io::Error::new( + io::ErrorKind::Other, + format!("启动进程失败: {}", e), + )) } } - - // 恢复原始工作目录 - if let Err(e) = env::set_current_dir(&original_dir) { - eprintln!("警告:无法恢复原始工作目录: {}", e); - } } diff --git a/src/config/dump.rs b/src/config/dump.rs index 972e555..630e357 100644 --- a/src/config/dump.rs +++ b/src/config/dump.rs @@ -31,34 +31,35 @@ impl DumpConfig { let file_contents = fs::read_to_string(&dump_file)?; // 使用 serde_json::Value 先解析JSON let json: serde_json::Value = serde_json::from_str(&file_contents)?; - + // 手动构建进程列表 - let processes = if let Some(processes) = json.get("processes").and_then(|v| v.as_array()) { - processes - .iter() - .map(|p| PmrProcessInfo { - pmr_id: p["pmr_id"].as_u64().unwrap_or(0) as u32, - pid: p["pid"].as_u64().unwrap_or(0) as u32, - name: p["name"].as_str().unwrap_or("").to_string(), - namespace: p["namespace"].as_str().unwrap_or("").to_string(), - status: p["status"].as_str().unwrap_or("").to_string(), - program: p["program"].as_str().unwrap_or("").to_string(), - workdir: p["workdir"].as_str().unwrap_or("").to_string(), - args: p["args"] - .as_array() - .map(|a| { - a.iter() - .filter_map(|v| v.as_str()) - .map(String::from) - .collect() - }) - .unwrap_or_default(), - restarts: p["restarts"].as_u64().unwrap_or(0) as u32, - }) - .collect() - } else { - Vec::new() - }; + let processes = + if let Some(processes) = json.get("processes").and_then(|v| v.as_array()) { + processes + .iter() + .map(|p| PmrProcessInfo { + pmr_id: p["pmr_id"].as_u64().unwrap_or(0) as u32, + pid: p["pid"].as_u64().unwrap_or(0) as u32, + name: p["name"].as_str().unwrap_or("").to_string(), + namespace: p["namespace"].as_str().unwrap_or("").to_string(), + status: p["status"].as_str().unwrap_or("").to_string(), + program: p["program"].as_str().unwrap_or("").to_string(), + workdir: p["workdir"].as_str().unwrap_or("").to_string(), + args: p["args"] + .as_array() + .map(|a| { + a.iter() + .filter_map(|v| v.as_str()) + .map(String::from) + .collect() + }) + .unwrap_or_default(), + restarts: p["restarts"].as_u64().unwrap_or(0) as u32, + }) + .collect() + } else { + Vec::new() + }; DumpData { processes } } else { @@ -93,7 +94,7 @@ impl DumpConfig { pid: u32, status: String, args: Vec, - ) -> io::Result<()> { + ) -> io::Result { let mut data = self.data.lock().unwrap(); let new_id = data.processes.iter().map(|p| p.pmr_id).max().unwrap_or(0) + 1; @@ -109,7 +110,8 @@ impl DumpConfig { restarts: 0, // 初始化重启次数为0 }); - self.save_data(&data) + self.save_data(&data)?; + Ok(new_id) } pub fn delete_process(&self, id: u32) -> io::Result<()> { diff --git a/src/config/log_path.rs b/src/config/log_path.rs new file mode 100644 index 0000000..ef6e888 --- /dev/null +++ b/src/config/log_path.rs @@ -0,0 +1,17 @@ +use std::fs; +use std::io; +use std::path::PathBuf; +use dirs; + +pub fn get_log_path(pmr_id: u32) -> io::Result { + let home_dir = dirs::home_dir() + .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Home directory not found"))?; + let log_dir = home_dir.join(".pmr").join("logs"); + + // 确保日志目录存在 + if !log_dir.exists() { + fs::create_dir_all(&log_dir)?; + } + + Ok(log_dir.join(format!("{}.log", pmr_id))) +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 88f4251..f95626b 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1 +1,2 @@ pub mod dump; +pub mod log_path; diff --git a/src/main.rs b/src/main.rs index 44445ef..aaa5833 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use commands::list::list_processes; use commands::restart::restart_process; use commands::start::start_process; use commands::stop::stop_process; +use commands::tail_log; use config::dump::DumpConfig; fn config_init() -> std::io::Result<()> { @@ -49,7 +50,12 @@ enum Commands { }, /// List running processes - #[command(alias = "ls")] + #[command( + alias = "ls", + alias = "ps", + alias = "status", + about = "List running processes. Alias: ls, ps, status" + )] List { /// Show all system processes #[arg(long)] @@ -57,7 +63,11 @@ enum Commands { }, /// Delete a process - #[command(alias = "rm")] + #[command( + alias = "rm", + alias = "del", + about = "Delete a process. Alias: rm, del" + )] Delete { /// Process ID or name target: String, @@ -86,6 +96,13 @@ enum Commands { #[arg(last = true)] args: Vec, }, + + /// View logs of a process + #[command(alias = "logs")] + Log { + /// Process ID or name + target: String, + }, } fn main() { @@ -108,7 +125,13 @@ fn main() { eprintln!("错误: 必须指定 --config 或 target"); return; } - start_process(config, name, namespace, target, args); + match start_process(config, name, namespace, target, args) { + Ok(_) => (), + Err(e) => { + eprintln!("启动进程失败: {}", e); + std::process::exit(1); + } + } } Commands::List { system } => { list_processes(system); @@ -129,7 +152,19 @@ fn main() { eprintln!("错误: 必须指定 --config 或 target"); return; } - restart_process(config, Some(namespace), target, args); + match restart_process(config, Some(namespace), target, args) { + Ok(_) => (), + Err(e) => { + eprintln!("重启进程失败: {}", e); + std::process::exit(1); + } + } + } + Commands::Log { target } => { + if let Err(e) = tail_log(target) { + eprintln!("查看日志失败: {}", e); + std::process::exit(1); + } } } }