mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-02-12 10:01:36 +00:00
Improved logging behaviour
This commit is contained in:
parent
95c6d19c08
commit
0dd7d65efc
@ -239,7 +239,7 @@
|
|||||||
<ThirdPartyComponent Name="pdfium-render" Developer="Alastair Carey, Dorian Rudolph & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/ajrcarey/pdfium-render/blob/master/LICENSE.md" RepositoryUrl="https://github.com/ajrcarey/pdfium-render" UseCase="@T("This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.")"/>
|
<ThirdPartyComponent Name="pdfium-render" Developer="Alastair Carey, Dorian Rudolph & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/ajrcarey/pdfium-render/blob/master/LICENSE.md" RepositoryUrl="https://github.com/ajrcarey/pdfium-render" UseCase="@T("This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.")"/>
|
||||||
<ThirdPartyComponent Name="sys-locale" Developer="1Password Team, ComplexSpaces & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/1Password/sys-locale/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/1Password/sys-locale" UseCase="@T("This library is used to determine the language of the operating system. This is necessary to set the language of the user interface.")"/>
|
<ThirdPartyComponent Name="sys-locale" Developer="1Password Team, ComplexSpaces & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/1Password/sys-locale/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/1Password/sys-locale" UseCase="@T("This library is used to determine the language of the operating system. This is necessary to set the language of the user interface.")"/>
|
||||||
<ThirdPartyComponent Name="sysinfo" Developer="Guillaume Gomez & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/GuillaumeGomez/sysinfo?tab=MIT-1-ov-file" RepositoryUrl="https://github.com/GuillaumeGomez/sysinfo" UseCase="@T("This library is used to manage processes across different operating systems, ensuring proper handling of stale or zombie processes.")"/>
|
<ThirdPartyComponent Name="sysinfo" Developer="Guillaume Gomez & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/GuillaumeGomez/sysinfo?tab=MIT-1-ov-file" RepositoryUrl="https://github.com/GuillaumeGomez/sysinfo" UseCase="@T("This library is used to manage processes across different operating systems, ensuring proper handling of stale or zombie processes.")"/>
|
||||||
<ThirdPartyComponent Name="tempfile" Developer="Steven Allen, Ashley Mannix & Open Source Community" LicenseName="Apache-2.0" LicenseUrl="https://github.com/Stebalien/tempfile?tab=Apache-2.0-1-ov-file" RepositoryUrl="https://github.com/Stebalien/tempfile" UseCase="@T("This library is used to create temporary folders for saving the certificate and private key data for communication with Qdrant.")"/>
|
<ThirdPartyComponent Name="tempfile" Developer="Steven Allen, Ashley Mannix & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/Stebalien/tempfile?tab=MIT-2-ov-file" RepositoryUrl="https://github.com/Stebalien/tempfile" UseCase="@T("This library is used to create temporary folders for saving the certificate and private key data for communication with Qdrant.")"/>
|
||||||
<ThirdPartyComponent Name="Lua-CSharp" Developer="Yusuke Nakada & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/nuskey8/Lua-CSharp/blob/main/LICENSE" RepositoryUrl="https://github.com/nuskey8/Lua-CSharp" UseCase="@T("We use Lua as the language for plugins. Lua-CSharp lets Lua scripts communicate with AI Studio and vice versa. Thank you, Yusuke Nakada, for this great library.")" />
|
<ThirdPartyComponent Name="Lua-CSharp" Developer="Yusuke Nakada & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/nuskey8/Lua-CSharp/blob/main/LICENSE" RepositoryUrl="https://github.com/nuskey8/Lua-CSharp" UseCase="@T("We use Lua as the language for plugins. Lua-CSharp lets Lua scripts communicate with AI Studio and vice versa. Thank you, Yusuke Nakada, for this great library.")" />
|
||||||
<ThirdPartyComponent Name="HtmlAgilityPack" Developer="ZZZ Projects & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE" RepositoryUrl="https://github.com/zzzprojects/html-agility-pack" UseCase="@T("We use the HtmlAgilityPack to extract content from the web. This is necessary, e.g., when you provide a URL as input for an assistant.")"/>
|
<ThirdPartyComponent Name="HtmlAgilityPack" Developer="ZZZ Projects & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE" RepositoryUrl="https://github.com/zzzprojects/html-agility-pack" UseCase="@T("We use the HtmlAgilityPack to extract content from the web. This is necessary, e.g., when you provide a URL as input for an assistant.")"/>
|
||||||
<ThirdPartyComponent Name="ReverseMarkdown" Developer="Babu Annamalai & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mysticmind/reversemarkdown-net/blob/master/LICENSE" RepositoryUrl="https://github.com/mysticmind/reversemarkdown-net" UseCase="@T("This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant.")"/>
|
<ThirdPartyComponent Name="ReverseMarkdown" Developer="Babu Annamalai & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mysticmind/reversemarkdown-net/blob/master/LICENSE" RepositoryUrl="https://github.com/mysticmind/reversemarkdown-net" UseCase="@T("This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant.")"/>
|
||||||
|
|||||||
@ -45,7 +45,19 @@ Do you want to test your changes before creating a PR? Follow these steps:
|
|||||||
9. Execute the command `dotnet run`.
|
9. Execute the command `dotnet run`.
|
||||||
10. After compiling the .NET code, the app will finally start inside the Tauri runtime window.
|
10. After compiling the .NET code, the app will finally start inside the Tauri runtime window.
|
||||||
|
|
||||||
You can now test your changes.
|
You can now test your changes. To stop the application:
|
||||||
|
- Close the Tauri window (GUI).
|
||||||
|
- Press ``Ctrl+C`` in the terminal where the app is running.
|
||||||
|
- Stop the process via your IDE’s run/debug controls.
|
||||||
|
|
||||||
|
> ⚠️ Important: Stopping the app via ``Ctrl+C`` or the IDE may not terminate the Qdrant sidecar process, especially on Windows. This can lead to startup failures when restarting the app.
|
||||||
|
|
||||||
|
If you encounter issues restarting Tauri:
|
||||||
|
Manually kill the Qdrant process:
|
||||||
|
**Linux/macOS:** Run pkill -f qdrant in your terminal.
|
||||||
|
**Windows:** Open Task Manager → Find qdrant.exe → Right-click → “End task”.
|
||||||
|
|
||||||
|
Restart your Tauri app.
|
||||||
|
|
||||||
## Create a release
|
## Create a release
|
||||||
In order to create a release:
|
In order to create a release:
|
||||||
|
|||||||
@ -17,6 +17,7 @@ use crate::environment::{is_dev, DATA_DIRECTORY};
|
|||||||
use crate::network::get_available_port;
|
use crate::network::get_available_port;
|
||||||
use crate::runtime_api::API_SERVER_PORT;
|
use crate::runtime_api::API_SERVER_PORT;
|
||||||
use crate::stale_process_cleanup::{kill_stale_process, log_potential_stale_process};
|
use crate::stale_process_cleanup::{kill_stale_process, log_potential_stale_process};
|
||||||
|
use crate::sidecar_types::SidecarType;
|
||||||
|
|
||||||
// The .NET server is started in a separate process and communicates with this
|
// The .NET server is started in a separate process and communicates with this
|
||||||
// runtime process via IPC. However, we do net start the .NET server in
|
// runtime process via IPC. However, we do net start the .NET server in
|
||||||
@ -30,6 +31,7 @@ static DOTNET_SERVER_PORT: Lazy<u16> = Lazy::new(|| get_available_port().unwrap(
|
|||||||
static DOTNET_INITIALIZED: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
|
static DOTNET_INITIALIZED: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
|
||||||
|
|
||||||
pub const PID_FILE_NAME: &str = "mindwork_ai_studio.pid";
|
pub const PID_FILE_NAME: &str = "mindwork_ai_studio.pid";
|
||||||
|
const SIDECAR_TYPE:SidecarType = SidecarType::Dotnet;
|
||||||
|
|
||||||
/// Returns the desired port of the .NET server. Our .NET app calls this endpoint to get
|
/// Returns the desired port of the .NET server. Our .NET app calls this endpoint to get
|
||||||
/// the port where the .NET server should listen to.
|
/// the port where the .NET server should listen to.
|
||||||
@ -100,7 +102,7 @@ pub fn start_dotnet_server() {
|
|||||||
.expect("Failed to spawn .NET server process.");
|
.expect("Failed to spawn .NET server process.");
|
||||||
let server_pid = child.pid();
|
let server_pid = child.pid();
|
||||||
info!(Source = "Bootloader .NET"; "The .NET server process started with PID={server_pid}.");
|
info!(Source = "Bootloader .NET"; "The .NET server process started with PID={server_pid}.");
|
||||||
log_potential_stale_process(Path::new(DATA_DIRECTORY.get().unwrap()).join(PID_FILE_NAME), server_pid);
|
log_potential_stale_process(Path::new(DATA_DIRECTORY.get().unwrap()).join(PID_FILE_NAME), server_pid, SIDECAR_TYPE);
|
||||||
|
|
||||||
// Save the server process to stop it later:
|
// Save the server process to stop it later:
|
||||||
*server_spawn_clone.lock().unwrap() = Some(child);
|
*server_spawn_clone.lock().unwrap() = Some(child);
|
||||||
@ -165,7 +167,7 @@ pub fn stop_dotnet_server() {
|
|||||||
/// Remove old Pid files and kill the corresponding processes
|
/// Remove old Pid files and kill the corresponding processes
|
||||||
pub fn cleanup_dotnet_server() {
|
pub fn cleanup_dotnet_server() {
|
||||||
let pid_path = Path::new(DATA_DIRECTORY.get().unwrap()).join(PID_FILE_NAME);
|
let pid_path = Path::new(DATA_DIRECTORY.get().unwrap()).join(PID_FILE_NAME);
|
||||||
if let Err(e) = kill_stale_process(pid_path) {
|
if let Err(e) = kill_stale_process(pid_path, SIDECAR_TYPE) {
|
||||||
warn!(Source = ".NET"; "Error during the cleanup of .NET: {}", e);
|
warn!(Source = ".NET"; "Error during the cleanup of .NET: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -17,3 +17,4 @@ pub mod qdrant;
|
|||||||
pub mod certificate_factory;
|
pub mod certificate_factory;
|
||||||
pub mod runtime_api_token;
|
pub mod runtime_api_token;
|
||||||
pub mod stale_process_cleanup;
|
pub mod stale_process_cleanup;
|
||||||
|
mod sidecar_types;
|
||||||
@ -17,6 +17,7 @@ use crate::certificate_factory::generate_certificate;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tempfile::{TempDir, Builder};
|
use tempfile::{TempDir, Builder};
|
||||||
use crate::stale_process_cleanup::{kill_stale_process, log_potential_stale_process};
|
use crate::stale_process_cleanup::{kill_stale_process, log_potential_stale_process};
|
||||||
|
use crate::sidecar_types::SidecarType;
|
||||||
|
|
||||||
// Qdrant server process started in a separate process and can communicate
|
// Qdrant server process started in a separate process and can communicate
|
||||||
// via HTTP or gRPC with the .NET server and the runtime process
|
// via HTTP or gRPC with the .NET server and the runtime process
|
||||||
@ -39,6 +40,7 @@ static API_TOKEN: Lazy<APIToken> = Lazy::new(|| {
|
|||||||
static TMPDIR: Lazy<Mutex<Option<TempDir>>> = Lazy::new(|| Mutex::new(None));
|
static TMPDIR: Lazy<Mutex<Option<TempDir>>> = Lazy::new(|| Mutex::new(None));
|
||||||
|
|
||||||
const PID_FILE_NAME: &str = "qdrant.pid";
|
const PID_FILE_NAME: &str = "qdrant.pid";
|
||||||
|
const SIDECAR_TYPE:SidecarType = SidecarType::Qdrant;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ProvideQdrantInfo {
|
pub struct ProvideQdrantInfo {
|
||||||
@ -76,7 +78,7 @@ pub fn start_qdrant_server(){
|
|||||||
let snapshot_path = path.join("snapshots").to_str().unwrap().to_string();
|
let snapshot_path = path.join("snapshots").to_str().unwrap().to_string();
|
||||||
let init_path = path.join(".qdrant-initalized").to_str().unwrap().to_string();
|
let init_path = path.join(".qdrant-initalized").to_str().unwrap().to_string();
|
||||||
|
|
||||||
let mut qdrant_server_environment = HashMap::from_iter([
|
let qdrant_server_environment = HashMap::from_iter([
|
||||||
(String::from("QDRANT__SERVICE__HTTP_PORT"), QDRANT_SERVER_PORT_HTTP.to_string()),
|
(String::from("QDRANT__SERVICE__HTTP_PORT"), QDRANT_SERVER_PORT_HTTP.to_string()),
|
||||||
(String::from("QDRANT__SERVICE__GRPC_PORT"), QDRANT_SERVER_PORT_GRPC.to_string()),
|
(String::from("QDRANT__SERVICE__GRPC_PORT"), QDRANT_SERVER_PORT_GRPC.to_string()),
|
||||||
(String::from("QDRANT_INIT_FILE_PATH"), init_path),
|
(String::from("QDRANT_INIT_FILE_PATH"), init_path),
|
||||||
@ -90,13 +92,6 @@ pub fn start_qdrant_server(){
|
|||||||
|
|
||||||
let server_spawn_clone = QDRANT_SERVER.clone();
|
let server_spawn_clone = QDRANT_SERVER.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
qdrant_server_environment.insert(
|
|
||||||
"MALLOC_CONF".to_string(),
|
|
||||||
"background_thread:false".to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let (mut rx, child) = Command::new_sidecar("qdrant")
|
let (mut rx, child) = Command::new_sidecar("qdrant")
|
||||||
.expect("Failed to create sidecar for Qdrant")
|
.expect("Failed to create sidecar for Qdrant")
|
||||||
.args(["--config-path", "resources/databases/qdrant/config.yaml"])
|
.args(["--config-path", "resources/databases/qdrant/config.yaml"])
|
||||||
@ -106,7 +101,7 @@ pub fn start_qdrant_server(){
|
|||||||
|
|
||||||
let server_pid = child.pid();
|
let server_pid = child.pid();
|
||||||
info!(Source = "Bootloader Qdrant"; "Qdrant server process started with PID={server_pid}.");
|
info!(Source = "Bootloader Qdrant"; "Qdrant server process started with PID={server_pid}.");
|
||||||
log_potential_stale_process(path.join(PID_FILE_NAME), server_pid);
|
log_potential_stale_process(path.join(PID_FILE_NAME), server_pid, SIDECAR_TYPE);
|
||||||
|
|
||||||
// Save the server process to stop it later:
|
// Save the server process to stop it later:
|
||||||
*server_spawn_clone.lock().unwrap() = Some(child);
|
*server_spawn_clone.lock().unwrap() = Some(child);
|
||||||
@ -193,7 +188,7 @@ pub fn drop_tmpdir() {
|
|||||||
/// Remove old Pid files and kill the corresponding processes
|
/// Remove old Pid files and kill the corresponding processes
|
||||||
pub fn cleanup_qdrant() {
|
pub fn cleanup_qdrant() {
|
||||||
let pid_path = Path::new(DATA_DIRECTORY.get().unwrap()).join("databases").join("qdrant").join(PID_FILE_NAME);
|
let pid_path = Path::new(DATA_DIRECTORY.get().unwrap()).join("databases").join("qdrant").join(PID_FILE_NAME);
|
||||||
if let Err(e) = kill_stale_process(pid_path) {
|
if let Err(e) = kill_stale_process(pid_path, SIDECAR_TYPE) {
|
||||||
warn!(Source = "Qdrant"; "Error during the cleanup of Qdrant: {}", e);
|
warn!(Source = "Qdrant"; "Error during the cleanup of Qdrant: {}", e);
|
||||||
}
|
}
|
||||||
if let Err(e) = delete_old_certificates() {
|
if let Err(e) = delete_old_certificates() {
|
||||||
|
|||||||
15
runtime/src/sidecar_types.rs
Normal file
15
runtime/src/sidecar_types.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub enum SidecarType {
|
||||||
|
Dotnet,
|
||||||
|
Qdrant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SidecarType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
SidecarType::Dotnet => write!(f, ".Net"),
|
||||||
|
SidecarType::Qdrant => write!(f, "Qdrant"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ use std::io::{Error, ErrorKind, Write};
|
|||||||
use std::path::{PathBuf};
|
use std::path::{PathBuf};
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use sysinfo::{Pid, ProcessesToUpdate, Signal, System};
|
use sysinfo::{Pid, ProcessesToUpdate, Signal, System};
|
||||||
|
use crate::sidecar_types::SidecarType;
|
||||||
|
|
||||||
fn parse_pid_file(content: &str) -> Result<(u32, String), Error> {
|
fn parse_pid_file(content: &str) -> Result<(u32, String), Error> {
|
||||||
let mut lines = content
|
let mut lines = content
|
||||||
@ -23,7 +24,7 @@ fn parse_pid_file(content: &str) -> Result<(u32, String), Error> {
|
|||||||
Ok((pid, name))
|
Ok((pid, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kill_stale_process(pid_file_path: PathBuf) -> Result<(), Error> {
|
pub fn kill_stale_process(pid_file_path: PathBuf, sidecar_type: SidecarType) -> Result<(), Error> {
|
||||||
if !pid_file_path.exists() {
|
if !pid_file_path.exists() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -51,30 +52,24 @@ pub fn kill_stale_process(pid_file_path: PathBuf) -> Result<(), Error> {
|
|||||||
if !killed {
|
if !killed {
|
||||||
return Err(Error::new(ErrorKind::Other, "Failed to kill process"));
|
return Err(Error::new(ErrorKind::Other, "Failed to kill process"));
|
||||||
}
|
}
|
||||||
|
info!(Source="Stale Process Cleanup";"{}: Killed process: \"{}\"", sidecar_type,pid_file_path.display());
|
||||||
system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true);
|
|
||||||
if !system.process(pid).is_none() {
|
|
||||||
return Err(Error::new(ErrorKind::Other, "Process still running after kill attempt"))
|
|
||||||
}
|
|
||||||
info!("Killed process: {}", pid_file_path.display());
|
|
||||||
} else {
|
} else {
|
||||||
info!("Pid file {} was found, but process was not.", pid);
|
info!(Source="Stale Process Cleanup";"{}: Pid file with process number '{}' was found, but process was not.", sidecar_type, pid);
|
||||||
};
|
};
|
||||||
|
|
||||||
fs::remove_file(&pid_file_path)?;
|
fs::remove_file(&pid_file_path)?;
|
||||||
info!("Deleted redundant Pid file: {}", pid_file_path.display());
|
info!(Source="Stale Process Cleanup";"{}: Deleted redundant Pid file: \"{}\"", sidecar_type,pid_file_path.display());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_potential_stale_process(pid_file_path: PathBuf, pid: u32) {
|
pub fn log_potential_stale_process(pid_file_path: PathBuf, pid: u32, sidecar_type: SidecarType) {
|
||||||
let mut system = System::new_all();
|
let mut system = System::new_all();
|
||||||
let pid_u32 = pid;
|
let pid = Pid::from_u32(pid);
|
||||||
let pid = Pid::from_u32(pid_u32);
|
|
||||||
system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true);
|
system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true);
|
||||||
let Some(process) = system.process(pid) else {
|
let Some(process) = system.process(pid) else {
|
||||||
warn!(
|
warn!(Source="Stale Process Cleanup";
|
||||||
"Pid file {} was not created because the process was not found.",
|
"{}: Pid file with process number '{}' was not created because the process was not found.",
|
||||||
pid_u32
|
sidecar_type, pid
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -82,13 +77,13 @@ pub fn log_potential_stale_process(pid_file_path: PathBuf, pid: u32) {
|
|||||||
match File::create(&pid_file_path) {
|
match File::create(&pid_file_path) {
|
||||||
Ok(mut file) => {
|
Ok(mut file) => {
|
||||||
let name = process.name().to_string_lossy();
|
let name = process.name().to_string_lossy();
|
||||||
let content = format!("{pid_u32}\n{name}\n");
|
let content = format!("{pid}\n{name}\n");
|
||||||
if let Err(e) = file.write_all(content.as_bytes()) {
|
if let Err(e) = file.write_all(content.as_bytes()) {
|
||||||
warn!("Failed to write to {}: {}", pid_file_path.display(), e);
|
warn!(Source="Stale Process Cleanup";"{}: Failed to write to \"{}\": {}", sidecar_type,pid_file_path.display(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to create {}: {}", pid_file_path.display(), e);
|
warn!(Source="Stale Process Cleanup";"{}: Failed to create \"{}\": {}", sidecar_type, pid_file_path.display(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user