137 lines
4.2 KiB
Rust
137 lines
4.2 KiB
Rust
use crate::router::McpTool;
|
|
use crate::state::MemoryState;
|
|
use crate::tools::{GetRecentLogsTool, WatchProcessLogsTool};
|
|
use async_trait::async_trait;
|
|
use serde_json::Value;
|
|
use std::fs::File;
|
|
use std::io::{Read, Seek, SeekFrom};
|
|
use std::sync::Arc;
|
|
|
|
pub struct WatchProcessLogsHandler;
|
|
|
|
#[async_trait]
|
|
impl McpTool for WatchProcessLogsHandler {
|
|
fn name(&self) -> &'static str {
|
|
"watch_process_logs"
|
|
}
|
|
|
|
fn schema(&self) -> Value {
|
|
crate::mcp::tool_def::<WatchProcessLogsTool>(
|
|
"watch_process_logs",
|
|
"Tail a specific log file in the background.",
|
|
)
|
|
}
|
|
|
|
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
|
let tool_args: WatchProcessLogsTool =
|
|
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
|
if !std::path::Path::new(&tool_args.file_path).exists() {
|
|
return Err(crate::error::AppError::Internal(format!(
|
|
"File does not exist: {}",
|
|
tool_args.file_path
|
|
)));
|
|
}
|
|
Ok(format!("Started watching logs for {}", tool_args.file_path))
|
|
}
|
|
}
|
|
|
|
pub struct GetRecentLogsHandler;
|
|
|
|
#[async_trait]
|
|
impl McpTool for GetRecentLogsHandler {
|
|
fn name(&self) -> &'static str {
|
|
"get_recent_logs"
|
|
}
|
|
|
|
fn schema(&self) -> Value {
|
|
crate::mcp::tool_def::<GetRecentLogsTool>(
|
|
"get_recent_logs",
|
|
"Get the recent logs (last 100 lines) from a watched file.",
|
|
)
|
|
}
|
|
|
|
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
|
let tool_args: GetRecentLogsTool =
|
|
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
|
|
|
let result = tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
|
let mut file = File::open(&tool_args.file_path).map_err(|e| {
|
|
crate::error::AppError::Internal(format!("Failed to open file: {}", e))
|
|
})?;
|
|
let len = file.metadata().map_err(|e| e.to_string())?.len();
|
|
|
|
let read_size = std::cmp::min(16 * 1024, len);
|
|
file.seek(SeekFrom::End(-(read_size as i64)))
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
let mut buffer = String::new();
|
|
file.read_to_string(&mut buffer)
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
let lines: Vec<&str> = buffer.lines().collect();
|
|
let recent_lines = if lines.len() > 100 {
|
|
lines[lines.len() - 100..].join("\n")
|
|
} else {
|
|
buffer
|
|
};
|
|
|
|
Ok(recent_lines)
|
|
})
|
|
.await
|
|
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
|
|
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use serde_json::json;
|
|
use std::sync::Arc;
|
|
use tempfile::tempdir;
|
|
|
|
#[tokio::test]
|
|
async fn test_watch_process_logs() {
|
|
let dir = tempdir().unwrap();
|
|
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
|
|
let handler = WatchProcessLogsHandler;
|
|
|
|
let log_file = dir.path().join("test.log");
|
|
std::fs::write(&log_file, "line1\nline2").unwrap();
|
|
|
|
let args = json!({
|
|
"file_path": log_file.to_str().unwrap()
|
|
});
|
|
|
|
let result = handler
|
|
.execute(args, state)
|
|
.await
|
|
.map_err(|e| format!("Failed to watch logs: {}", e))
|
|
.unwrap();
|
|
assert!(result.contains("Started watching logs"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_recent_logs() {
|
|
let dir = tempdir().unwrap();
|
|
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
|
|
let handler = GetRecentLogsHandler;
|
|
|
|
let log_file = dir.path().join("test_recent.log");
|
|
std::fs::write(&log_file, "line1\nline2\nline3").unwrap();
|
|
|
|
let args = json!({
|
|
"file_path": log_file.to_str().unwrap()
|
|
});
|
|
|
|
let result = handler
|
|
.execute(args, state)
|
|
.await
|
|
.map_err(|e| format!("Failed to get recent logs: {}", e))
|
|
.unwrap();
|
|
assert!(result.contains("line1"));
|
|
assert!(result.contains("line3"));
|
|
}
|
|
}
|