From 00237b178486f4b555ff11d8accf97b202d17b85 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 13 Jan 2026 10:00:36 +0100 Subject: [PATCH] Refactored log merging --- app/MindWork AI Studio/Program.cs | 1 + .../Tools/Rust/LogEventRequest.cs | 9 ++++ .../Tools/Rust/LogEventResponse.cs | 3 ++ .../Tools/Services/RustService.Log.cs | 23 +++++++++ .../Tools/TerminalLogger.cs | 27 ++++++++-- .../wwwroot/changelog/v26.1.2.md | 3 +- runtime/src/dotnet.rs | 40 ++------------- runtime/src/log.rs | 49 +++++++++++++++++-- runtime/src/runtime_api.rs | 1 + 9 files changed, 111 insertions(+), 45 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/Rust/LogEventRequest.cs create mode 100644 app/MindWork AI Studio/Tools/Rust/LogEventResponse.cs diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs index fa7927b1..e87dc0ca 100644 --- a/app/MindWork AI Studio/Program.cs +++ b/app/MindWork AI Studio/Program.cs @@ -185,6 +185,7 @@ internal sealed class Program var rustLogger = app.Services.GetRequiredService>(); rust.SetLogger(rustLogger); rust.SetEncryptor(encryption); + TerminalLogger.SetRustService(rust); RUST_SERVICE = rust; ENCRYPTION = encryption; diff --git a/app/MindWork AI Studio/Tools/Rust/LogEventRequest.cs b/app/MindWork AI Studio/Tools/Rust/LogEventRequest.cs new file mode 100644 index 00000000..18e95aed --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/LogEventRequest.cs @@ -0,0 +1,9 @@ +namespace AIStudio.Tools.Rust; + +public readonly record struct LogEventRequest( + string Timestamp, + string Level, + string Category, + string Message, + string? Exception +); diff --git a/app/MindWork AI Studio/Tools/Rust/LogEventResponse.cs b/app/MindWork AI Studio/Tools/Rust/LogEventResponse.cs new file mode 100644 index 00000000..1c8c1055 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/LogEventResponse.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Tools.Rust; + +public readonly record struct LogEventResponse(bool Success, string Issue); diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Log.cs b/app/MindWork AI Studio/Tools/Services/RustService.Log.cs index e7b52438..595a9708 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.Log.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.Log.cs @@ -12,4 +12,27 @@ public sealed partial class RustService { return await this.http.GetFromJsonAsync("/log/paths", this.jsonRustSerializerOptions); } + + /// + /// Sends a log event to the Rust runtime. + /// + /// The timestamp of the log event. + /// The log level. + /// The category of the log event. + /// The log message. + /// Optional exception details. + public void LogEvent(string timestamp, string level, string category, string message, string? exception = null) + { + try + { + // Fire-and-forget the log event to avoid blocking: + var request = new LogEventRequest(timestamp, level, category, message, exception); + _ = this.http.PostAsJsonAsync("/log/event", request, this.jsonRustSerializerOptions); + } + catch + { + Console.WriteLine("Failed to send log event to Rust service."); + // Ignore errors to avoid log loops + } + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/TerminalLogger.cs b/app/MindWork AI Studio/Tools/TerminalLogger.cs index ce87feb4..88be6f19 100644 --- a/app/MindWork AI Studio/Tools/TerminalLogger.cs +++ b/app/MindWork AI Studio/Tools/TerminalLogger.cs @@ -1,3 +1,5 @@ +using AIStudio.Tools.Services; + using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Console; @@ -6,6 +8,17 @@ namespace AIStudio.Tools; public sealed class TerminalLogger() : ConsoleFormatter(FORMATTER_NAME) { public const string FORMATTER_NAME = "AI Studio Terminal Logger"; + + private static RustService? RUST_SERVICE; + + /// + /// Sets the Rust service for logging events. + /// + /// The Rust service instance. + public static void SetRustService(RustService service) + { + RUST_SERVICE = service; + } public override void Write(in LogEntry logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter) { @@ -13,11 +26,15 @@ public sealed class TerminalLogger() : ConsoleFormatter(FORMATTER_NAME) var timestamp = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff"); var logLevel = logEntry.LogLevel.ToString(); var category = logEntry.Category; - - textWriter.Write($"=> {timestamp} [{logLevel}] {category}: {message}"); - if (logEntry.Exception is not null) - textWriter.Write($" Exception was = {logEntry.Exception}"); - + var exception = logEntry.Exception?.ToString(); + + textWriter.Write($"[{timestamp}] {logLevel} [{category}] {message}"); + if (exception is not null) + textWriter.Write($" Exception: {exception}"); + textWriter.WriteLine(); + + // Send log event to Rust via API (fire-and-forget): + RUST_SERVICE?.LogEvent(timestamp, logLevel, category, message, exception); } } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.1.2.md b/app/MindWork AI Studio/wwwroot/changelog/v26.1.2.md index b05871b0..a861ccb0 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.1.2.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.1.2.md @@ -1,2 +1,3 @@ # v26.1.2, build 232 (2026-01-xx xx:xx UTC) -- Added the option to hide specific assistants by configuration plugins. This is useful for enterprise environments in organizations. \ No newline at end of file +- Added the option to hide specific assistants by configuration plugins. This is useful for enterprise environments in organizations. +- Fixed a logging bug that prevented log events from being recorded in some cases. \ No newline at end of file diff --git a/runtime/src/dotnet.rs b/runtime/src/dotnet.rs index 26b793f5..5d3f4d71 100644 --- a/runtime/src/dotnet.rs +++ b/runtime/src/dotnet.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; use base64::Engine; use base64::prelude::BASE64_STANDARD; -use log::{debug, error, info, warn}; +use log::{error, info, warn}; use once_cell::sync::Lazy; use rocket::get; use tauri::api::process::{Command, CommandChild, CommandEvent}; @@ -101,43 +101,11 @@ pub fn start_dotnet_server() { *server_spawn_clone.lock().unwrap() = Some(child); // Log the output of the .NET server: + // NOTE: Log events are sent via structured HTTP API calls. + // This loop serves for fundamental output (e.g., startup errors). while let Some(CommandEvent::Stdout(line)) = rx.recv().await { - - // Remove newline characters from the end: let line = line.trim_end(); - - // Starts the line with '=>'? - if line.starts_with("=>") { - // Yes. This means that the line is a log message from the .NET server. - // The format is: ' [] : '. - // We try to parse this line and log it with the correct log level: - let line = line.trim_start_matches("=>").trim(); - let parts = line.split_once(": ").unwrap(); - let left_part = parts.0.trim(); - let message = parts.1.trim(); - let parts = left_part.split_once("] ").unwrap(); - let level = parts.0.split_once("[").unwrap().1.trim(); - let source = parts.1.trim(); - match level { - "Trace" => debug!(Source = ".NET Server", Comp = source; "{message}"), - "Debug" => debug!(Source = ".NET Server", Comp = source; "{message}"), - "Information" => info!(Source = ".NET Server", Comp = source; "{message}"), - "Warning" => warn!(Source = ".NET Server", Comp = source; "{message}"), - "Error" => error!(Source = ".NET Server", Comp = source; "{message}"), - "Critical" => error!(Source = ".NET Server", Comp = source; "{message}"), - - _ => error!(Source = ".NET Server", Comp = source; "{message} (unknown log level '{level}')"), - } - } else { - let lower_line = line.to_lowercase(); - if lower_line.contains("error") { - error!(Source = ".NET Server"; "{line}"); - } else if lower_line.contains("warning") { - warn!(Source = ".NET Server"; "{line}"); - } else { - info!(Source = ".NET Server"; "{line}"); - } - } + info!(Source = ".NET Server (stdout)"; "{line}"); } }); } diff --git a/runtime/src/log.rs b/runtime/src/log.rs index b626639a..d9d403f3 100644 --- a/runtime/src/log.rs +++ b/runtime/src/log.rs @@ -6,11 +6,11 @@ use std::path::{absolute, PathBuf}; use std::sync::OnceLock; use flexi_logger::{DeferredNow, Duplicate, FileSpec, Logger, LoggerHandle}; use flexi_logger::writers::FileLogWriter; -use log::kv; +use log::{debug, error, info, kv, warn}; use log::kv::{Key, Value, VisitSource}; -use rocket::get; +use rocket::{get, post}; use rocket::serde::json::Json; -use rocket::serde::Serialize; +use rocket::serde::{Deserialize, Serialize}; use crate::api_token::APIToken; use crate::environment::is_dev; @@ -231,9 +231,52 @@ pub async fn get_log_paths(_token: APIToken) -> Json { }) } +/// Logs an event from the .NET server. +#[post("/log/event", data = "")] +pub fn log_event(_token: APIToken, event: Json) -> Json { + let event = event.into_inner(); + let message = &event.message.as_str(); + let category = &event.category.as_str(); + + // Log with the appropriate level + match event.level.as_str() { + "Trace" | "Debug" => debug!(Source = ".NET Server", Comp = category; "{message}"), + "Information" => info!(Source = ".NET Server", Comp = category; "{message}"), + "Warning" => warn!(Source = ".NET Server", Comp = category; "{message}"), + "Error" | "Critical" => { + if let Some(ref ex) = event.exception { + error!(Source = ".NET Server", Comp = category; "{message} Exception: {ex}") + } else { + error!(Source = ".NET Server", Comp = category; "{message}") + } + }, + _ => error!(Source = ".NET Server", Comp = category; "{message} (unknown log level '{}')", event.level), + } + + Json(LogEventResponse { success: true, issue: String::new() }) +} + /// The response the get log paths request. #[derive(Serialize)] pub struct LogPathsResponse { log_startup_path: String, log_app_path: String, +} + +/// A log event from the .NET server. +#[derive(Deserialize)] +#[allow(unused)] +pub struct LogEvent { + timestamp: String, + level: String, + category: String, + message: String, + exception: Option, +} + +/// The response to a log event request. +#[derive(Serialize)] +pub struct LogEventResponse { + success: bool, + issue: String, } \ No newline at end of file diff --git a/runtime/src/runtime_api.rs b/runtime/src/runtime_api.rs index 745b82c4..0b1cc8c1 100644 --- a/runtime/src/runtime_api.rs +++ b/runtime/src/runtime_api.rs @@ -86,6 +86,7 @@ pub fn start_runtime_api() { crate::environment::read_enterprise_env_config_server_url, crate::file_data::extract_data, crate::log::get_log_paths, + crate::log::log_event, ]) .ignite().await.unwrap() .launch().await.unwrap();