Improved logging behaviour

This commit is contained in:
PaulKoudelka 2026-02-02 18:02:32 +01:00
parent 95c6d19c08
commit 0dd7d65efc
7 changed files with 53 additions and 33 deletions

View File

@ -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="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="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="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.")"/>

View File

@ -45,7 +45,19 @@ Do you want to test your changes before creating a PR? Follow these steps:
9. Execute the command `dotnet run`.
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 IDEs 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
In order to create a release:

View File

@ -17,6 +17,7 @@ use crate::environment::{is_dev, DATA_DIRECTORY};
use crate::network::get_available_port;
use crate::runtime_api::API_SERVER_PORT;
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
// 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));
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
/// 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.");
let server_pid = child.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:
*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
pub fn cleanup_dotnet_server() {
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);
}
}

View File

@ -16,4 +16,5 @@ pub mod pandoc;
pub mod qdrant;
pub mod certificate_factory;
pub mod runtime_api_token;
pub mod stale_process_cleanup;
pub mod stale_process_cleanup;
mod sidecar_types;

View File

@ -17,6 +17,7 @@ use crate::certificate_factory::generate_certificate;
use std::path::PathBuf;
use tempfile::{TempDir, Builder};
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
// 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));
const PID_FILE_NAME: &str = "qdrant.pid";
const SIDECAR_TYPE:SidecarType = SidecarType::Qdrant;
#[derive(Serialize)]
pub struct ProvideQdrantInfo {
@ -76,7 +78,7 @@ pub fn start_qdrant_server(){
let snapshot_path = path.join("snapshots").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__GRPC_PORT"), QDRANT_SERVER_PORT_GRPC.to_string()),
(String::from("QDRANT_INIT_FILE_PATH"), init_path),
@ -90,13 +92,6 @@ pub fn start_qdrant_server(){
let server_spawn_clone = QDRANT_SERVER.clone();
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")
.expect("Failed to create sidecar for Qdrant")
.args(["--config-path", "resources/databases/qdrant/config.yaml"])
@ -106,7 +101,7 @@ pub fn start_qdrant_server(){
let server_pid = child.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:
*server_spawn_clone.lock().unwrap() = Some(child);
@ -193,7 +188,7 @@ pub fn drop_tmpdir() {
/// Remove old Pid files and kill the corresponding processes
pub fn cleanup_qdrant() {
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);
}
if let Err(e) = delete_old_certificates() {

View 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"),
}
}
}

View File

@ -4,6 +4,7 @@ use std::io::{Error, ErrorKind, Write};
use std::path::{PathBuf};
use log::{info, warn};
use sysinfo::{Pid, ProcessesToUpdate, Signal, System};
use crate::sidecar_types::SidecarType;
fn parse_pid_file(content: &str) -> Result<(u32, String), Error> {
let mut lines = content
@ -23,7 +24,7 @@ fn parse_pid_file(content: &str) -> Result<(u32, String), Error> {
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() {
return Ok(());
}
@ -51,30 +52,24 @@ pub fn kill_stale_process(pid_file_path: PathBuf) -> Result<(), Error> {
if !killed {
return Err(Error::new(ErrorKind::Other, "Failed to kill process"));
}
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());
info!(Source="Stale Process Cleanup";"{}: Killed process: \"{}\"", sidecar_type,pid_file_path.display());
} 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)?;
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(())
}
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 pid_u32 = pid;
let pid = Pid::from_u32(pid_u32);
let pid = Pid::from_u32(pid);
system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true);
let Some(process) = system.process(pid) else {
warn!(
"Pid file {} was not created because the process was not found.",
pid_u32
warn!(Source="Stale Process Cleanup";
"{}: Pid file with process number '{}' was not created because the process was not found.",
sidecar_type, pid
);
return;
};
@ -82,13 +77,13 @@ pub fn log_potential_stale_process(pid_file_path: PathBuf, pid: u32) {
match File::create(&pid_file_path) {
Ok(mut file) => {
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()) {
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) => {
warn!("Failed to create {}: {}", pid_file_path.display(), e);
warn!(Source="Stale Process Cleanup";"{}: Failed to create \"{}\": {}", sidecar_type, pid_file_path.display(), e);
}
}
}