From 9d2b63bbaa035aaaab83d47cbfac4e45c2540a9c Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 27 Feb 2025 20:17:51 +0100 Subject: [PATCH] Added paths to log files in the "About" dialog for easier support (#304) --- app/MindWork AI Studio/Pages/About.razor | 32 ++++++++++++ app/MindWork AI Studio/Pages/About.razor.cs | 33 ++++++++++++- .../Tools/Rust/GetLogPathsResponse.cs | 3 ++ .../Tools/Services/RustService.Log.cs | 15 ++++++ .../wwwroot/changelog/v0.9.31.md | 1 + runtime/src/log.rs | 49 +++++++++++++++---- runtime/src/runtime_api.rs | 1 + 7 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/Rust/GetLogPathsResponse.cs create mode 100644 app/MindWork AI Studio/Tools/Services/RustService.Log.cs diff --git a/app/MindWork AI Studio/Pages/About.razor b/app/MindWork AI Studio/Pages/About.razor index 60746420..f4f96af3 100644 --- a/app/MindWork AI Studio/Pages/About.razor +++ b/app/MindWork AI Studio/Pages/About.razor @@ -26,6 +26,38 @@ + + + + Explanation + + + AI Studio creates a log file at startup, in which events during startup are recorded. After startup, + another log file is created that records all events that occur during the use of the app. This + includes any errors that may occur. Depending on when an error occurs (at startup or during use), + the contents of these log files can be helpful for troubleshooting. Sensitive information such as + passwords is not included in the log files. + + + + By clicking on the respective path, the path is copied to the clipboard. You might open these files + with a text editor to view their contents. + + + + Startup log file + + + + + + + Usage log file + + + + + diff --git a/app/MindWork AI Studio/Pages/About.razor.cs b/app/MindWork AI Studio/Pages/About.razor.cs index 100ebdbe..51727506 100644 --- a/app/MindWork AI Studio/Pages/About.razor.cs +++ b/app/MindWork AI Studio/Pages/About.razor.cs @@ -1,5 +1,8 @@ using System.Reflection; +using AIStudio.Tools.Rust; +using AIStudio.Tools.Services; + using Microsoft.AspNetCore.Components; namespace AIStudio.Pages; @@ -9,9 +12,15 @@ public partial class About : ComponentBase [Inject] private MessageBus MessageBus { get; init; } = null!; + [Inject] + private RustService RustService { get; init; } = null!; + + [Inject] + private ISnackbar Snackbar { get; init; } = null!; + private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly(); private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute()!; - + private static string VersionDotnetRuntime => $"Used .NET runtime: v{META_DATA.DotnetVersion}"; private static string VersionDotnetSdk => $"Used .NET SDK: v{META_DATA.DotnetSdkVersion}"; @@ -26,6 +35,28 @@ public partial class About : ComponentBase private static string TauriVersion => $"Tauri: v{META_DATA.TauriVersion}"; + private GetLogPathsResponse logPaths; + + #region Overrides of ComponentBase + + private async Task CopyStartupLogPath() + { + await this.RustService.CopyText2Clipboard(this.Snackbar, this.logPaths.LogStartupPath); + } + + private async Task CopyAppLogPath() + { + await this.RustService.CopyText2Clipboard(this.Snackbar, this.logPaths.LogAppPath); + } + + protected override async Task OnInitializedAsync() + { + this.logPaths = await this.RustService.GetLogPaths(); + await base.OnInitializedAsync(); + } + + #endregion + private const string LICENSE = """ # Functional Source License, Version 1.1, MIT Future License diff --git a/app/MindWork AI Studio/Tools/Rust/GetLogPathsResponse.cs b/app/MindWork AI Studio/Tools/Rust/GetLogPathsResponse.cs new file mode 100644 index 00000000..4cd6169e --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/GetLogPathsResponse.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Tools.Rust; + +public readonly record struct GetLogPathsResponse(string LogStartupPath, string LogAppPath); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Log.cs b/app/MindWork AI Studio/Tools/Services/RustService.Log.cs new file mode 100644 index 00000000..e7b52438 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Services/RustService.Log.cs @@ -0,0 +1,15 @@ +using AIStudio.Tools.Rust; + +namespace AIStudio.Tools.Services; + +public sealed partial class RustService +{ + /// + /// Get the paths of the log files. + /// + /// The paths of the log files. + public async Task GetLogPaths() + { + return await this.http.GetFromJsonAsync("/log/paths", this.jsonRustSerializerOptions); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.31.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.31.md index 51bb5e75..b24969ab 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.31.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.31.md @@ -2,3 +2,4 @@ - Added Helmholtz (aka "Blablador") as provider. This provider is available to all researchers and employees of the 18 Helmholtz Centers as well as all eduGAIN organizations worldwide. - Added GWDG SAIA as provider. This provider is available to all researchers and employees of the GWDG, the Max Planck Society, the 18 Helmholtz Centers, and most German universities. - Added DeepSeek as provider. +- Added paths to log files in the "About" dialog for easier troubleshooting and support. \ No newline at end of file diff --git a/runtime/src/log.rs b/runtime/src/log.rs index 30befd46..4a244713 100644 --- a/runtime/src/log.rs +++ b/runtime/src/log.rs @@ -7,10 +7,18 @@ use flexi_logger::{DeferredNow, Duplicate, FileSpec, Logger, LoggerHandle}; use flexi_logger::writers::FileLogWriter; use log::kv; use log::kv::{Key, Value, VisitSource}; +use rocket::get; +use rocket::serde::json::Json; +use rocket::serde::Serialize; +use crate::api_token::APIToken; use crate::environment::is_dev; static LOGGER: OnceLock = OnceLock::new(); +static LOG_STARTUP_PATH: OnceLock = OnceLock::new(); + +static LOG_APP_PATH: OnceLock = OnceLock::new(); + /// Initialize the logging system. pub fn init_logging() { @@ -42,11 +50,16 @@ pub fn init_logging() { false => "AI Studio Events", }; + let log_path = FileSpec::default() + .basename(log_basename) + .suppress_timestamp() + .suffix("log"); + + // Store the startup log path: + LOG_STARTUP_PATH.set(log_path.as_pathbuf(None).canonicalize().unwrap().to_str().unwrap().to_string()).expect("Cannot store the startup log path"); + let runtime_logger = Logger::try_with_str(log_config).expect("Cannot create logging") - .log_to_file(FileSpec::default() - .basename(log_basename) - .suppress_timestamp() - .suffix("log")) + .log_to_file(log_path) .duplicate_to_stdout(Duplicate::All) .use_utc() .format_for_files(file_logger_format) @@ -63,12 +76,13 @@ pub fn init_logging() { /// Switch the logging system to a file-based output. pub fn switch_to_file_logging(logger_path: PathBuf) -> Result<(), Box>{ - LOGGER.get().expect("No LOGGER was set").handle.reset_flw(&FileLogWriter::builder( - FileSpec::default() - .directory(logger_path) - .basename("events") - .suppress_timestamp() - .suffix("log")))?; + let log_path = FileSpec::default() + .directory(logger_path) + .basename("events") + .suppress_timestamp() + .suffix("log"); + LOG_APP_PATH.set(log_path.as_pathbuf(None).to_str().unwrap().to_string()).expect("Cannot store the app log path"); + LOGGER.get().expect("No LOGGER was set").handle.reset_flw(&FileLogWriter::builder(log_path))?; Ok(()) } @@ -160,4 +174,19 @@ fn file_logger_format( // Write the log message: write!(w, "{}", &record.args()) +} + +#[get("/log/paths")] +pub async fn get_log_paths(_token: APIToken) -> Json { + Json(LogPathsResponse { + log_startup_path: LOG_STARTUP_PATH.get().expect("No startup log path was set").clone(), + log_app_path: LOG_APP_PATH.get().expect("No app log path was set").clone(), + }) +} + +/// The response the get log paths request. +#[derive(Serialize)] +pub struct LogPathsResponse { + log_startup_path: String, + log_app_path: String, } \ No newline at end of file diff --git a/runtime/src/runtime_api.rs b/runtime/src/runtime_api.rs index 1fa5bc75..728c4a55 100644 --- a/runtime/src/runtime_api.rs +++ b/runtime/src/runtime_api.rs @@ -77,6 +77,7 @@ pub fn start_runtime_api() { crate::secret::delete_secret, crate::environment::get_data_directory, crate::environment::get_config_directory, + crate::log::get_log_paths, ]) .ignite().await.unwrap() .launch().await.unwrap();