From 9527c9ca09c37e004e264ad284a2e060b86b70b4 Mon Sep 17 00:00:00 2001 From: Lilian Date: Tue, 13 Aug 2024 23:48:21 +0200 Subject: [PATCH] [frontend] Add InMemory Logger --- Iceshrimp.Frontend/Components/ErrorUi.razor | 31 +++++++++++++++++-- .../Components/ErrorUi.razor.css | 6 ++-- .../Components/ErrorUi.razor.js | 13 ++++++++ .../Core/InMemoryLogger/InMemoryLogService.cs | 27 ++++++++++++++++ .../Core/InMemoryLogger/InMemoryLogger.cs | 18 +++++++++++ .../InMemoryLoggerConfiguration.cs | 7 +++++ .../InMemoryLogger/InMemoryLoggerExtension.cs | 30 ++++++++++++++++++ Iceshrimp.Frontend/Iceshrimp.Frontend.csproj | 1 + Iceshrimp.Frontend/Pages/SingleNote.razor | 7 +++-- Iceshrimp.Frontend/Startup.cs | 2 ++ 10 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 Iceshrimp.Frontend/Components/ErrorUi.razor.js create mode 100644 Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogService.cs create mode 100644 Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogger.cs create mode 100644 Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLoggerConfiguration.cs create mode 100644 Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLoggerExtension.cs diff --git a/Iceshrimp.Frontend/Components/ErrorUi.razor b/Iceshrimp.Frontend/Components/ErrorUi.razor index e7fed3b0..b32e6e54 100644 --- a/Iceshrimp.Frontend/Components/ErrorUi.razor +++ b/Iceshrimp.Frontend/Components/ErrorUi.razor @@ -1,9 +1,13 @@ +@using System.Text +@using Iceshrimp.Frontend.Core.InMemoryLogger @using Iceshrimp.Frontend.Core.Services @using Iceshrimp.Frontend.Localization @using Microsoft.Extensions.Localization @inject IStringLocalizer Loc; @inject NavigationManager Navigation; @inject VersionService Version; +@inject InMemoryLogService LogService; +@inject IJSRuntime Js;

@Loc["Unhandled Exception has occured"]

@@ -14,7 +18,7 @@ @Loc["Providing the below stack trace and version information will aid in debugging."]
-
+
@Exception.GetType(): @Exception.Message @Exception.StackTrace
@@ -34,16 +38,39 @@ }
+ @Loc["Logs"] +
+ @foreach (var line in _logs) + { + @line
+ } +
+ @code { - [Parameter] [EditorRequired] public required Exception Exception { get; set; } + [Parameter] [EditorRequired] public required Exception Exception { get; set; } + private IReadOnlyCollection _logs = []; + private IJSInProcessObjectReference _module = null!; private void Reload() { Navigation.Refresh(true); } + + protected override async Task OnInitializedAsync() + { + _logs = LogService.GetLogs(); + _module = (IJSInProcessObjectReference) await Js.InvokeAsync("import", "./Components/ErrorUi.razor.js"); + } + + private void DownloadLogs() + { + var logBytes = _logs.SelectMany(p => Encoding.UTF8.GetBytes(p)).ToArray(); + _module.InvokeVoid("DownloadFile", "log.txt", "text/plain", logBytes); + } + } \ No newline at end of file diff --git a/Iceshrimp.Frontend/Components/ErrorUi.razor.css b/Iceshrimp.Frontend/Components/ErrorUi.razor.css index 2ddd9da2..de298817 100644 --- a/Iceshrimp.Frontend/Components/ErrorUi.razor.css +++ b/Iceshrimp.Frontend/Components/ErrorUi.razor.css @@ -1,9 +1,9 @@ -.stacktrace { +.log-block { margin-top: 1rem; align-self: center; max-width: 80vw; max-height: 50vh; - overflow: scroll; + overflow: auto; background-color: black; border-radius: 1rem; padding: 0.5rem; @@ -31,7 +31,7 @@ code { .value { margin-bottom: 0.5rem; max-width: 100vw; - overflow: scroll; + overflow: auto; } .body { diff --git a/Iceshrimp.Frontend/Components/ErrorUi.razor.js b/Iceshrimp.Frontend/Components/ErrorUi.razor.js new file mode 100644 index 00000000..295c87ea --- /dev/null +++ b/Iceshrimp.Frontend/Components/ErrorUi.razor.js @@ -0,0 +1,13 @@ +export function DownloadFile(filename, contentType, content) { + const file = new File([content], filename, { type: contentType}); + const exportUrl = URL.createObjectURL(file); + + const a = document.createElement("a"); + document.body.appendChild(a); + a.href = exportUrl; + a.download = filename; + a.target = "_self"; + a.click(); + + URL.revokeObjectURL(exportUrl); +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogService.cs b/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogService.cs new file mode 100644 index 00000000..74115aec --- /dev/null +++ b/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogService.cs @@ -0,0 +1,27 @@ +namespace Iceshrimp.Frontend.Core.InMemoryLogger; + +internal class InMemoryLogService +{ + private List LogBuffer { get; } = []; + private int _bufferCapacity = 100; + + public void Add(string logline) + { + if (LogBuffer.Count > _bufferCapacity - 1) LogBuffer.RemoveAt(0); + LogBuffer.Add(logline); + } + + public void ResizeBuffer(int newCapacity) + { + if (newCapacity > _bufferCapacity) + { + LogBuffer.RemoveRange(0, newCapacity - _bufferCapacity); + } + _bufferCapacity = newCapacity; + } + + public IReadOnlyCollection GetLogs() + { + return LogBuffer.AsReadOnly(); + } +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogger.cs b/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogger.cs new file mode 100644 index 00000000..67c56323 --- /dev/null +++ b/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogger.cs @@ -0,0 +1,18 @@ +using Iceshrimp.Frontend.Core.Services; + +namespace Iceshrimp.Frontend.Core.InMemoryLogger; + +internal class InMemoryLogger (Func getCurrentConfig, InMemoryLogService logService) : ILogger +{ + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + logService.Add(formatter(state, exception)); + } + + public bool IsEnabled(LogLevel logLevel) + { + return getCurrentConfig().LogLevel.HasFlag(logLevel); + } + + public IDisposable? BeginScope(TState state) where TState : notnull => default!; +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLoggerConfiguration.cs b/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLoggerConfiguration.cs new file mode 100644 index 00000000..a0f20ff2 --- /dev/null +++ b/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLoggerConfiguration.cs @@ -0,0 +1,7 @@ +namespace Iceshrimp.Frontend.Core.InMemoryLogger; + +internal class InMemoryLoggerConfiguration +{ + public int BufferSize { get; set; } = 100; + public LogLevel LogLevel { get; set; } = LogLevel.Information; +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLoggerExtension.cs b/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLoggerExtension.cs new file mode 100644 index 00000000..e9e2a419 --- /dev/null +++ b/Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLoggerExtension.cs @@ -0,0 +1,30 @@ +namespace Iceshrimp.Frontend.Core.InMemoryLogger; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Configuration; + +internal static class InMemoryLoggerExtension +{ + public static void AddInMemoryLogger( + this ILoggingBuilder builder) + { + builder.AddConfiguration(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); + LoggerProviderOptions.RegisterProviderOptions(builder.Services); + } + + public static void AddInMemoryLogger( + this ILoggingBuilder builder, + Action configure + ) + { + builder.Services.TryAddSingleton(); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); + builder.Services.Configure(configure); + } +} + + \ No newline at end of file diff --git a/Iceshrimp.Frontend/Iceshrimp.Frontend.csproj b/Iceshrimp.Frontend/Iceshrimp.Frontend.csproj index 8b83f6df..e626ddb2 100644 --- a/Iceshrimp.Frontend/Iceshrimp.Frontend.csproj +++ b/Iceshrimp.Frontend/Iceshrimp.Frontend.csproj @@ -30,6 +30,7 @@ + diff --git a/Iceshrimp.Frontend/Pages/SingleNote.razor b/Iceshrimp.Frontend/Pages/SingleNote.razor index 32912c55..50186b40 100644 --- a/Iceshrimp.Frontend/Pages/SingleNote.razor +++ b/Iceshrimp.Frontend/Pages/SingleNote.razor @@ -12,7 +12,8 @@ @inject MessageService MessageService @inject StateService State @inject NavigationManager Navigation -@inject IStringLocalizer Loc; +@inject IStringLocalizer Loc +@inject ILogger Logger; @implements IDisposable @@ -82,6 +83,7 @@ protected override async Task OnParametersSetAsync() { + Logger.LogTrace($"Opening NoteID: {NoteId}"); _componentState = LoadState.Loading; if (NoteId == null) { @@ -98,8 +100,9 @@ Descendants = await descendantsTask; Ascendants = await ascendantsTask; } - catch (ApiException) + catch (ApiException e) { + Logger.LogWarning($"Failed to load ID '{NoteId}' due to API Exception: {e.Message}"); _componentState = LoadState.Error; return; } diff --git a/Iceshrimp.Frontend/Startup.cs b/Iceshrimp.Frontend/Startup.cs index d6e11ff8..619008bd 100644 --- a/Iceshrimp.Frontend/Startup.cs +++ b/Iceshrimp.Frontend/Startup.cs @@ -1,6 +1,7 @@ using System.Globalization; using Blazored.LocalStorage; using Iceshrimp.Frontend; +using Iceshrimp.Frontend.Core.InMemoryLogger; using Iceshrimp.Frontend.Core.Miscellaneous; using Iceshrimp.Frontend.Core.Services; using Ljbc1994.Blazor.IntersectionObserver; @@ -14,6 +15,7 @@ builder.RootComponents.Add("head::after"); builder.Services.AddSingleton(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddLocalization(); +builder.Logging.AddInMemoryLogger(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddIntersectionObserver();