feat: log info

This commit is contained in:
GyDi 2022-09-05 20:30:39 +08:00 committed by GitHub
parent fcee41f00d
commit 9e7c7ac163
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 110 additions and 34 deletions

View File

@ -5,7 +5,7 @@ use crate::{
use crate::{log_if_err, ret_err, wrap_err}; use crate::{log_if_err, ret_err, wrap_err};
use anyhow::Result; use anyhow::Result;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::collections::HashMap; use std::collections::{HashMap, VecDeque};
use tauri::{api, State}; use tauri::{api, State};
type CmdResult<T = ()> = Result<T, String>; type CmdResult<T = ()> = Result<T, String>;
@ -256,6 +256,12 @@ pub fn get_cur_proxy(core: State<'_, Core>) -> CmdResult<Option<SysProxyConfig>>
wrap_err!(sysopt.get_sysproxy()) wrap_err!(sysopt.get_sysproxy())
} }
#[tauri::command]
pub fn get_clash_logs(core: State<'_, Core>) -> CmdResult<VecDeque<String>> {
let service = core.service.lock();
Ok(service.get_logs())
}
/// open app config dir /// open app config dir
#[tauri::command] #[tauri::command]
pub fn open_app_dir() -> Result<(), String> { pub fn open_app_dir() -> Result<(), String> {

View File

@ -129,6 +129,7 @@ impl Core {
let mut service = self.service.lock(); let mut service = self.service.lock();
service.stop()?; service.stop()?;
service.set_core(Some(clash_core)); service.set_core(Some(clash_core));
service.clear_logs();
service.start()?; service.start()?;
drop(service); drop(service);

View File

@ -2,28 +2,38 @@ use super::{notice::Notice, ClashInfo};
use crate::log_if_err; use crate::log_if_err;
use crate::utils::{config, dirs}; use crate::utils::{config, dirs};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use parking_lot::RwLock;
use reqwest::header::HeaderMap; use reqwest::header::HeaderMap;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
use std::{collections::HashMap, time::Duration}; use std::sync::Arc;
use std::{
collections::{HashMap, VecDeque},
time::Duration,
};
use tauri::api::process::{Command, CommandChild, CommandEvent}; use tauri::api::process::{Command, CommandChild, CommandEvent};
use tokio::time::sleep; use tokio::time::sleep;
static mut CLASH_CORE: &str = "clash"; static mut CLASH_CORE: &str = "clash";
const LOGS_QUEUE_LEN: usize = 100;
#[derive(Debug)] #[derive(Debug)]
pub struct Service { pub struct Service {
sidecar: Option<CommandChild>, sidecar: Option<CommandChild>,
logs: Arc<RwLock<VecDeque<String>>>,
#[allow(unused)] #[allow(unused)]
service_mode: bool, service_mode: bool,
} }
impl Service { impl Service {
pub fn new() -> Service { pub fn new() -> Service {
let queue = VecDeque::with_capacity(LOGS_QUEUE_LEN + 10);
Service { Service {
sidecar: None, sidecar: None,
logs: Arc::new(RwLock::new(queue)),
service_mode: false, service_mode: false,
} }
} }
@ -39,47 +49,47 @@ impl Service {
self.service_mode = enable; self.service_mode = enable;
} }
#[cfg(not(windows))]
pub fn start(&mut self) -> Result<()> { pub fn start(&mut self) -> Result<()> {
self.start_clash_by_sidecar() #[cfg(not(windows))]
} self.start_clash_by_sidecar()?;
#[cfg(windows)] #[cfg(windows)]
pub fn start(&mut self) -> Result<()> { {
if !self.service_mode { if !self.service_mode {
return self.start_clash_by_sidecar(); return self.start_clash_by_sidecar();
}
tauri::async_runtime::spawn(async move {
match Self::check_service().await {
Ok(status) => {
// 未启动clash
if status.code != 0 {
log_if_err!(Self::start_clash_by_service().await);
}
}
Err(err) => log::error!(target: "app", "{err}"),
} }
});
tauri::async_runtime::spawn(async move {
match Self::check_service().await {
Ok(status) => {
// 未启动clash
if status.code != 0 {
log_if_err!(Self::start_clash_by_service().await);
}
}
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
Ok(()) Ok(())
} }
#[cfg(not(windows))]
pub fn stop(&mut self) -> Result<()> { pub fn stop(&mut self) -> Result<()> {
self.stop_clash_by_sidecar() #[cfg(not(windows))]
} self.stop_clash_by_sidecar()?;
#[cfg(windows)] #[cfg(windows)]
pub fn stop(&mut self) -> Result<()> { {
if !self.service_mode { if !self.service_mode {
return self.stop_clash_by_sidecar(); return self.stop_clash_by_sidecar();
}
tauri::async_runtime::spawn(async move {
log_if_err!(Self::stop_clash_by_service().await);
});
} }
tauri::async_runtime::spawn(async move {
log_if_err!(Self::stop_clash_by_service().await);
});
Ok(()) Ok(())
} }
@ -88,6 +98,24 @@ impl Service {
self.start() self.start()
} }
pub fn get_logs(&self) -> VecDeque<String> {
self.logs.read().clone()
}
#[allow(unused)]
pub fn set_logs(&self, text: String) {
let mut logs = self.logs.write();
if logs.len() > LOGS_QUEUE_LEN {
(*logs).pop_front();
}
(*logs).push_back(text);
}
pub fn clear_logs(&self) {
let mut logs = self.logs.write();
(*logs).clear();
}
/// start the clash sidecar /// start the clash sidecar
fn start_clash_by_sidecar(&mut self) -> Result<()> { fn start_clash_by_sidecar(&mut self) -> Result<()> {
if self.sidecar.is_some() { if self.sidecar.is_some() {
@ -112,14 +140,28 @@ impl Service {
self.sidecar = Some(cmd_child); self.sidecar = Some(cmd_child);
// clash log // clash log
let logs = self.logs.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let write_log = |text: String| {
let mut logs = logs.write();
if logs.len() >= LOGS_QUEUE_LEN {
(*logs).pop_front();
}
(*logs).push_back(text);
};
while let Some(event) = rx.recv().await { while let Some(event) = rx.recv().await {
match event { match event {
CommandEvent::Stdout(line) => { CommandEvent::Stdout(line) => {
let stdout = if line.len() > 33 { &line[33..] } else { &line }; let can_short = line.starts_with("time=") && line.len() > 33;
let stdout = if can_short { &line[33..] } else { &line };
log::info!(target: "app" ,"[clash]: {}", stdout); log::info!(target: "app" ,"[clash]: {}", stdout);
write_log(line);
}
CommandEvent::Stderr(err) => {
log::error!(target: "app" ,"[clash error]: {}", err);
write_log(err);
} }
CommandEvent::Stderr(err) => log::error!(target: "app" ,"[clash error]: {}", err),
_ => {} _ => {}
} }
} }

View File

@ -118,6 +118,7 @@ fn main() -> std::io::Result<()> {
cmds::restart_sidecar, cmds::restart_sidecar,
// clash // clash
cmds::get_clash_info, cmds::get_clash_info,
cmds::get_clash_logs,
cmds::patch_clash_config, cmds::patch_clash_config,
cmds::change_clash_core, cmds::change_clash_core,
cmds::get_runtime_config, cmds::get_runtime_config,

View File

@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
import { useSetRecoilState } from "recoil"; import { useSetRecoilState } from "recoil";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import { getInformation } from "@/services/api"; import { getInformation } from "@/services/api";
import { getClashLogs } from "@/services/cmds";
import { atomLogData } from "@/services/states"; import { atomLogData } from "@/services/states";
const MAX_LOG_NUM = 1000; const MAX_LOG_NUM = 1000;
@ -13,6 +14,8 @@ export default function useLogSetup() {
const setLogData = useSetRecoilState(atomLogData); const setLogData = useSetRecoilState(atomLogData);
useEffect(() => { useEffect(() => {
getClashLogs().then(setLogData);
let ws: WebSocket = null!; let ws: WebSocket = null!;
const handler = (event: MessageEvent<any>) => { const handler = (event: MessageEvent<any>) => {

View File

@ -1,6 +1,29 @@
import { invoke } from "@tauri-apps/api/tauri"; import { invoke } from "@tauri-apps/api/tauri";
import Notice from "@/components/base/base-notice"; import Notice from "@/components/base/base-notice";
export async function getClashLogs() {
const regex = /time="(.+?)"\s+level=(.+?)\s+msg="(.+?)"/;
const newRegex = /(.+?)\s+(.+?)\s+(.+)/;
const logs = await invoke<string[]>("get_clash_logs");
return logs
.map((log) => {
const result = log.match(regex);
if (result) {
const [_, time, type, payload] = result;
return { time, type, payload };
}
const result2 = log.match(newRegex);
if (result2) {
const [_, time, type, payload] = result2;
return { time, type, payload };
}
return null;
})
.filter(Boolean) as ApiType.LogItem[];
}
export async function getProfiles() { export async function getProfiles() {
return invoke<CmdType.ProfilesConfig>("get_profiles"); return invoke<CmdType.ProfilesConfig>("get_profiles");
} }