Skip to content

Commit 77dca87

Browse files
authored
Merge pull request #55 from Mythetech/feat/dev-event-viewer
feat: dev event viewer
2 parents f152e05 + c2cccbf commit 77dca87

File tree

11 files changed

+468
-2
lines changed

11 files changed

+468
-2
lines changed

Apollo.Components/Editor/ToolsMenu.razor

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@using Apollo.Components.Infrastructure.MessageBus
2+
@using Apollo.Components.Infrastructure.Environment
23
@using Apollo.Components.NuGet.Commands
34
@using Apollo.Components.Tools.Commands
45
@using Apollo.Components.Theme
@@ -11,6 +12,12 @@
1112
<MudMenuItem Icon="@ApolloIcons.Search" OnClick="@(async () => await Bus.PublishAsync(new OpenRegexTesterDialog()))">
1213
Regex Tester
1314
</MudMenuItem>
15+
@if (Environment.IsDevelopment())
16+
{
17+
<MudMenuItem Icon="@ApolloIcons.Events" OnClick="@(async () => await Bus.PublishAsync(new OpenEventViewerDialog()))">
18+
Event Viewer
19+
</MudMenuItem>
20+
}
1421
<MudMenu StartIcon="@ApolloIcons.Guid" Label="Generate GUID">
1522
<MudMenuItem Icon="@ApolloIcons.Guid" OnClick="@(async () => await Bus.PublishAsync(new GenerateGuid()))">
1623
GUID
@@ -23,5 +30,6 @@
2330

2431
@code {
2532
[Inject] public IMessageBus Bus { get; set; } = default!;
33+
[Inject] public IRuntimeEnvironment Environment { get; set; } = default!;
2634
}
2735

Apollo.Components/Infrastructure/MessageBus/BusRegistrationExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public static void RegisterConsumerType(this IMessageBus bus, Type messageType,
5050

5151
public static IServiceCollection AddMessageBus(this IServiceCollection services)
5252
{
53+
services.AddSingleton<CapturedEventState>();
5354
services.AddSingleton<IMessageBus, InMemoryMessageBus>();
5455

5556
services.RegisterConsumers(Assembly.GetExecutingAssembly());
@@ -59,6 +60,7 @@ public static IServiceCollection AddMessageBus(this IServiceCollection services)
5960

6061
public static IServiceCollection AddMessageBus(this IServiceCollection services, params Assembly[] assemblies)
6162
{
63+
services.AddSingleton<CapturedEventState>();
6264
services.AddSingleton<IMessageBus, InMemoryMessageBus>();
6365

6466
foreach (var assembly in assemblies)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System.Text.Json;
2+
3+
namespace Apollo.Components.Infrastructure.MessageBus;
4+
5+
public sealed class CapturedEventState
6+
{
7+
private readonly object _gate = new();
8+
private readonly List<CapturedEvent> _events = new();
9+
private readonly JsonSerializerOptions _serializerOptions = new()
10+
{
11+
WriteIndented = true
12+
};
13+
14+
public event Action? Changed;
15+
16+
public int MaxEvents { get; set; } = 2000;
17+
18+
public IReadOnlyList<CapturedEvent> GetEventsSnapshot()
19+
{
20+
lock (_gate)
21+
{
22+
return _events
23+
.OrderByDescending(e => e.Timestamp)
24+
.ToList();
25+
}
26+
}
27+
28+
public IReadOnlyList<CapturedEventAggregate> GetAggregateSnapshot()
29+
{
30+
lock (_gate)
31+
{
32+
return _events
33+
.GroupBy(e => e.EventType)
34+
.Select(g => new CapturedEventAggregate(g.Key, g.Count()))
35+
.OrderByDescending(x => x.Count)
36+
.ToList();
37+
}
38+
}
39+
40+
public void Clear()
41+
{
42+
lock (_gate)
43+
{
44+
_events.Clear();
45+
}
46+
47+
Changed?.Invoke();
48+
}
49+
50+
public void Add(Type eventType, object payload)
51+
{
52+
var serialized = JsonSerializer.Serialize(payload, eventType, _serializerOptions);
53+
var captured = new CapturedEvent(
54+
Guid.NewGuid(),
55+
DateTimeOffset.UtcNow,
56+
eventType.FullName ?? eventType.Name,
57+
serialized);
58+
59+
lock (_gate)
60+
{
61+
_events.Add(captured);
62+
TrimToMax();
63+
}
64+
65+
Changed?.Invoke();
66+
}
67+
68+
private void TrimToMax()
69+
{
70+
if (MaxEvents <= 0)
71+
return;
72+
73+
var overflow = _events.Count - MaxEvents;
74+
if (overflow <= 0)
75+
return;
76+
77+
_events.RemoveRange(0, overflow);
78+
}
79+
}
80+
81+
public sealed record CapturedEvent(
82+
Guid Id,
83+
DateTimeOffset Timestamp,
84+
string EventType,
85+
string Payload);
86+
87+
public sealed record CapturedEventAggregate(
88+
string EventType,
89+
int Count);
90+
91+

Apollo.Components/Infrastructure/MessageBus/InMemoryMessageBus.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace Apollo.Components.Infrastructure.MessageBus;
22
using Microsoft.Extensions.Logging;
3+
using Apollo.Components.Infrastructure.Environment;
34

45
public class InMemoryMessageBus : IMessageBus
56
{
@@ -9,15 +10,36 @@ public class InMemoryMessageBus : IMessageBus
910

1011
private readonly IServiceProvider _serviceProvider;
1112
private readonly ILogger<InMemoryMessageBus> _logger;
13+
private readonly CapturedEventState _capturedEventState;
1214

13-
public InMemoryMessageBus(IServiceProvider serviceProvider, ILogger<InMemoryMessageBus> logger)
15+
internal bool CaptureDebugInformation { get; }
16+
17+
public InMemoryMessageBus(
18+
IServiceProvider serviceProvider,
19+
ILogger<InMemoryMessageBus> logger,
20+
CapturedEventState capturedEventState,
21+
IRuntimeEnvironment runtimeEnvironment)
1422
{
1523
_serviceProvider = serviceProvider;
1624
_logger = logger;
25+
_capturedEventState = capturedEventState;
26+
CaptureDebugInformation = runtimeEnvironment.IsDevelopment();
1727
}
1828

1929
public async Task PublishAsync<TMessage>(TMessage message) where TMessage : class
2030
{
31+
if (CaptureDebugInformation)
32+
{
33+
try
34+
{
35+
_capturedEventState.Add(typeof(TMessage), message);
36+
}
37+
catch (Exception ex)
38+
{
39+
_logger.LogError(ex, "Error capturing message {MessageType}", typeof(TMessage).Name);
40+
}
41+
}
42+
2143
var registeredConsumers = GetOrResolveConsumers<TMessage>();
2244

2345
var manualSubscribers = _subscribers.TryGetValue(typeof(TMessage), out var subscribers)

Apollo.Components/Theme/ApolloIcons.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,6 @@ public static class ApolloIcons
121121
public const string Guid = Icons.Material.TwoTone.Fingerprint;
122122

123123
public const string GuidV7 = Icons.Material.TwoTone.Schedule;
124+
125+
public const string Events = Icons.Material.TwoTone.EventNote;
124126
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace Apollo.Components.Tools.Commands;
2+
3+
public record OpenEventViewerDialog();
4+
5+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Apollo.Components.Infrastructure.MessageBus;
2+
using Apollo.Components.Tools.Commands;
3+
using MudBlazor;
4+
5+
namespace Apollo.Components.Tools.Consumers;
6+
7+
public sealed class EventViewerDialogOpener : IConsumer<OpenEventViewerDialog>
8+
{
9+
private readonly IDialogService _dialogService;
10+
11+
public EventViewerDialogOpener(IDialogService dialogService)
12+
{
13+
_dialogService = dialogService;
14+
}
15+
16+
public async Task Consume(OpenEventViewerDialog message)
17+
{
18+
var options = new DialogOptions
19+
{
20+
CloseOnEscapeKey = true,
21+
NoHeader = true,
22+
MaxWidth = MaxWidth.ExtraLarge,
23+
FullWidth = true,
24+
};
25+
26+
await _dialogService.ShowAsync<EventViewerDialog>("Event Viewer", options);
27+
}
28+
}
29+
30+

0 commit comments

Comments
 (0)