Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor console logs page to use parameters instead of ref #5923

Merged
merged 3 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Aspire.Dashboard/Aspire.Dashboard.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
Given we want to be able to support people running on environments where they only have the 9.0 SDK/runtime installed,
we allow roll-forward to the next major in order to support these customers.-->
<RollForward>Major</RollForward>

<DefineConstants>$(DefineConstants);ASPIRE_DASHBOARD</DefineConstants>
</PropertyGroup>

<PropertyGroup>
Expand Down
49 changes: 26 additions & 23 deletions src/Aspire.Dashboard/Components/Controls/LogViewer.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,33 @@

<div class="log-overflow console-overflow continuous-scroll-overflow">
<div class="log-container console-container" id="logContainer">
<Virtualize Items="@LogEntries.GetEntries()" ItemSize="20" OverscanCount="100" TItem="LogEntry">
<div class="log-line-row-container">
<div class="log-line-row console-line-row">
<span class="log-line-area" role="log">
<span class="log-line-number">@context.LineNumber</span>
<span class="log-content">
@{
var hasPrefix = false;
}
@if (context.Timestamp is { } timestamp)
{
hasPrefix = true;
<span class="timestamp" title="@FormatHelpers.FormatDateTime(TimeProvider, timestamp, MillisecondsDisplay.Full, CultureInfo.CurrentCulture)">@GetDisplayTimestamp(timestamp)</span>
}
@if (context.Type == LogEntryType.Error)
{
hasPrefix = true;
<fluent-badge appearance="accent">stderr</fluent-badge>
}
@((MarkupString)((hasPrefix ? "&#32;" : string.Empty) + (context.Content ?? string.Empty)))
@if (LogEntries is {} logEntries)
{
<Virtualize Items="logEntries.GetEntries()" ItemSize="20" OverscanCount="100" TItem="LogEntry">
<div class="log-line-row-container">
<div class="log-line-row console-line-row">
<span class="log-line-area" role="log">
<span class="log-line-number">@context.LineNumber</span>
<span class="log-content">
@{
var hasPrefix = false;
}
@if (context.Timestamp is { } timestamp)
{
hasPrefix = true;
<span class="timestamp" title="@FormatHelpers.FormatDateTime(TimeProvider, timestamp, MillisecondsDisplay.Full, CultureInfo.CurrentCulture)">@GetDisplayTimestamp(timestamp)</span>
}
@if (context.Type == LogEntryType.Error)
{
hasPrefix = true;
<fluent-badge appearance="accent">stderr</fluent-badge>
}
@((MarkupString)((hasPrefix ? "&#32;" : string.Empty) + (context.Content ?? string.Empty)))
</span>
</span>
</span>
</div>
</div>
</div>
</Virtualize>
</Virtualize>
}
</div>
</div>
49 changes: 19 additions & 30 deletions src/Aspire.Dashboard/Components/Controls/LogViewer.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using Aspire.Dashboard.Configuration;
using Aspire.Dashboard.Extensions;
using Aspire.Dashboard.Model;
using Aspire.Hosting.ConsoleLogs;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;

namespace Aspire.Dashboard.Components;
Expand All @@ -18,7 +16,8 @@ namespace Aspire.Dashboard.Components;
public sealed partial class LogViewer
{
private readonly bool _convertTimestampsFromUtc = true;
private bool _logsCleared;
private LogEntries? _logEntries;
private bool _logsChanged;

[Inject]
public required BrowserTimeProvider TimeProvider { get; init; }
Expand All @@ -29,27 +28,33 @@ public sealed partial class LogViewer
[Inject]
public required ILogger<LogViewer> Logger { get; init; }

[Inject]
public required IOptions<DashboardOptions> Options { get; init; }
[Parameter]
public LogEntries? LogEntries { get; set; } = null!;

internal LogEntries LogEntries { get; set; } = null!;
protected override void OnParametersSet()
{
if (_logEntries != LogEntries)
{
Logger.LogDebug("Log entries changed.");

public string? ResourceName { get; set; }
_logsChanged = true;
_logEntries = LogEntries;
}

protected override void OnInitialized()
{
LogEntries = new(Options.Value.Frontend.MaxConsoleLogCount);
base.OnParametersSet();
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (_logsCleared)
if (_logsChanged)
{
await JS.InvokeVoidAsync("resetContinuousScrollPosition");
_logsCleared = false;
_logsChanged = false;
}
if (firstRender)
{
Logger.LogDebug("Initializing log viewer.");

await JS.InvokeVoidAsync("initializeContinuousScroll");
DimensionManager.OnViewportInformationChanged += OnBrowserResize;
}
Expand All @@ -71,27 +76,11 @@ private string GetDisplayTimestamp(DateTimeOffset timestamp)
return date.ToString(KnownFormats.ConsoleLogsUITimestampFormat, CultureInfo.InvariantCulture);
}

internal void ClearLogs()
{
Logger.LogDebug("Clearing logs for {ResourceName}.", ResourceName);

_logsCleared = true;
LogEntries.Clear();
ResourceName = null;
StateHasChanged();
}

public ValueTask DisposeAsync()
{
Logger.LogDebug("Disposing log viewer.");

DimensionManager.OnViewportInformationChanged -= OnBrowserResize;
return ValueTask.CompletedTask;
}

// Calling StateHasChanged on the page isn't updating the LogViewer.
// This exposes way to tell the log view it has updated and to re-render.
internal async Task LogsAddedAsync()
{
Logger.LogDebug("Logs added.");
await InvokeAsync(StateHasChanged);
}
}
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
</MobilePageTitleToolbarSection>

<MainSection>
<LogViewer @ref="LogViewer"/>
<LogViewer LogEntries="@_logEntries" />
</MainSection>
</AspirePageContentLayout>
</div>
42 changes: 14 additions & 28 deletions src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
using System.Collections.Immutable;
using System.Diagnostics;
using Aspire.Dashboard.Components.Layout;
using Aspire.Dashboard.Configuration;
using Aspire.Dashboard.ConsoleLogs;
using Aspire.Dashboard.Extensions;
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Model.Otlp;
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Resources;
using Aspire.Dashboard.Utils;
using Aspire.Hosting.ConsoleLogs;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;

namespace Aspire.Dashboard.Components.Pages;

Expand All @@ -30,6 +33,9 @@ private sealed class ConsoleLogsSubscription
public void Cancel() => _cts.Cancel();
}

[Inject]
public required IOptions<DashboardOptions> Options { get; init; }

[Inject]
public required IDashboardClient DashboardClient { get; init; }

Expand Down Expand Up @@ -57,15 +63,14 @@ private sealed class ConsoleLogsSubscription
[Parameter]
public string? ResourceName { get; set; }

private readonly TaskCompletionSource _logViewerReadyTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly CancellationTokenSource _resourceSubscriptionCancellation = new();
private readonly ConcurrentDictionary<string, ResourceViewModel> _resourceByName = new(StringComparers.ResourceName);
private ImmutableList<SelectViewModel<ResourceTypeDetails>>? _resources;
private Task? _resourceSubscriptionTask;
private ConsoleLogsSubscription? _consoleLogsSubscription;
internal LogEntries _logEntries = null!;

// UI
public LogViewer LogViewer = null!;
private SelectViewModel<ResourceTypeDetails> _noSelection = null!;
private AspirePageContentLayout? _contentLayout;

Expand All @@ -77,6 +82,7 @@ private sealed class ConsoleLogsSubscription

protected override async Task OnInitializedAsync()
{
_logEntries = new(Options.Value.Frontend.MaxConsoleLogCount);
_noSelection = new() { Id = null, Name = ControlsStringsLoc[nameof(ControlsStrings.None)] };
PageViewModel = new ConsoleLogsViewModel { SelectedOption = _noSelection, SelectedResource = null, Status = Loc[nameof(Dashboard.Resources.ConsoleLogs.ConsoleLogsLoadingResources)] };

Expand Down Expand Up @@ -206,14 +212,8 @@ protected override async Task OnParametersSetAsync()
_consoleLogsSubscription = newConsoleLogsSubscription;
}

// Wait for the first render to complete so that the log viewer is available.
if (!_logViewerReadyTcs.Task.IsCompletedSuccessfully)
{
Logger.LogDebug("Waiting for log viewer to be available.");
await _logViewerReadyTcs.Task;
}

LogViewer.ClearLogs();
Logger.LogDebug("Creating new log entries collection.");
_logEntries = new(Options.Value.Frontend.MaxConsoleLogCount);

if (newConsoleLogsSubscription is not null)
{
Expand All @@ -222,15 +222,6 @@ protected override async Task OnParametersSetAsync()
}
}

protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Logger.LogDebug("Log viewer ready.");
_logViewerReadyTcs.SetResult();
}
}

internal static ImmutableList<SelectViewModel<ResourceTypeDetails>> GetConsoleLogResourceSelectViewModels(
ConcurrentDictionary<string, ResourceViewModel> resourcesByName,
SelectViewModel<ResourceTypeDetails> noSelectionViewModel,
Expand Down Expand Up @@ -329,16 +320,16 @@ private void LoadLogs(ConsoleLogsSubscription newConsoleLogsSubscription)
foreach (var (lineNumber, content, isErrorOutput) in batch)
{
// Set the base line number using the reported line number of the first log line.
if (LogViewer.LogEntries.EntriesCount == 0)
if (_logEntries.EntriesCount == 0)
{
LogViewer.LogEntries.BaseLineNumber = lineNumber;
_logEntries.BaseLineNumber = lineNumber;
}

var logEntry = logParser.CreateLogEntry(content, isErrorOutput);
LogViewer.LogEntries.InsertSorted(logEntry);
_logEntries.InsertSorted(logEntry);
}

await LogViewer.LogsAddedAsync();
await InvokeAsync(StateHasChanged);
}
}
finally
Expand Down Expand Up @@ -412,11 +403,6 @@ public async ValueTask DisposeAsync()
await TaskHelpers.WaitIgnoreCancelAsync(_resourceSubscriptionTask);

await StopAndClearConsoleLogsSubscriptionAsync();

if (LogViewer is { } logViewer)
{
await logViewer.DisposeAsync();
}
}

public class ConsoleLogsViewModel
Expand Down
7 changes: 7 additions & 0 deletions src/Shared/ConsoleLogs/LogEntries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@

namespace Aspire.Hosting.ConsoleLogs;

// Type is shared by dashboard and hosting.
// It needs to be public in dashboard so it can be bound to a parameter.
// It needs to be internal in hosting because we don't want to expose it as public API.
[DebuggerDisplay("Count = {EntriesCount}")]
#if ASPIRE_DASHBOARD
public sealed class LogEntries(int maximumEntryCount)
#else
internal sealed class LogEntries(int maximumEntryCount)
#endif
{
private readonly CircularBuffer<LogEntry> _logEntries = new(maximumEntryCount);

Expand Down
8 changes: 8 additions & 0 deletions src/Shared/ConsoleLogs/LogEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@
namespace Aspire.Hosting.ConsoleLogs;

[DebuggerDisplay("LineNumber = {LineNumber}, Timestamp = {Timestamp}, Content = {Content}, Type = {Type}")]
#if ASPIRE_DASHBOARD
public sealed class LogEntry
#else
internal sealed class LogEntry
#endif
{
public string? Content { get; set; }
public DateTime? Timestamp { get; set; }
public LogEntryType Type { get; init; } = LogEntryType.Default;
public int LineNumber { get; set; }
}

#if ASPIRE_DASHBOARD
public enum LogEntryType
#else
internal enum LogEntryType
#endif
{
Default,
Error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public async Task ResourceName_ViaUrlAndResourceLoaded_LogViewerUpdated()

logger.LogInformation("Log results are added to log viewer.");
consoleLogsChannel.Writer.TryWrite([new ResourceLogLine(1, "Hello world", IsErrorMessage: false)]);
cut.WaitForState(() => instance.LogViewer.LogEntries.EntriesCount > 0);
cut.WaitForState(() => instance._logEntries.EntriesCount > 0);
}

private void SetupConsoleLogsServices(TestDashboardClient? dashboardClient = null)
Expand Down
Loading