diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs
index 19fb739325c7753ac1b0af62a916c986587d1a0a..e7a3d2b29bd40bae4c517aa61bae0a754a06e0c9 100644
--- a/src-tauri/src/cmds.rs
+++ b/src-tauri/src/cmds.rs
@@ -5,7 +5,7 @@ use crate::{
 use crate::{log_if_err, ret_err, wrap_err};
 use anyhow::Result;
 use serde_yaml::Mapping;
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
 use tauri::{api, State};
 
 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())
 }
 
+#[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
 #[tauri::command]
 pub fn open_app_dir() -> Result<(), String> {
diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs
index 70b0e5c44d162bbd725f4d751d631922d5bfd3f9..f3bd3f9fd3396d573381e179bd1bf6aaa4958504 100644
--- a/src-tauri/src/core/mod.rs
+++ b/src-tauri/src/core/mod.rs
@@ -129,6 +129,7 @@ impl Core {
     let mut service = self.service.lock();
     service.stop()?;
     service.set_core(Some(clash_core));
+    service.clear_logs();
     service.start()?;
     drop(service);
 
diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs
index 8557dcd74d498ead3eb6fd8abb196a04b1135aa0..7c29e6f3e8526a01aa5993147e257f1d299d5e23 100644
--- a/src-tauri/src/core/service.rs
+++ b/src-tauri/src/core/service.rs
@@ -2,28 +2,38 @@ use super::{notice::Notice, ClashInfo};
 use crate::log_if_err;
 use crate::utils::{config, dirs};
 use anyhow::{bail, Result};
+use parking_lot::RwLock;
 use reqwest::header::HeaderMap;
 use serde_yaml::Mapping;
 use std::fs;
 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 tokio::time::sleep;
 
 static mut CLASH_CORE: &str = "clash";
+const LOGS_QUEUE_LEN: usize = 100;
 
 #[derive(Debug)]
 pub struct Service {
   sidecar: Option<CommandChild>,
 
+  logs: Arc<RwLock<VecDeque<String>>>,
+
   #[allow(unused)]
   service_mode: bool,
 }
 
 impl Service {
   pub fn new() -> Service {
+    let queue = VecDeque::with_capacity(LOGS_QUEUE_LEN + 10);
     Service {
       sidecar: None,
+      logs: Arc::new(RwLock::new(queue)),
       service_mode: false,
     }
   }
@@ -39,46 +49,46 @@ impl Service {
     self.service_mode = enable;
   }
 
-  #[cfg(not(windows))]
   pub fn start(&mut self) -> Result<()> {
-    self.start_clash_by_sidecar()
-  }
+    #[cfg(not(windows))]
+    self.start_clash_by_sidecar()?;
 
-  #[cfg(windows)]
-  pub fn start(&mut self) -> Result<()> {
-    if !self.service_mode {
-      return self.start_clash_by_sidecar();
-    }
+    #[cfg(windows)]
+    {
+      if !self.service_mode {
+        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);
+      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}"),
         }
-        Err(err) => log::error!(target: "app", "{err}"),
-      }
-    });
+      });
+    }
 
     Ok(())
   }
 
-  #[cfg(not(windows))]
   pub fn stop(&mut self) -> Result<()> {
-    self.stop_clash_by_sidecar()
-  }
+    #[cfg(not(windows))]
+    self.stop_clash_by_sidecar()?;
 
-  #[cfg(windows)]
-  pub fn stop(&mut self) -> Result<()> {
-    if !self.service_mode {
-      return self.stop_clash_by_sidecar();
-    }
+    #[cfg(windows)]
+    {
+      if !self.service_mode {
+        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(())
   }
@@ -88,6 +98,24 @@ impl Service {
     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
   fn start_clash_by_sidecar(&mut self) -> Result<()> {
     if self.sidecar.is_some() {
@@ -112,14 +140,28 @@ impl Service {
     self.sidecar = Some(cmd_child);
 
     // clash log
+    let logs = self.logs.clone();
     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 {
         match event {
           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);
+            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),
           _ => {}
         }
       }
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index fff8cb1088bcec8edeafc7204715de36bbaf65bf..542eb7a5b2527ebee2693b667ccb5356c36fe2b4 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -118,6 +118,7 @@ fn main() -> std::io::Result<()> {
       cmds::restart_sidecar,
       // clash
       cmds::get_clash_info,
+      cmds::get_clash_logs,
       cmds::patch_clash_config,
       cmds::change_clash_core,
       cmds::get_runtime_config,
diff --git a/src/components/layout/use-log-setup.ts b/src/components/layout/use-log-setup.ts
index 9c5f0d50c24c36dbd1357a6d5245f2e629d1fa52..2d05477061180171877241099634b337793c4ad1 100644
--- a/src/components/layout/use-log-setup.ts
+++ b/src/components/layout/use-log-setup.ts
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
 import { useSetRecoilState } from "recoil";
 import { listen } from "@tauri-apps/api/event";
 import { getInformation } from "@/services/api";
+import { getClashLogs } from "@/services/cmds";
 import { atomLogData } from "@/services/states";
 
 const MAX_LOG_NUM = 1000;
@@ -13,6 +14,8 @@ export default function useLogSetup() {
   const setLogData = useSetRecoilState(atomLogData);
 
   useEffect(() => {
+    getClashLogs().then(setLogData);
+
     let ws: WebSocket = null!;
 
     const handler = (event: MessageEvent<any>) => {
diff --git a/src/services/cmds.ts b/src/services/cmds.ts
index a5844f4992743a0d02209a9674010ddea791f189..e4f9ebf4082e76ad08140a627a58ca9466c2642c 100644
--- a/src/services/cmds.ts
+++ b/src/services/cmds.ts
@@ -1,6 +1,29 @@
 import { invoke } from "@tauri-apps/api/tauri";
 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() {
   return invoke<CmdType.ProfilesConfig>("get_profiles");
 }