[frontend] Add InMemory Logger
This commit is contained in:
parent
87bcb23be9
commit
9527c9ca09
10 changed files with 135 additions and 7 deletions
|
@ -1,9 +1,13 @@
|
||||||
|
@using System.Text
|
||||||
|
@using Iceshrimp.Frontend.Core.InMemoryLogger
|
||||||
@using Iceshrimp.Frontend.Core.Services
|
@using Iceshrimp.Frontend.Core.Services
|
||||||
@using Iceshrimp.Frontend.Localization
|
@using Iceshrimp.Frontend.Localization
|
||||||
@using Microsoft.Extensions.Localization
|
@using Microsoft.Extensions.Localization
|
||||||
@inject IStringLocalizer<Localization> Loc;
|
@inject IStringLocalizer<Localization> Loc;
|
||||||
@inject NavigationManager Navigation;
|
@inject NavigationManager Navigation;
|
||||||
@inject VersionService Version;
|
@inject VersionService Version;
|
||||||
|
@inject InMemoryLogService LogService;
|
||||||
|
@inject IJSRuntime Js;
|
||||||
|
|
||||||
<div class="error-ui">
|
<div class="error-ui">
|
||||||
<h3>@Loc["Unhandled Exception has occured"]</h3>
|
<h3>@Loc["Unhandled Exception has occured"]</h3>
|
||||||
|
@ -14,7 +18,7 @@
|
||||||
@Loc["Providing the below stack trace and version information will aid in debugging."]
|
@Loc["Providing the below stack trace and version information will aid in debugging."]
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stacktrace">
|
<div class="log-block">
|
||||||
<code>@Exception.GetType(): @Exception.Message @Exception.StackTrace</code>
|
<code>@Exception.GetType(): @Exception.Message @Exception.StackTrace</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="version">
|
<div class="version">
|
||||||
|
@ -34,16 +38,39 @@
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@Loc["Logs"]
|
||||||
|
<div class="log-block">
|
||||||
|
@foreach (var line in _logs)
|
||||||
|
{
|
||||||
|
<code>@line</code><br/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn" @onclick="Reload">@Loc["Reload Application"]</button>
|
<button class="btn" @onclick="Reload">@Loc["Reload Application"]</button>
|
||||||
|
<button class="btn" @onclick="DownloadLogs">@Loc["Download Logs"]</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter] [EditorRequired] public required Exception Exception { get; set; }
|
[Parameter] [EditorRequired] public required Exception Exception { get; set; }
|
||||||
|
private IReadOnlyCollection<string> _logs = [];
|
||||||
|
private IJSInProcessObjectReference _module = null!;
|
||||||
|
|
||||||
private void Reload()
|
private void Reload()
|
||||||
{
|
{
|
||||||
Navigation.Refresh(true);
|
Navigation.Refresh(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_logs = LogService.GetLogs();
|
||||||
|
_module = (IJSInProcessObjectReference) await Js.InvokeAsync<IJSObjectReference>("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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
.stacktrace {
|
.log-block {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
max-height: 50vh;
|
max-height: 50vh;
|
||||||
overflow: scroll;
|
overflow: auto;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
@ -31,7 +31,7 @@ code {
|
||||||
.value {
|
.value {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
overflow: scroll;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
|
|
13
Iceshrimp.Frontend/Components/ErrorUi.razor.js
Normal file
13
Iceshrimp.Frontend/Components/ErrorUi.razor.js
Normal file
|
@ -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);
|
||||||
|
}
|
27
Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogService.cs
Normal file
27
Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogService.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
namespace Iceshrimp.Frontend.Core.InMemoryLogger;
|
||||||
|
|
||||||
|
internal class InMemoryLogService
|
||||||
|
{
|
||||||
|
private List<string> 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<string> GetLogs()
|
||||||
|
{
|
||||||
|
return LogBuffer.AsReadOnly();
|
||||||
|
}
|
||||||
|
}
|
18
Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogger.cs
Normal file
18
Iceshrimp.Frontend/Core/InMemoryLogger/InMemoryLogger.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using Iceshrimp.Frontend.Core.Services;
|
||||||
|
|
||||||
|
namespace Iceshrimp.Frontend.Core.InMemoryLogger;
|
||||||
|
|
||||||
|
internal class InMemoryLogger (Func<InMemoryLoggerConfiguration> getCurrentConfig, InMemoryLogService logService) : ILogger
|
||||||
|
{
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||||
|
{
|
||||||
|
logService.Add(formatter(state, exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled(LogLevel logLevel)
|
||||||
|
{
|
||||||
|
return getCurrentConfig().LogLevel.HasFlag(logLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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<InMemoryLogService>();
|
||||||
|
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, InMemoryLoggerProvider>());
|
||||||
|
LoggerProviderOptions.RegisterProviderOptions<InMemoryLoggerConfiguration, InMemoryLoggerProvider>(builder.Services);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddInMemoryLogger(
|
||||||
|
this ILoggingBuilder builder,
|
||||||
|
Action<InMemoryLoggerConfiguration> configure
|
||||||
|
)
|
||||||
|
{
|
||||||
|
builder.Services.TryAddSingleton<InMemoryLogService>();
|
||||||
|
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, InMemoryLoggerProvider>());
|
||||||
|
builder.Services.Configure(configure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.8" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="8.0.0" />
|
||||||
<PackageReference Include="TypedSignalR.Client" Version="3.5.2" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
<PackageReference Include="TypedSignalR.Client" Version="3.5.2" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
<PackageReference Include="Iceshrimp.Assets.PhosphorIcons" Version="2.0.3" />
|
<PackageReference Include="Iceshrimp.Assets.PhosphorIcons" Version="2.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
@inject MessageService MessageService
|
@inject MessageService MessageService
|
||||||
@inject StateService State
|
@inject StateService State
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject IStringLocalizer<Localization> Loc;
|
@inject IStringLocalizer<Localization> Loc
|
||||||
|
@inject ILogger<SingleNote> Logger;
|
||||||
|
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
|
||||||
|
@ -82,6 +83,7 @@
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
|
Logger.LogTrace($"Opening NoteID: {NoteId}");
|
||||||
_componentState = LoadState.Loading;
|
_componentState = LoadState.Loading;
|
||||||
if (NoteId == null)
|
if (NoteId == null)
|
||||||
{
|
{
|
||||||
|
@ -98,8 +100,9 @@
|
||||||
Descendants = await descendantsTask;
|
Descendants = await descendantsTask;
|
||||||
Ascendants = await ascendantsTask;
|
Ascendants = await ascendantsTask;
|
||||||
}
|
}
|
||||||
catch (ApiException)
|
catch (ApiException e)
|
||||||
{
|
{
|
||||||
|
Logger.LogWarning($"Failed to load ID '{NoteId}' due to API Exception: {e.Message}");
|
||||||
_componentState = LoadState.Error;
|
_componentState = LoadState.Error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Blazored.LocalStorage;
|
using Blazored.LocalStorage;
|
||||||
using Iceshrimp.Frontend;
|
using Iceshrimp.Frontend;
|
||||||
|
using Iceshrimp.Frontend.Core.InMemoryLogger;
|
||||||
using Iceshrimp.Frontend.Core.Miscellaneous;
|
using Iceshrimp.Frontend.Core.Miscellaneous;
|
||||||
using Iceshrimp.Frontend.Core.Services;
|
using Iceshrimp.Frontend.Core.Services;
|
||||||
using Ljbc1994.Blazor.IntersectionObserver;
|
using Ljbc1994.Blazor.IntersectionObserver;
|
||||||
|
@ -14,6 +15,7 @@ builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||||
|
|
||||||
builder.Services.AddSingleton(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
builder.Services.AddSingleton(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||||
builder.Services.AddLocalization();
|
builder.Services.AddLocalization();
|
||||||
|
builder.Logging.AddInMemoryLogger();
|
||||||
builder.Services.AddSingleton<ApiClient>();
|
builder.Services.AddSingleton<ApiClient>();
|
||||||
builder.Services.AddSingleton<ApiService>();
|
builder.Services.AddSingleton<ApiService>();
|
||||||
builder.Services.AddIntersectionObserver();
|
builder.Services.AddIntersectionObserver();
|
||||||
|
|
Loading…
Add table
Reference in a new issue