Add func: log(with some bug)

This commit is contained in:
gaofei 2025-01-14 17:39:54 +08:00
parent 9297451aa2
commit 5f9314052e
10 changed files with 362 additions and 154 deletions

View File

@ -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"

View File

@ -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<PmrProcess> {
}
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 {

89
src/commands/log.rs Normal file
View File

@ -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::<u32>() {
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(())
}

View File

@ -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;

View File

@ -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<PathBuf>,
namespace: Option<String>,
target: Option<String>,
args: Vec<String>,
) {
) -> 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::<u32>() {
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),
))
}
}
}

View File

@ -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<String>,
args: Vec<String>,
) {
) -> 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::<u32>() {
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,27 +108,49 @@ 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("无法将进程添加到配置文件");
.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 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::inherit())
.stderr(Stdio::inherit());
.stdout(Stdio::from(stdout_log))
.stderr(Stdio::from(stderr_log));
match cmd.spawn() {
Ok(child) => {
@ -127,46 +158,52 @@ pub fn start_process(
println!("启动进程 '{}' PID: {}", process_name, pid);
dump_config
.add_process(
process_name,
namespace,
workdir,
target_program,
pid,
"running".to_string(),
args,
)
.expect("无法将进程添加到配置文件");
.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);
}
}

View File

@ -33,7 +33,8 @@ impl DumpConfig {
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()) {
let processes =
if let Some(processes) = json.get("processes").and_then(|v| v.as_array()) {
processes
.iter()
.map(|p| PmrProcessInfo {
@ -93,7 +94,7 @@ impl DumpConfig {
pid: u32,
status: String,
args: Vec<String>,
) -> io::Result<()> {
) -> io::Result<u32> {
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<()> {

17
src/config/log_path.rs Normal file
View File

@ -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<PathBuf> {
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)))
}

View File

@ -1 +1,2 @@
pub mod dump;
pub mod log_path;

View File

@ -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<String>,
},
/// 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);
}
}
}
}