mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-13 17:13:48 +00:00
Logged .NET events with the time they happened (#1042)
This commit is contained in:
1 parent
ca6ad6b1f0
commit
4616d1a4b2
12 files changed
+354
-17
No files matched your search
@@ -10129,6 +10129,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3454691558"] = "You are running
|
||||
-- Unknown error
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3461425987"] = "Unknown error"
|
||||
|
||||
-- chrono handles dates and times in the Rust runtime. We use it to record in the log when each event actually happened, even when the message about it arrives a moment later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3489383665"] = "chrono handles dates and times in the Rust runtime. We use it to record in the log when each event actually happened, even when the message about it arrives a moment later."
|
||||
|
||||
-- Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3494984593"] = "Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!"
|
||||
|
||||
|
||||
@@ -382,6 +382,7 @@
|
||||
<ThirdPartyComponent Name="futures" Developer="Alex Crichton, Taiki Endo, Taylor Cramer, Nemo157, Josef Brandl, Aaron Turon & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rust-lang/futures-rs/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rust-lang/futures-rs" UseCase="@T("This is a library providing the foundations for asynchronous programming in Rust. It includes key trait definitions like Stream, as well as utilities like join!, select!, and various futures combinator methods which enable expressive asynchronous control flow.")"/>
|
||||
<ThirdPartyComponent Name="async-stream" Developer="Carl Lerche, Taiki Endo & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tokio-rs/async-stream/blob/master/LICENSE" RepositoryUrl="https://github.com/tokio-rs/async-stream" UseCase="@T("This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system.")"/>
|
||||
<ThirdPartyComponent Name="flexi_logger" Developer="emabee & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/emabee/flexi_logger/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/emabee/flexi_logger" UseCase="@T("This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible.")"/>
|
||||
<ThirdPartyComponent Name="chrono" Developer="Paul Dicker, Kang Seonghoon, Dirkjan Ochtman, Brandon W Maister, Eric Sheppard, James Thomas Moon & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/chronotope/chrono/blob/main/LICENSE.txt" RepositoryUrl="https://github.com/chronotope/chrono" UseCase="@T("chrono handles dates and times in the Rust runtime. We use it to record in the log when each event actually happened, even when the message about it arrives a moment later.")"/>
|
||||
<ThirdPartyComponent Name="dirs" Developer="soc, Wang Xuerui & Open Source Community" LicenseName="MIT" LicenseUrl="https://codeberg.org/dirs/dirs-rs/src/branch/main/LICENSE-MIT" RepositoryUrl="https://codeberg.org/dirs/dirs-rs" UseCase="@T("dirs determines the platform-specific local application data directory. AI Studio uses it so the Flatpak startup log is written to the same application data directory that Tauri uses.")"/>
|
||||
<ThirdPartyComponent Name="rand" Developer="Rust developers & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rust-random/rand/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rust-random/rand" UseCase="@T("We must generate random numbers, e.g., for securing the interprocess communication between the user interface and the runtime. The rand library is great for this purpose.")"/>
|
||||
<ThirdPartyComponent Name="pptx-to-md" Developer="Nils Kruthoff & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/nilskruthoff/pptx-parser/blob/master/LICENCE-MIT" RepositoryUrl="https://github.com/nilskruthoff/pptx-parser" UseCase="@T("We use this library to be able to read PowerPoint files. This allows us to insert content from slides into prompts and take PowerPoint files into account in RAG processes. We thank Nils Kruthoff for his work on this Rust crate.")"/>
|
||||
|
||||
+3
@@ -10131,6 +10131,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3454691558"] = "Sie verwenden ei
|
||||
-- Unknown error
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3461425987"] = "Unbekannter Fehler"
|
||||
|
||||
-- chrono handles dates and times in the Rust runtime. We use it to record in the log when each event actually happened, even when the message about it arrives a moment later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3489383665"] = "chrono verarbeitet Datums- und Zeitangaben in der Rust-Laufzeitumgebung. Damit halten wir im Protokoll fest, wann jedes Ereignis tatsächlich stattgefunden hat – auch wenn die entsprechende Nachricht erst einen Moment später eintrifft."
|
||||
|
||||
-- Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3494984593"] = "Tauri wird verwendet, um die Blazor-Benutzeroberfläche bereitzustellen. Es ist ein großartiges Projekt, das die Erstellung von Desktop-Anwendungen mit Webtechnologien ermöglicht. Ich liebe Tauri!"
|
||||
|
||||
|
||||
+3
@@ -10131,6 +10131,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3454691558"] = "You are running
|
||||
-- Unknown error
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3461425987"] = "Unknown error"
|
||||
|
||||
-- chrono handles dates and times in the Rust runtime. We use it to record in the log when each event actually happened, even when the message about it arrives a moment later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3489383665"] = "chrono handles dates and times in the Rust runtime. We use it to record in the log when each event actually happened, even when the message about it arrives a moment later."
|
||||
|
||||
-- Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3494984593"] = "Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!"
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>
|
||||
/// A log event the Rust runtime writes to the log file.
|
||||
/// </summary>
|
||||
/// <param name="Timestamp">When the event happened, in the round-trip format of TerminalLogger.FormatTransportTimestamp, e.g., 2026-10-09T17:53:54.5629130+00:00. The Rust runtime logs the event with this time; when it cannot read it, the event shows the time it arrived instead.</param>
|
||||
/// <param name="Level">The log level.</param>
|
||||
/// <param name="Category">The category of the log event.</param>
|
||||
/// <param name="Message">The log message.</param>
|
||||
/// <param name="Exception">Optional exception message.</param>
|
||||
/// <param name="StackTrace">Optional exception stack trace.</param>
|
||||
public readonly record struct LogEventRequest(
|
||||
string Timestamp,
|
||||
string Level,
|
||||
|
||||
@@ -16,7 +16,7 @@ public sealed partial class RustService
|
||||
/// <summary>
|
||||
/// Sends a log event to the Rust runtime.
|
||||
/// </summary>
|
||||
/// <param name="timestamp">The timestamp of the log event.</param>
|
||||
/// <param name="timestamp">When the log event happened, formatted by TerminalLogger.FormatTransportTimestamp.</param>
|
||||
/// <param name="level">The log level.</param>
|
||||
/// <param name="category">The category of the log event.</param>
|
||||
/// <param name="message">The log message.</param>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
@@ -59,7 +60,9 @@ public sealed class TerminalLogger() : ConsoleFormatter(FORMATTER_NAME)
|
||||
public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter)
|
||||
{
|
||||
var message = logEntry.Formatter(logEntry.State, logEntry.Exception);
|
||||
var timestamp = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var timestamp = now.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
|
||||
var transportTimestamp = FormatTransportTimestamp(now);
|
||||
var logLevel = logEntry.LogLevel.ToString();
|
||||
var category = logEntry.Category;
|
||||
var exceptionMessage = logEntry.Exception?.Message;
|
||||
@@ -85,13 +88,26 @@ public sealed class TerminalLogger() : ConsoleFormatter(FORMATTER_NAME)
|
||||
|
||||
// Send log event to Rust via API (fire-and-forget):
|
||||
if (RUST_SERVICE is not null)
|
||||
RUST_SERVICE.LogEvent(timestamp, logLevel, category, message, exceptionMessage, stackTrace);
|
||||
RUST_SERVICE.LogEvent(transportTimestamp, logLevel, category, message, exceptionMessage, stackTrace);
|
||||
|
||||
// Buffer early log events until the RustService is available:
|
||||
else
|
||||
EARLY_LOG_BUFFER.Enqueue(new LogEventRequest(timestamp, logLevel, category, message, exceptionMessage, stackTrace));
|
||||
EARLY_LOG_BUFFER.Enqueue(new LogEventRequest(transportTimestamp, logLevel, category, message, exceptionMessage, stackTrace));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the time of a log event for the Rust runtime.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The round-trip format is ISO 8601 with seven fractional digits and the offset, e.g.,
|
||||
/// 2026-10-09T17:53:54.5629130+00:00, and it is the same in every culture. The Rust runtime
|
||||
/// reads it as RFC 3339, so it can log the event with the time it happened instead of the
|
||||
/// time it arrived.
|
||||
/// </remarks>
|
||||
/// <param name="timestamp">The time of the log event.</param>
|
||||
/// <returns>The time in the round-trip format.</returns>
|
||||
public static string FormatTransportTimestamp(DateTimeOffset timestamp) => timestamp.ToString("O", CultureInfo.InvariantCulture);
|
||||
|
||||
private static string GetColorForLogLevel(LogLevel logLevel) => logLevel switch
|
||||
{
|
||||
LogLevel.Trace => ANSI_GRAY,
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
- Fixed OpenAI models using about 4,300 more tokens than necessary with every request. AI Studio offered them OpenAI's own web search in the background, even when you had not selected a web search. To let a model search the web, select the Web Search tool below the message field.
|
||||
- Fixed AI Studio sometimes rebuilding the index of a data source from scratch when the configuration of your organization was applied while the data source was being updated.
|
||||
- Fixed justified texts being hyphenated by the rules of English even when AI Studio shows another language, such as German. Screen readers now also know which language AI Studio uses.
|
||||
- Fixed the log file of AI Studio showing the wrong time for some entries when AI Studio was busy. Each entry now shows when it actually happened.
|
||||
- Updated the code contributions on the supporters page, which now thank everyone who has contributed code to AI Studio so far.
|
||||
- Upgraded several libraries to improve security.
|
||||
- Upgraded to Rust v1.99.0
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Globalization;
|
||||
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Tests.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Pins the format in which log events travel to the Rust runtime.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Rust runtime reads this format to log each event with the time it happened. Its tests in
|
||||
/// runtime/src/log.rs parse the same text, so both sides have to change together.
|
||||
/// </remarks>
|
||||
[TestFixture]
|
||||
public sealed class TerminalLoggerTests
|
||||
{
|
||||
private const string TRANSPORT_TIMESTAMP = "2026-10-09T17:53:54.5629130+00:00";
|
||||
|
||||
private static readonly DateTimeOffset EVENT_TIME = new DateTimeOffset(2026, 10, 9, 17, 53, 54, TimeSpan.Zero).AddTicks(5_629_130);
|
||||
|
||||
[Test]
|
||||
public void TheTransportTimestampIsPinned()
|
||||
{
|
||||
Assert.That(TerminalLogger.FormatTransportTimestamp(EVENT_TIME), Is.EqualTo(TRANSPORT_TIMESTAMP));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TheTransportTimestampIgnoresTheCurrentCulture()
|
||||
{
|
||||
var culture = (CultureInfo)CultureInfo.InvariantCulture.Clone();
|
||||
culture.DateTimeFormat.TimeSeparator = ".";
|
||||
|
||||
var previousCulture = CultureInfo.CurrentCulture;
|
||||
try
|
||||
{
|
||||
CultureInfo.CurrentCulture = culture;
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(EVENT_TIME.ToString("yyyy-MM-dd HH:mm:ss.fff"), Is.EqualTo("2026-10-09 17.53.54.562"), "The format sent before followed the time separator of the current culture.");
|
||||
Assert.That(TerminalLogger.FormatTransportTimestamp(EVENT_TIME), Is.EqualTo(TRANSPORT_TIMESTAMP));
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
CultureInfo.CurrentCulture = previousCulture;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user