Ionite
1 year ago
committed by
GitHub
28 changed files with 776 additions and 9 deletions
@ -0,0 +1,74 @@
|
||||
<UserControl x:Class="StabilityMatrix.Avalonia.Diagnostics.LogViewer.Controls.LogViewerControl" |
||||
xmlns="https://github.com/avaloniaui" |
||||
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:converters="clr-namespace:StabilityMatrix.Avalonia.Diagnostics.LogViewer.Converters" |
||||
xmlns:logging="clr-namespace:StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging" |
||||
x:CompileBindings="True" |
||||
x:DataType="logging:ILogDataStoreImpl" |
||||
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" > |
||||
|
||||
<Grid RowDefinitions="*,Auto"> |
||||
<Grid.Resources> |
||||
<converters:ChangeColorTypeConverter x:Key="ColorConverter" /> |
||||
<converters:EventIdConverter x:Key="EventIdConverter"/> |
||||
<SolidColorBrush x:Key="ColorBlack">Black</SolidColorBrush> |
||||
<SolidColorBrush x:Key="ColorTransparent">Transparent</SolidColorBrush> |
||||
</Grid.Resources> |
||||
<Grid.Styles> |
||||
<Style Selector="DataGridRow"> |
||||
<Setter Property="Padding" Value="0" /> |
||||
<Setter Property="Foreground" |
||||
x:DataType="logging:LogModel" |
||||
Value="{Binding Color.Foreground, |
||||
FallbackValue=White, |
||||
Converter={StaticResource ColorConverter}, ConverterParameter={StaticResource ColorBlack}}" /> |
||||
<Setter Property="Background" |
||||
x:DataType="logging:LogModel" |
||||
Value="{Binding Color.Background, |
||||
FallbackValue=Black, |
||||
Converter={StaticResource ColorConverter}, ConverterParameter={StaticResource ColorTransparent}}" /> |
||||
</Style> |
||||
<Style Selector="DataGridCell.size"> |
||||
<Setter Property="FontSize" Value="13" /> |
||||
<Setter Property="Padding" Value="0" /> |
||||
</Style> |
||||
</Grid.Styles> |
||||
<DataGrid x:Name="MyDataGrid" |
||||
ItemsSource="{Binding DataStore.Entries}" AutoGenerateColumns="False" |
||||
CanUserResizeColumns="True" |
||||
CanUserReorderColumns="True" |
||||
CanUserSortColumns="False" |
||||
LayoutUpdated="OnLayoutUpdated"> |
||||
|
||||
<DataGrid.Styles> |
||||
<Style Selector="TextBlock"> |
||||
<Setter Property="TextWrapping" Value="WrapWithOverflow" /> |
||||
</Style> |
||||
</DataGrid.Styles> |
||||
|
||||
<DataGrid.Columns> |
||||
<DataGridTextColumn CellStyleClasses="size" Header="Time" Width="Auto" Binding="{Binding Timestamp}" IsVisible="{Binding #IsTimestampVisible.IsChecked}"/> |
||||
<DataGridTextColumn CellStyleClasses="size" Header="Level" Width="Auto" Binding="{Binding LogLevel}" /> |
||||
<!--<DataGridTextColumn CellStyleClasses="size" Header="Event Id" Width="120" Binding="{Binding EventId, Converter={StaticResource EventIdConverter}}" />--> |
||||
<DataGridTextColumn CellStyleClasses="size" Header="Callsite" Width="Auto" Binding="{Binding LoggerDisplayName}" /> |
||||
<DataGridTextColumn CellStyleClasses="size" Header="State" Width="*" Binding="{Binding State}" /> |
||||
<DataGridTextColumn CellStyleClasses="size" Header="Exception" Width="Auto" Binding="{Binding Exception}" /> |
||||
</DataGrid.Columns> |
||||
</DataGrid> |
||||
|
||||
<StackPanel Grid.Row="1" Margin="20 10" Orientation="Horizontal"> |
||||
<CheckBox x:Name="CanAutoScroll" |
||||
FontSize="11" |
||||
Content="Auto Scroll log" |
||||
IsChecked="True"/> |
||||
<CheckBox x:Name="IsTimestampVisible" |
||||
FontSize="11" |
||||
Content="Show Timestamp"/> |
||||
</StackPanel> |
||||
|
||||
|
||||
</Grid> |
||||
|
||||
</UserControl> |
@ -0,0 +1,52 @@
|
||||
using System.Collections.Specialized; |
||||
using Avalonia.Controls; |
||||
using Avalonia.LogicalTree; |
||||
using Avalonia.Threading; |
||||
using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Controls; |
||||
|
||||
public partial class LogViewerControl : UserControl |
||||
{ |
||||
public LogViewerControl() |
||||
=> InitializeComponent(); |
||||
|
||||
private ILogDataStoreImpl? vm; |
||||
private LogModel? item; |
||||
|
||||
protected override void OnDataContextChanged(EventArgs e) |
||||
{ |
||||
base.OnDataContextChanged(e); |
||||
|
||||
if (DataContext is null) |
||||
return; |
||||
|
||||
vm = (ILogDataStoreImpl)DataContext; |
||||
vm.DataStore.Entries.CollectionChanged += OnCollectionChanged; |
||||
} |
||||
|
||||
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) |
||||
{ |
||||
Dispatcher.UIThread.Post(() => |
||||
{ |
||||
item = MyDataGrid.ItemsSource.Cast<LogModel>().LastOrDefault(); |
||||
}); |
||||
} |
||||
|
||||
protected void OnLayoutUpdated(object? sender, EventArgs e) |
||||
{ |
||||
if (CanAutoScroll.IsChecked != true || item is null) |
||||
return; |
||||
|
||||
MyDataGrid.ScrollIntoView(item, null); |
||||
item = null; |
||||
} |
||||
|
||||
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) |
||||
{ |
||||
base.OnDetachedFromLogicalTree(e); |
||||
|
||||
if (vm is null) return; |
||||
vm.DataStore.Entries.CollectionChanged -= OnCollectionChanged; |
||||
} |
||||
} |
@ -0,0 +1,25 @@
|
||||
using System.Globalization; |
||||
using Avalonia.Data.Converters; |
||||
using Avalonia.Media; |
||||
using SysDrawColor = System.Drawing.Color; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Converters; |
||||
|
||||
public class ChangeColorTypeConverter : IValueConverter |
||||
{ |
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||
{ |
||||
if (value is null) |
||||
return new SolidColorBrush((Color)(parameter ?? Colors.Black)); |
||||
|
||||
var sysDrawColor = (SysDrawColor)value!; |
||||
return new SolidColorBrush(Color.FromArgb( |
||||
sysDrawColor.A, |
||||
sysDrawColor.R, |
||||
sysDrawColor.G, |
||||
sysDrawColor.B)); |
||||
} |
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||
=> throw new NotImplementedException(); |
||||
} |
@ -0,0 +1,22 @@
|
||||
using System.Globalization; |
||||
using Avalonia.Data.Converters; |
||||
using Microsoft.Extensions.Logging; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Converters; |
||||
|
||||
public class EventIdConverter : IValueConverter |
||||
{ |
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||
{ |
||||
if (value is null) |
||||
return "0"; |
||||
|
||||
var eventId = (EventId)value; |
||||
|
||||
return eventId.ToString(); |
||||
} |
||||
|
||||
// If not implemented, an error is thrown |
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||
=> new EventId(0, value?.ToString() ?? string.Empty); |
||||
} |
@ -0,0 +1,55 @@
|
||||
using Microsoft.Extensions.Logging; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Extensions; |
||||
|
||||
public static class LoggerExtensions |
||||
{ |
||||
public static void Emit(this ILogger logger, EventId eventId, |
||||
LogLevel logLevel, string message, Exception? exception = null, params object?[] args) |
||||
{ |
||||
if (logger is null) |
||||
return; |
||||
|
||||
//if (!logger.IsEnabled(logLevel)) |
||||
// return; |
||||
|
||||
switch (logLevel) |
||||
{ |
||||
case LogLevel.Trace: |
||||
logger.LogTrace(eventId, message, args); |
||||
break; |
||||
|
||||
case LogLevel.Debug: |
||||
logger.LogDebug(eventId, message, args); |
||||
break; |
||||
|
||||
case LogLevel.Information: |
||||
logger.LogInformation(eventId, message, args); |
||||
break; |
||||
|
||||
case LogLevel.Warning: |
||||
logger.LogWarning(eventId, exception, message, args); |
||||
break; |
||||
|
||||
case LogLevel.Error: |
||||
logger.LogError(eventId, exception, message, args); |
||||
break; |
||||
|
||||
case LogLevel.Critical: |
||||
logger.LogCritical(eventId, exception, message, args); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public static void TestPattern(this ILogger logger, EventId eventId) |
||||
{ |
||||
var exception = new Exception("Test Error Message"); |
||||
|
||||
logger.Emit(eventId, LogLevel.Trace, "Trace Test Pattern"); |
||||
logger.Emit(eventId, LogLevel.Debug, "Debug Test Pattern"); |
||||
logger.Emit(eventId, LogLevel.Information, "Information Test Pattern"); |
||||
logger.Emit(eventId, LogLevel.Warning, "Warning Test Pattern"); |
||||
logger.Emit(eventId, LogLevel.Error, "Error Test Pattern", exception); |
||||
logger.Emit(eventId, LogLevel.Critical, "Critical Test Pattern", exception); |
||||
} |
||||
} |
@ -0,0 +1,47 @@
|
||||
using System.Drawing; |
||||
using Microsoft.Extensions.Logging; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; |
||||
|
||||
public class DataStoreLoggerConfiguration |
||||
{ |
||||
#region Properties |
||||
|
||||
public EventId EventId { get; set; } |
||||
|
||||
public Dictionary<LogLevel, LogEntryColor> Colors { get; } = new() |
||||
{ |
||||
[LogLevel.Trace] = new LogEntryColor |
||||
{ |
||||
Foreground = Color.DarkGray |
||||
}, |
||||
[LogLevel.Debug] = new LogEntryColor |
||||
{ |
||||
Foreground = Color.Gray |
||||
}, |
||||
[LogLevel.Information] = new LogEntryColor |
||||
{ |
||||
Foreground = Color.WhiteSmoke, |
||||
}, |
||||
[LogLevel.Warning] = new LogEntryColor |
||||
{ |
||||
Foreground = Color.Orange |
||||
}, |
||||
[LogLevel.Error] = new LogEntryColor |
||||
{ |
||||
Foreground = Color.White, |
||||
Background = Color.OrangeRed |
||||
}, |
||||
[LogLevel.Critical] = new LogEntryColor |
||||
{ |
||||
Foreground = Color.White, |
||||
Background = Color.Red |
||||
}, |
||||
[LogLevel.None] = new LogEntryColor |
||||
{ |
||||
Foreground = Color.Magenta |
||||
} |
||||
}; |
||||
|
||||
#endregion |
||||
} |
@ -0,0 +1,9 @@
|
||||
using System.Collections.ObjectModel; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; |
||||
|
||||
public interface ILogDataStore |
||||
{ |
||||
ObservableCollection<LogModel> Entries { get; } |
||||
void AddEntry(LogModel logModel); |
||||
} |
@ -0,0 +1,6 @@
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; |
||||
|
||||
public interface ILogDataStoreImpl |
||||
{ |
||||
public ILogDataStore DataStore { get; } |
||||
} |
@ -0,0 +1,38 @@
|
||||
using System.Collections.ObjectModel; |
||||
using Avalonia.Threading; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; |
||||
|
||||
public class LogDataStore : ILogDataStore |
||||
{ |
||||
public static LogDataStore Instance { get; } = new(); |
||||
|
||||
#region Fields |
||||
|
||||
private static readonly SemaphoreSlim _semaphore = new(initialCount: 1); |
||||
|
||||
#endregion |
||||
|
||||
#region Properties |
||||
|
||||
public ObservableCollection<LogModel> Entries { get; } = new(); |
||||
|
||||
#endregion |
||||
|
||||
#region Methods |
||||
|
||||
public virtual void AddEntry(LogModel logModel) |
||||
{ |
||||
// ensure only one operation at time from multiple threads |
||||
_semaphore.Wait(); |
||||
|
||||
Dispatcher.UIThread.Post(() => |
||||
{ |
||||
Entries.Add(logModel); |
||||
}); |
||||
|
||||
_semaphore.Release(); |
||||
} |
||||
|
||||
#endregion |
||||
} |
@ -0,0 +1,20 @@
|
||||
using System.Drawing; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; |
||||
|
||||
public class LogEntryColor |
||||
{ |
||||
public LogEntryColor() |
||||
{ |
||||
} |
||||
|
||||
public LogEntryColor(Color foreground, Color background) |
||||
{ |
||||
Foreground = foreground; |
||||
Background = background; |
||||
} |
||||
|
||||
public Color Foreground { get; set; } = Color.Black; |
||||
public Color Background { get; set; } = Color.Transparent; |
||||
|
||||
} |
@ -0,0 +1,33 @@
|
||||
using Microsoft.Extensions.Logging; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; |
||||
|
||||
public class LogModel |
||||
{ |
||||
#region Properties |
||||
|
||||
public DateTime Timestamp { get; set; } |
||||
|
||||
public LogLevel LogLevel { get; set; } |
||||
|
||||
public EventId EventId { get; set; } |
||||
|
||||
public object? State { get; set; } |
||||
|
||||
public string? LoggerName { get; set; } |
||||
|
||||
public string? CallerClassName { get; set; } |
||||
|
||||
public string? CallerMemberName { get; set; } |
||||
|
||||
public string? Exception { get; set; } |
||||
|
||||
public LogEntryColor? Color { get; set; } |
||||
|
||||
#endregion |
||||
|
||||
public string LoggerDisplayName => |
||||
LoggerName? |
||||
.Split('.', StringSplitOptions.RemoveEmptyEntries) |
||||
.LastOrDefault() ?? ""; |
||||
} |
@ -0,0 +1,21 @@
|
||||
using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels; |
||||
|
||||
public class LogViewerControlViewModel : ViewModel, ILogDataStoreImpl |
||||
{ |
||||
#region Constructor |
||||
|
||||
public LogViewerControlViewModel(ILogDataStore dataStore) |
||||
{ |
||||
DataStore = dataStore; |
||||
} |
||||
|
||||
#endregion |
||||
|
||||
#region Properties |
||||
|
||||
public ILogDataStore DataStore { get; set; } |
||||
|
||||
#endregion |
||||
} |
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel; |
||||
using System.Runtime.CompilerServices; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels; |
||||
|
||||
public class ObservableObject : INotifyPropertyChanged |
||||
{ |
||||
protected bool Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string? propertyName = null) |
||||
{ |
||||
if (EqualityComparer<TValue>.Default.Equals(field, newValue)) return false; |
||||
field = newValue; |
||||
OnPropertyChanged(propertyName); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged; |
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) |
||||
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
||||
} |
@ -0,0 +1,3 @@
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels; |
||||
|
||||
public class ViewModel : ObservableObject { /* skip */ } |
@ -0,0 +1,75 @@
|
||||
using System.Diagnostics; |
||||
using Microsoft.Extensions.Logging; |
||||
using NLog; |
||||
using NLog.Targets; |
||||
using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; |
||||
using MsLogLevel = Microsoft.Extensions.Logging.LogLevel; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer; |
||||
|
||||
[Target("DataStoreLogger")] |
||||
public class DataStoreLoggerTarget : TargetWithLayout |
||||
{ |
||||
#region Fields |
||||
|
||||
private ILogDataStore? _dataStore; |
||||
private DataStoreLoggerConfiguration? _config; |
||||
|
||||
#endregion |
||||
|
||||
#region methods |
||||
|
||||
protected override void InitializeTarget() |
||||
{ |
||||
// we need to inject dependencies |
||||
// var serviceProvider = ResolveService<IServiceProvider>(); |
||||
|
||||
// reference the shared instance |
||||
_dataStore = LogDataStore.Instance; |
||||
// _dataStore = serviceProvider.GetRequiredService<ILogDataStore>(); |
||||
|
||||
// load the config options |
||||
/*var options |
||||
= serviceProvider.GetService<IOptionsMonitor<DataStoreLoggerConfiguration>>();*/ |
||||
|
||||
// _config = options?.CurrentValue ?? new DataStoreLoggerConfiguration(); |
||||
_config = new DataStoreLoggerConfiguration(); |
||||
|
||||
base.InitializeTarget(); |
||||
} |
||||
|
||||
protected override void Write(LogEventInfo logEvent) |
||||
{ |
||||
// cast NLog Loglevel to Microsoft LogLevel type |
||||
var logLevel = (MsLogLevel)Enum.ToObject(typeof(MsLogLevel), logEvent.Level.Ordinal); |
||||
|
||||
// format the message |
||||
var message = RenderLogEvent(Layout, logEvent); |
||||
|
||||
// retrieve the EventId |
||||
logEvent.Properties.TryGetValue("EventId", out var result); |
||||
if (result is not EventId eventId) |
||||
{ |
||||
eventId = _config!.EventId; |
||||
} |
||||
|
||||
// add log entry |
||||
_dataStore?.AddEntry(new LogModel |
||||
{ |
||||
Timestamp = DateTime.UtcNow, |
||||
LogLevel = logLevel, |
||||
// do we override the default EventId if it exists? |
||||
EventId = eventId.Id == 0 && (_config?.EventId.Id ?? 0) != 0 ? _config!.EventId : eventId, |
||||
State = message, |
||||
LoggerName = logEvent.LoggerName, |
||||
CallerClassName = logEvent.CallerClassName, |
||||
CallerMemberName = logEvent.CallerMemberName, |
||||
Exception = logEvent.Exception?.Message ?? (logLevel == MsLogLevel.Error ? message : ""), |
||||
Color = _config!.Colors[logLevel], |
||||
}); |
||||
|
||||
Debug.WriteLine($"--- [{logLevel.ToString()[..3]}] {message} - {logEvent.Exception?.Message ?? "no error"}"); |
||||
} |
||||
|
||||
#endregion |
||||
} |
@ -0,0 +1,65 @@
|
||||
using System.Diagnostics.CodeAnalysis; |
||||
using Microsoft.Extensions.Configuration; |
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Logging; |
||||
using NLog; |
||||
using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging; |
||||
using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels; |
||||
using LogDataStore = StabilityMatrix.Avalonia.Diagnostics.LogViewer.Logging.LogDataStore; |
||||
using MsLogLevel = Microsoft.Extensions.Logging.LogLevel; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Extensions; |
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] |
||||
public static class ServicesExtension |
||||
{ |
||||
public static IServiceCollection AddLogViewer(this IServiceCollection services) |
||||
{ |
||||
services.AddSingleton<ILogDataStore, LogDataStore>(); |
||||
services.AddSingleton<LogViewerControlViewModel>(); |
||||
|
||||
return services; |
||||
} |
||||
|
||||
public static IServiceCollection AddLogViewer( |
||||
this IServiceCollection services, |
||||
Action<DataStoreLoggerConfiguration> configure) |
||||
{ |
||||
services.AddSingleton<ILogDataStore>(Core.Logging.LogDataStore.Instance); |
||||
services.AddSingleton<LogViewerControlViewModel>(); |
||||
services.Configure(configure); |
||||
|
||||
return services; |
||||
} |
||||
|
||||
public static ILoggingBuilder AddNLogTargets(this ILoggingBuilder builder, IConfiguration config) |
||||
{ |
||||
LogManager |
||||
.Setup() |
||||
// Register custom Target |
||||
.SetupExtensions(extensionBuilder => |
||||
extensionBuilder.RegisterTarget<DataStoreLoggerTarget>("DataStoreLogger")); |
||||
|
||||
/*builder |
||||
.ClearProviders() |
||||
.SetMinimumLevel(MsLogLevel.Trace) |
||||
// Load NLog settings from appsettings*.json |
||||
.AddNLog(config, |
||||
// custom options for capturing the EventId information |
||||
new NLogProviderOptions |
||||
{ |
||||
// https://nlog-project.org/2021/08/25/nlog-5-0-preview1-ready.html#nlogextensionslogging-changes-capture-of-eventid |
||||
IgnoreEmptyEventId = false, |
||||
CaptureEventId = EventIdCaptureType.Legacy |
||||
});*/ |
||||
|
||||
return builder; |
||||
} |
||||
|
||||
public static ILoggingBuilder AddNLogTargets(this ILoggingBuilder builder, IConfiguration config, Action<DataStoreLoggerConfiguration> configure) |
||||
{ |
||||
builder.AddNLogTargets(config); |
||||
builder.Services.Configure(configure); |
||||
return builder; |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
MIT License |
||||
|
||||
Copyright (c) 2022 Graeme Grant |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,13 @@
|
||||
using Avalonia.Threading; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Logging; |
||||
|
||||
public class LogDataStore : Core.Logging.LogDataStore |
||||
{ |
||||
#region Methods |
||||
|
||||
public override async void AddEntry(Core.Logging.LogModel logModel) |
||||
=> await Dispatcher.UIThread.InvokeAsync(() => base.AddEntry(logModel)); |
||||
|
||||
#endregion |
||||
} |
@ -0,0 +1,3 @@
|
||||
## LogViewer |
||||
|
||||
Source code in the `StabilityMatrix.Avalonia.Diagnostics.LogViewer `namespace is included from [CodeProject](https://www.codeproject.com/Articles/5357417/LogViewer-Control-for-WinForms-WPF-and-Avalonia-in) under the [MIT License](LICENSE). |
@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<PropertyGroup> |
||||
<TargetFramework>net7.0</TargetFramework> |
||||
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64;osx-arm64</RuntimeIdentifiers> |
||||
<ImplicitUsings>enable</ImplicitUsings> |
||||
<Nullable>enable</Nullable> |
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> |
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting> |
||||
</PropertyGroup> |
||||
|
||||
<ItemGroup> |
||||
<InternalsVisibleTo Include="StabilityMatrix.Tests" /> |
||||
</ItemGroup> |
||||
|
||||
<ItemGroup> |
||||
<Folder Include="LogViewer\Core\" /> |
||||
<Folder Include="Views\" /> |
||||
</ItemGroup> |
||||
|
||||
<ItemGroup> |
||||
<PackageReference Include="Avalonia" Version="11.0.4" /> |
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.4" /> |
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> |
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> |
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" /> |
||||
<PackageReference Include="NLog" Version="5.2.3" /> |
||||
</ItemGroup> |
||||
|
||||
<ItemGroup> |
||||
<Compile Update="Views\LogWindow.axaml.cs"> |
||||
<DependentUpon>LogWindow.axaml</DependentUpon> |
||||
</Compile> |
||||
</ItemGroup> |
||||
|
||||
</Project> |
@ -0,0 +1,20 @@
|
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.ViewModels; |
||||
|
||||
public class LogWindowViewModel |
||||
{ |
||||
public LogViewerControlViewModel LogViewer { get; } |
||||
|
||||
public LogWindowViewModel(LogViewerControlViewModel logViewer) |
||||
{ |
||||
LogViewer = logViewer; |
||||
} |
||||
|
||||
public static LogWindowViewModel FromServiceProvider(IServiceProvider services) |
||||
{ |
||||
return new LogWindowViewModel( |
||||
services.GetRequiredService<LogViewerControlViewModel>()); |
||||
} |
||||
} |
@ -0,0 +1,22 @@
|
||||
<Window |
||||
x:Class="StabilityMatrix.Avalonia.Diagnostics.Views.LogWindow" |
||||
xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Diagnostics.LogViewer.Controls" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.Diagnostics.ViewModels" |
||||
Title="Log Viewer" |
||||
Width="900" |
||||
Height="750" |
||||
Focusable="True" |
||||
d:DesignHeight="450" |
||||
d:DesignWidth="800" |
||||
x:CompileBindings="True" |
||||
x:DataType="vm:LogWindowViewModel" |
||||
WindowStartupLocation="CenterScreen" |
||||
mc:Ignorable="d"> |
||||
|
||||
<controls:LogViewerControl DataContext="{Binding LogViewer}" /> |
||||
|
||||
</Window> |
@ -0,0 +1,39 @@
|
||||
using Avalonia.Controls; |
||||
using Avalonia.Input; |
||||
using Avalonia.Interactivity; |
||||
using StabilityMatrix.Avalonia.Diagnostics.ViewModels; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Diagnostics.Views; |
||||
|
||||
public partial class LogWindow : Window |
||||
{ |
||||
public LogWindow() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
public static IDisposable Attach(TopLevel root, IServiceProvider serviceProvider) |
||||
{ |
||||
return Attach(root, serviceProvider, new KeyGesture(Key.F11)); |
||||
} |
||||
|
||||
public static IDisposable Attach(TopLevel root, IServiceProvider serviceProvider, KeyGesture gesture) |
||||
{ |
||||
return (root ?? throw new ArgumentNullException(nameof(root))).AddDisposableHandler( |
||||
KeyDownEvent, |
||||
PreviewKeyDown, |
||||
RoutingStrategies.Tunnel); |
||||
|
||||
void PreviewKeyDown(object? sender, KeyEventArgs e) |
||||
{ |
||||
if (gesture.Matches(e)) |
||||
{ |
||||
var window = new LogWindow() |
||||
{ |
||||
DataContext = LogWindowViewModel.FromServiceProvider(serviceProvider) |
||||
}; |
||||
window.Show(); |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue