From e62074cc268f7383aba850961b20c7c021010f6b Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Fri, 12 Feb 2021 16:18:44 -0600 Subject: [PATCH] add basic logging ui (#14) --- .../Logs/LogEntryViewModel.cs | 12 ++++++++ ErsatzTV.Application/Logs/Mapper.cs | 16 ++++++++++ .../Logs/Queries/GetRecentLogEntries.cs | 7 +++++ .../Queries/GetRecentLogEntriesHandler.cs | 21 ++++++++++++++ ErsatzTV.Core/Domain/LogEntry.cs | 12 ++++++++ ErsatzTV.Core/FileSystemLayout.cs | 2 ++ .../Interfaces/Repositories/ILogRepository.cs | 11 +++++++ .../Configurations/LogEntryConfiguration.cs | 12 ++++++++ ErsatzTV.Infrastructure/Data/LogContext.cs | 21 ++++++++++++++ .../Data/Repositories/LogRepository.cs | 19 ++++++++++++ ErsatzTV/ErsatzTV.csproj | 1 + ErsatzTV/Pages/Logs.razor | 29 +++++++++++++++++++ ErsatzTV/Program.cs | 2 ++ ErsatzTV/Shared/MainLayout.razor | 1 + ErsatzTV/Startup.cs | 4 +++ 15 files changed, 170 insertions(+) create mode 100644 ErsatzTV.Application/Logs/LogEntryViewModel.cs create mode 100644 ErsatzTV.Application/Logs/Mapper.cs create mode 100644 ErsatzTV.Application/Logs/Queries/GetRecentLogEntries.cs create mode 100644 ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs create mode 100644 ErsatzTV.Core/Domain/LogEntry.cs create mode 100644 ErsatzTV.Core/Interfaces/Repositories/ILogRepository.cs create mode 100644 ErsatzTV.Infrastructure/Data/Configurations/LogEntryConfiguration.cs create mode 100644 ErsatzTV.Infrastructure/Data/LogContext.cs create mode 100644 ErsatzTV.Infrastructure/Data/Repositories/LogRepository.cs create mode 100644 ErsatzTV/Pages/Logs.razor diff --git a/ErsatzTV.Application/Logs/LogEntryViewModel.cs b/ErsatzTV.Application/Logs/LogEntryViewModel.cs new file mode 100644 index 000000000..0f81c11ed --- /dev/null +++ b/ErsatzTV.Application/Logs/LogEntryViewModel.cs @@ -0,0 +1,12 @@ +using System; + +namespace ErsatzTV.Application.Logs +{ + public record LogEntryViewModel( + int Id, + DateTime Timestamp, + string Level, + string Exception, + string RenderedMessage, + string Properties); +} diff --git a/ErsatzTV.Application/Logs/Mapper.cs b/ErsatzTV.Application/Logs/Mapper.cs new file mode 100644 index 000000000..7d9055b81 --- /dev/null +++ b/ErsatzTV.Application/Logs/Mapper.cs @@ -0,0 +1,16 @@ +using ErsatzTV.Core.Domain; + +namespace ErsatzTV.Application.Logs +{ + internal static class Mapper + { + internal static LogEntryViewModel ProjectToViewModel(LogEntry logEntry) => + new( + logEntry.Id, + logEntry.Timestamp, + logEntry.Level, + logEntry.Exception, + logEntry.RenderedMessage, + logEntry.Properties); + } +} diff --git a/ErsatzTV.Application/Logs/Queries/GetRecentLogEntries.cs b/ErsatzTV.Application/Logs/Queries/GetRecentLogEntries.cs new file mode 100644 index 000000000..0b92744d7 --- /dev/null +++ b/ErsatzTV.Application/Logs/Queries/GetRecentLogEntries.cs @@ -0,0 +1,7 @@ +using System.Collections.Generic; +using MediatR; + +namespace ErsatzTV.Application.Logs.Queries +{ + public record GetRecentLogEntries : IRequest>; +} diff --git a/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs b/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs new file mode 100644 index 000000000..6997a3de3 --- /dev/null +++ b/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; +using MediatR; +using static ErsatzTV.Application.Logs.Mapper; + +namespace ErsatzTV.Application.Logs.Queries +{ + public class GetRecentLogEntriesHandler : IRequestHandler> + { + private readonly ILogRepository _logRepository; + + public GetRecentLogEntriesHandler(ILogRepository logRepository) => _logRepository = logRepository; + + public Task> Handle(GetRecentLogEntries request, CancellationToken cancellationToken) => + _logRepository.GetRecentLogEntries().Map(list => list.Map(ProjectToViewModel).ToList()); + } +} diff --git a/ErsatzTV.Core/Domain/LogEntry.cs b/ErsatzTV.Core/Domain/LogEntry.cs new file mode 100644 index 000000000..6685e8341 --- /dev/null +++ b/ErsatzTV.Core/Domain/LogEntry.cs @@ -0,0 +1,12 @@ +using System; + +namespace ErsatzTV.Core.Domain +{ + public record LogEntry( + int Id, + DateTime Timestamp, + string Level, + string Exception, + string RenderedMessage, + string Properties); +} diff --git a/ErsatzTV.Core/FileSystemLayout.cs b/ErsatzTV.Core/FileSystemLayout.cs index 5e2f17cf9..258c619c9 100644 --- a/ErsatzTV.Core/FileSystemLayout.cs +++ b/ErsatzTV.Core/FileSystemLayout.cs @@ -13,6 +13,8 @@ namespace ErsatzTV.Core public static readonly string DatabasePath = Path.Combine(AppDataFolder, "ersatztv.sqlite3"); + public static readonly string LogDatabasePath = Path.Combine(AppDataFolder, "logs.sqlite3"); + public static readonly string ImageCacheFolder = Path.Combine(AppDataFolder, "cache", "images"); public static readonly string PlexSecretsPath = Path.Combine(AppDataFolder, "plex-secrets.json"); diff --git a/ErsatzTV.Core/Interfaces/Repositories/ILogRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/ILogRepository.cs new file mode 100644 index 000000000..096d24be6 --- /dev/null +++ b/ErsatzTV.Core/Interfaces/Repositories/ILogRepository.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ErsatzTV.Core.Domain; + +namespace ErsatzTV.Core.Interfaces.Repositories +{ + public interface ILogRepository + { + public Task> GetRecentLogEntries(); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/LogEntryConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/LogEntryConfiguration.cs new file mode 100644 index 000000000..e79223e0d --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/LogEntryConfiguration.cs @@ -0,0 +1,12 @@ +using ErsatzTV.Core.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ErsatzTV.Infrastructure.Data.Configurations +{ + public class LogEntryConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) => + builder.ToTable("Logs"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/LogContext.cs b/ErsatzTV.Infrastructure/Data/LogContext.cs new file mode 100644 index 000000000..875bf84bd --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/LogContext.cs @@ -0,0 +1,21 @@ +using ErsatzTV.Core.Domain; +using Microsoft.EntityFrameworkCore; + +namespace ErsatzTV.Infrastructure.Data +{ + public class LogContext : DbContext + { + public LogContext(DbContextOptions options) + : base(options) + { + } + + public DbSet LogEntries { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(typeof(LogContext).Assembly); + } + } +} diff --git a/ErsatzTV.Infrastructure/Data/Repositories/LogRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/LogRepository.cs new file mode 100644 index 000000000..7d4864ce1 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Repositories/LogRepository.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Repositories; +using Microsoft.EntityFrameworkCore; + +namespace ErsatzTV.Infrastructure.Data.Repositories +{ + public class LogRepository : ILogRepository + { + private readonly LogContext _logContext; + + public LogRepository(LogContext logContext) => _logContext = logContext; + + public Task> GetRecentLogEntries() => + _logContext.LogEntries.OrderByDescending(e => e.Id).Take(100).ToListAsync(); + } +} diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj index 890d128ad..1b97f7cad 100644 --- a/ErsatzTV/ErsatzTV.csproj +++ b/ErsatzTV/ErsatzTV.csproj @@ -25,6 +25,7 @@ + diff --git a/ErsatzTV/Pages/Logs.razor b/ErsatzTV/Pages/Logs.razor new file mode 100644 index 000000000..23e43dce1 --- /dev/null +++ b/ErsatzTV/Pages/Logs.razor @@ -0,0 +1,29 @@ +@page "/system/logs" +@using ErsatzTV.Application.Logs +@using ErsatzTV.Application.Logs.Queries +@inject IMediator Mediator + + + + Timestamp + Level + Message + Properties + + + @context.Timestamp + @context.Level + @context.RenderedMessage + @context.Properties + + + + + + +@code { + private List _logEntries; + + protected override async Task OnInitializedAsync() => _logEntries = await Mediator.Send(new GetRecentLogEntries()); + +} \ No newline at end of file diff --git a/ErsatzTV/Program.cs b/ErsatzTV/Program.cs index 517746095..b7c7d5fe8 100644 --- a/ErsatzTV/Program.cs +++ b/ErsatzTV/Program.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Threading.Tasks; +using ErsatzTV.Core; using ErsatzTV.Extensions; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -25,6 +26,7 @@ namespace ErsatzTV Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(Configuration) .Enrich.FromLogContext() + .WriteTo.SQLite(FileSystemLayout.LogDatabasePath, retentionPeriod: TimeSpan.FromDays(1)) .CreateLogger(); try diff --git a/ErsatzTV/Shared/MainLayout.razor b/ErsatzTV/Shared/MainLayout.razor index e348ed1ab..6182a5555 100644 --- a/ErsatzTV/Shared/MainLayout.razor +++ b/ErsatzTV/Shared/MainLayout.razor @@ -32,6 +32,7 @@ Schedules Playouts + Logs diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index 7ae4b9cee..e4403bdbd 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -108,6 +108,9 @@ namespace ErsatzTV o.MigrationsAssembly("ErsatzTV.Infrastructure"); })); + services.AddDbContext( + options => options.UseSqlite($"Data Source={FileSystemLayout.LogDatabasePath}")); + services.AddMediatR(typeof(GetAllChannels).Assembly); services.AddRefitClient() @@ -156,6 +159,7 @@ namespace ErsatzTV services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped();