Compare commits

...
1 Commits
Author SHA1 Message Date
amacocianandGitHub 0203eef006 Rework logging (#10)
* Rework log storing into a litedb.
LogView implementation to view logs.
Create crash.log on crash.

* Rename Logs namespace to Logging.
2021-05-18 19:49:51 +02:00
17 changed files with 393 additions and 56 deletions
+1
View File
@@ -13,6 +13,7 @@
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,60 @@
using Daybreak.Services.Logging;
using FluentAssertions;
using LiteDB;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
namespace Daybreak.Tests.Services
{
[TestClass]
public class JsonLoggerProviderTests
{
private ILogsManager logsManager;
private ILoggerProvider loggerProvider;
private ILiteDatabase liteDatabase;
[TestInitialize]
public void InitializeProvider()
{
File.Delete("Daybreak.db");
this.liteDatabase = new LiteDatabase("Daybreak.db");
this.logsManager = new JsonLogsManager(this.liteDatabase);
this.loggerProvider = new JsonLoggerProvider(this.logsManager);
}
[TestCleanup]
public void TestCleanup()
{
this.liteDatabase.Dispose();
}
[TestMethod]
public void CreateLoggerReturnsLogger()
{
var logger = this.loggerProvider.CreateLogger("SomeCategory");
logger.Should().NotBeNull();
}
[TestMethod]
public void LoggerLogsAndReaderReads()
{
var logger = this.loggerProvider.CreateLogger("SomeCategory");
logger.LogInformation("Logging some stuff");
logger.LogError("Logging some error");
this.logsManager.GetLogs().Should().HaveCount(2);
}
[TestMethod]
public void DeletingLogsShouldDeleteLogs()
{
var logger = this.loggerProvider.CreateLogger("SomeCategory");
logger.LogInformation("Logging some stuff");
logger.LogError("Logging some error");
this.logsManager.GetLogs().Should().HaveCount(2);
this.logsManager.DeleteLogs();
this.logsManager.GetLogs().Should().HaveCount(0);
}
}
}
@@ -5,6 +5,7 @@ using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Configuration;
using Daybreak.Services.Credentials;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.Logging;
using Daybreak.Services.Mutex;
using Daybreak.Services.Privilege;
using Daybreak.Services.Runtime;
@@ -14,13 +15,40 @@ using Daybreak.Services.Shortcuts;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
using Microsoft.Extensions.Http.Logging;
using Microsoft.Extensions.Logging;
using Slim;
using System.Extensions;
using System.Net.Http;
using System.Windows.Extensions.Http;
using System.Windows.Extensions;
using LiteDB;
namespace Daybreak.Configuration
{
public static class ProjectConfiguration
{
public static void RegisterResolvers(IServiceManager serviceManager)
{
serviceManager.ThrowIfNull(nameof(serviceManager));
serviceManager.RegisterLoggerFactory((sp) =>
{
var factory = new LoggerFactory();
factory.AddProvider(new JsonLoggerProvider(sp.GetService<ILogsManager>()));
return factory;
});
serviceManager.RegisterResolver(
new HttpClientResolver()
.WithHttpMessageHandlerFactory((serviceProvider, categoryType) =>
{
var loggerType = typeof(ILogger<>).MakeGenericType(categoryType);
var logger = serviceProvider.GetService(loggerType).As<ILogger>();
var handler = new LoggingHttpMessageHandler(logger) { InnerHandler = new HttpClientHandler() };
return handler;
}));
}
public static void RegisterServices(IServiceProducer serviceProducer)
{
serviceProducer.ThrowIfNull(nameof(serviceProducer));
@@ -40,6 +68,8 @@ namespace Daybreak.Configuration
serviceProducer.RegisterSingleton<IPrivilegeManager, PrivilegeManager>();
serviceProducer.RegisterSingleton<IScreenManager, ScreenManager>();
serviceProducer.RegisterSingleton<IShortcutManager, ShortcutManager>();
serviceProducer.RegisterSingleton<ILogsManager, JsonLogsManager>();
serviceProducer.RegisterSingleton<ILiteDatabase, LiteDatabase>(sp => new LiteDatabase("Daybreak.db"));
}
public static void RegisterLifetimeServices(IApplicationLifetimeProducer applicationLifetimeProducer)
{
@@ -66,6 +96,7 @@ namespace Daybreak.Configuration
viewProducer.RegisterView<RequestElevationView>();
viewProducer.RegisterView<ScreenChoiceView>();
viewProducer.RegisterView<VersionManagementView>();
viewProducer.RegisterView<LogsView>();
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<UserControl x:Class="Daybreak.Controls.LogsGlyph"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m7,3l0,-1.00209c0,-1.09965 0.89762,-1.99791 2.00488,-1.99791l0.99024,0c1.11098,0 2.00488,0.8945 2.00488,1.99791l0,1.00209l2.00442,0c0.55074,0 0.99558,0.44725 0.99558,0.99896l0,1.00208c0,0.5563 -0.44574,0.99896 -0.99558,0.99896l-9.00884,0c-0.55074,0 -0.99558,-0.44725 -0.99558,-0.99896l0,-1.00208c0,-0.5563 0.44574,-0.99896 0.99558,-0.99896l2.00442,0l0,0zm8.99999,1l1.00259,0c1.10649,0 1.99742,0.89704 1.99742,2.00359l0,20.99282c0,1.11383 -0.89428,2.00359 -1.99742,2.00359l-15.00516,0c-1.10649,0 -1.99742,-0.89704 -1.99742,-2.00359l0,-20.99282c0,-1.11383 0.89428,-2.00359 1.99742,-2.00359l1.00259,0c-0.00001,0.00163 -0.00001,0.00325 -0.00001,0.00488l0,0.99024c0,1.10726 0.89354,2.00488 2.00276,2.00488l8.99448,0c1.10609,0 2.00276,-0.8939 2.00276,-2.00488l0,-0.99024c0,-0.00163 0,-0.00325 -0.00001,-0.00488l0,0l0,0zm-6.49999,-1c0.27614,0 0.5,-0.22386 0.5,-0.5c0,-0.27614 -0.22386,-0.5 -0.5,-0.5c-0.27614,0 -0.5,0.22386 -0.5,0.5c0,0.27614 0.22386,0.5 0.5,0.5l0,0zm-6.5,8l0,1l13,0l0,-1l-13,0l0,0zm0,3l0,1l13,0l0,-1l-13,0l0,0zm0,3l0,1l13,0l0,-1l-13,0l0,0zm0,3l0,1l13,0l0,-1l-13,0l0,0zm0,3l0,1l13,0l0,-1l-13,0l0,0z"></Path>
</Viewbox>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for LogsGlyph.xaml
/// </summary>
public partial class LogsGlyph : UserControl
{
public LogsGlyph()
{
InitializeComponent();
}
}
}
+4 -2
View File
@@ -11,11 +11,13 @@
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<Version>0.8.9.2</Version>
<Version>0.8.9.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.11.32" />
<PackageReference Include="LiteDB" Version="5.0.10" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.818.41" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
@@ -25,7 +27,7 @@
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.2.0" />
<PackageReference Include="WCL" Version="1.0.2" />
<PackageReference Include="WpfExtended" Version="0.3.2" />
<PackageReference Include="WpfExtended" Version="0.3.3" />
<PackageReference Include="WpfExtended.SourceGeneration" Version="0.1.1" />
<PackageReference Include="WpfScreenHelper" Version="1.0.0" />
</ItemGroup>
+11 -19
View File
@@ -2,18 +2,15 @@
using Daybreak.Exceptions;
using Daybreak.Services.ApplicationLifetime;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using NReco.Logging.File;
using Slim;
using System;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Extensions;
using System.Windows.Extensions.Http;
namespace Daybreak.Launch
{
@@ -28,22 +25,9 @@ namespace Daybreak.Launch
return LaunchMainWindow();
}
protected override ILoggerFactory SetupLoggerFactory()
{
var factory = new LoggerFactory();
factory.AddProvider(new FileLoggerProvider("Daybreak.db"));
return factory;
}
protected override void SetupServiceManager(IServiceManager serviceManager)
{
serviceManager.RegisterResolver(
new HttpClientResolver()
.WithHttpMessageHandlerFactory((serviceProvider, categoryType) =>
{
var loggerType = typeof(ILogger<>).MakeGenericType(categoryType);
var logger = serviceProvider.GetService(loggerType).As<ILogger>();
return new LoggingHttpHandler(logger, new HttpClientHandler());
}));
ProjectConfiguration.RegisterResolvers(serviceManager);
}
protected override void RegisterServices(IServiceProducer serviceProducer)
{
@@ -63,6 +47,14 @@ namespace Daybreak.Launch
{
this.ServiceManager.GetService<ILogger>().LogCritical(e, $"{nameof(FatalException)} encountered. Closing application.");
MessageBox.Show(fatalException.ToString());
File.WriteAllText("crash.log", e.ToString());
return false;
}
else if (e is TargetInvocationException targetInvocationException && e.InnerException is FatalException innerFatalException)
{
this.ServiceManager.GetService<ILogger>().LogCritical(e, $"{nameof(FatalException)} encountered. Closing application.");
MessageBox.Show(innerFatalException.ToString());
File.WriteAllText("crash.log", e.ToString());
return false;
}
else if (e is AggregateException aggregateException)
+20
View File
@@ -0,0 +1,20 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
namespace Daybreak.Models
{
public sealed class Log
{
[JsonProperty("DateTime")]
public DateTime LogTime { get; set; }
[JsonProperty("Category")]
public string Category { get; set; }
[JsonProperty("EventId")]
public string EventId { get; set; }
[JsonProperty("LogLevel")]
public LogLevel LogLevel { get; set; }
[JsonProperty("Message")]
public string Message { get; set; }
}
}
+12
View File
@@ -0,0 +1,12 @@
using Daybreak.Models;
using System.Collections.Generic;
namespace Daybreak.Services.Logging
{
public interface ILogsManager
{
void WriteLog(Log log);
IEnumerable<Log> GetLogs();
int DeleteLogs();
}
}
+45
View File
@@ -0,0 +1,45 @@
using Daybreak.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Extensions;
namespace Daybreak.Services.Logging
{
public sealed class JsonLogger : ILogger
{
private readonly string category;
private readonly JsonLoggerProvider jsonLoggerProvider;
public JsonLogger(string category, JsonLoggerProvider jsonLoggerProvider)
{
this.category = category;
this.jsonLoggerProvider = jsonLoggerProvider.ThrowIfNull(nameof(jsonLoggerProvider));
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var message = formatter(state, exception);
var log = new Log
{
LogLevel = logLevel,
EventId = eventId.Name,
Message = message,
Category = category,
LogTime = DateTime.Now
};
this.jsonLoggerProvider.LogEntry(log);
}
}
}
@@ -0,0 +1,30 @@
using Daybreak.Models;
using Microsoft.Extensions.Logging;
using System.Extensions;
namespace Daybreak.Services.Logging
{
public sealed class JsonLoggerProvider : ILoggerProvider
{
private readonly ILogsManager logsManager;
public JsonLoggerProvider(ILogsManager logsManager)
{
this.logsManager = logsManager.ThrowIfNull(nameof(logsManager));
}
public void LogEntry(Log log)
{
this.logsManager.WriteLog(log);
}
public ILogger CreateLogger(string categoryName)
{
return new JsonLogger(categoryName, this);
}
public void Dispose()
{
}
}
}
@@ -0,0 +1,30 @@
using Daybreak.Models;
using LiteDB;
using System.Collections.Generic;
using System.Extensions;
namespace Daybreak.Services.Logging
{
public sealed class JsonLogsManager : ILogsManager
{
private readonly ILiteDatabase liteDatabase;
public JsonLogsManager(ILiteDatabase liteDatabase)
{
this.liteDatabase = liteDatabase.ThrowIfNull(nameof(liteDatabase));
}
public IEnumerable<Log> GetLogs()
{
return this.liteDatabase.GetCollection<Log>().FindAll();
}
public void WriteLog(Log log)
{
this.liteDatabase.GetCollection<Log>().Insert(log);
}
public int DeleteLogs()
{
return this.liteDatabase.GetCollection<Log>().DeleteAll();
}
}
}
-35
View File
@@ -1,35 +0,0 @@
using Microsoft.Extensions.Logging;
using System;
using System.Extensions;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Daybreak.Utils
{
public sealed class LoggingHttpHandler : DelegatingHandler
{
private readonly ILogger logger;
public LoggingHttpHandler(ILogger logger, HttpMessageHandler innerHandler) : base(innerHandler)
{
this.logger = logger.ThrowIfNull(nameof(logger));
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
this.logger.LogInformation($"{request.Method} - {request.RequestUri}");
try
{
var response = await base.SendAsync(request, cancellationToken);
this.logger.LogInformation($"{request.RequestUri} - {response.StatusCode}");
return response;
}
catch(Exception e)
{
this.logger.LogError($"Exception encountered while processing {request.Method} - {request.RequestUri}", e);
throw;
}
}
}
}
+60
View File
@@ -0,0 +1,60 @@
<UserControl x:Class="Daybreak.Views.LogsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Views"
xmlns:controls="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="{x:Type TextBlock}" x:Key="WrapText">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</UserControl.Resources>
<Grid Background="#A0202020">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<controls:BackButton Foreground="White" Height="30" Width="30" HorizontalAlignment="Left" Margin="5"
Clicked="BackButton_Clicked"></controls:BackButton>
<controls:BinButton Foreground="White" Height="30" Width="30" HorizontalAlignment="Right" Margin="5"
Clicked="BinButton_Clicked"></controls:BinButton>
<controls:RefreshGlyph Foreground="White" Height="30" Width="30" HorizontalAlignment="Right" Margin="0, 5, 45, 5"
Clicked="RefreshGlyph_Clicked"></controls:RefreshGlyph>
<DataGrid IsReadOnly="True" Background="Transparent" Foreground="White" Grid.Row="1"
ItemsSource="{Binding ElementName=_this, Path=Logs, Mode=OneWay}" HorizontalScrollBarVisibility="Disabled"
AutoGenerateColumns="False" HeadersVisibility="Column" EnableColumnVirtualization="True"
EnableRowVirtualization="True">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Foreground" Value="White"></Setter>
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="BorderBrush" Value="#80808080"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderBrush" Value="#80808080"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
</Style>
</DataGrid.CellStyle>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="Foreground" Value="White"></Setter>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn IsReadOnly="True" Header="DateTime" Binding="{Binding LogTime}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="Category" Binding="{Binding Category}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="LogLevel" Binding="{Binding LogLevel}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="EventId" Binding="{Binding EventId}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="Message" Binding="{Binding Message}" ElementStyle="{StaticResource WrapText}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
+49
View File
@@ -0,0 +1,49 @@
using Daybreak.Services.Logging;
using Daybreak.Services.ViewManagement;
using System;
using System.Collections.ObjectModel;
using System.Extensions;
using System.Windows.Controls;
using Daybreak.Models;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for LogsView.xaml
/// </summary>
public partial class LogsView : UserControl
{
private readonly IViewManager viewManager;
private readonly ILogsManager logManager;
public ObservableCollection<Log> Logs { get; } = new ObservableCollection<Log>();
public LogsView(
IViewManager viewManager,
ILogsManager logManager)
{
this.logManager = logManager.ThrowIfNull(nameof(logManager));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.UpdateLogs();
}
private void UpdateLogs()
{
this.Logs.ClearAnd().AddRange(this.logManager.GetLogs());
}
private void BackButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<SettingsCategoryView>();
}
private void BinButton_Clicked(object sender, EventArgs e)
{
this.logManager.DeleteLogs();
this.UpdateLogs();
}
private void RefreshGlyph_Clicked(object sender, EventArgs e)
{
this.UpdateLogs();
}
}
}
+6
View File
@@ -48,6 +48,12 @@
<controls:StaticGlyph></controls:StaticGlyph>
</controls:TileButton.InnerContent>
</controls:TileButton>
<controls:TileButton Title="Logs" Foreground="White" BorderBrush="White" BorderThickness="2"
HighlightColor="White" Clicked="LogsButton_Clicked" Height="150" Width="150">
<controls:TileButton.InnerContent>
<controls:LogsGlyph></controls:LogsGlyph>
</controls:TileButton.InnerContent>
</controls:TileButton>
</WrapPanel>
</WrapPanel>
</Grid>
@@ -43,6 +43,11 @@ namespace Daybreak.Views
this.viewManager.ShowView<VersionManagementView>(this);
}
private void LogsButton_Clicked(object sender, System.EventArgs e)
{
this.viewManager.ShowView<LogsView>();
}
private void FileButton_Clicked(object sender, System.EventArgs e)
{
this.viewManager.ShowView<ExecutablesView>();