initial commit

This commit is contained in:
Matthias Heil
2026-04-04 13:30:13 +02:00
commit 6bed9b284c
186 changed files with 10650 additions and 0 deletions
@@ -0,0 +1,14 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AvaloniaSerilogDI"
x:Class="AvaloniaSerilogDI.App">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>
@@ -0,0 +1,207 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using AvaloniaSerilogDI.ViewModels;
using AvaloniaSerilogDI.Views;
using Common.Core.Extensions;
using LogViewer.Avalonia;
using LogViewer.Core;
using MessageBox.Avalonia;
using MessageBox.Avalonia.Enums;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using RandomLogging.Service;
using Serilog;
using Serilog.Sinks.LogView.Core;
using System;
using System.Drawing;
using System.Reflection;
using System.Threading;
using Icon = MessageBox.Avalonia.Enums.Icon;
namespace AvaloniaSerilogDI;
public partial class App : Application
{
#region Constructors
public override void Initialize()
=> AvaloniaXamlLoader.Load(this);
#endregion
#region Fields
private IHost? _host;
private CancellationTokenSource? _cancellationTokenSource;
#endregion
#region Methods
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
ExpressionObserver.DataValidators.RemoveAll(x => x is DataAnnotationsValidationPlugin);
// catch all unhandled errors
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
HostApplicationBuilder builder = Host.CreateApplicationBuilder();
builder
// Register the Random Logging Service
.AddRandomBackgroundService()
// visual debugging tools
.AddLogViewer();
IServiceCollection services = builder.Services;
// Serilog Logger
// Azure: https://devblogs.microsoft.com/dotnet/asp-net-core-logging/
// ApplicationInsights: https://github.com/serilog-contrib/serilog-sinks-applicationinsights
// AmazonCloudWatch: https://blog.ivankahl.com/logging-dotnet-to-aws-cloudwatch-using-serilog/
// video: https://www.youtube.com/watch?v=nVAkSBpsuTk (How Structured Logging With Serilog Can Make Your Life Easier)
// video: https://www.youtube.com/watch?v=_iryZxv8Rxw (C# Logging with Serilog and Seq - Structured Logging Made Easy)
// ps: docker run -d --restart unless-stopped --name seq -e ACCEPT_EULA=Y -v c:\WIP\LogData:/data -p 8081:80 datalust/seq:latest
// docker rmi datalust/seq --force
// ref: https://stackoverflow.com/questions/66304596/how-to-dependency-inject-serilog-into-the-rest-of-my-classes-in-net-console-app
services.AddLogging(configure: cfg =>
{
Log.Logger = new LoggerConfiguration()
//Serilog.Core.Logger logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.WriteTo.DataStoreLoggerSink(
dataStoreProvider: () => _host!.Services.TryGetService<ILogDataStore>()!
//dataStoreProvider: () => _host!.Services.TryGetService<ILogDataStore>()!,
//options =>
//{
// options.Colors[LogLevel.Trace] = new()
// {
// Foreground = Color.White,
// Background = Color.DarkGray
// };
// options.Colors[LogLevel.Debug] = new()
// {
// Foreground = Color.White,
// Background = Color.Gray
// };
// options.Colors[LogLevel.Information] = new()
// {
// Foreground = Color.White,
// Background = Color.DodgerBlue
// };
// options.Colors[LogLevel.Warning] = new()
// {
// Foreground = Color.White,
// Background = Color.Orchid
// };
//}
)
.CreateLogger();
cfg.ClearProviders()
.AddSerilog(Log.Logger);
});
services
.AddSingleton<MainViewModel>()
.AddSingleton(service => new MainWindow
{
DataContext = service.GetRequiredService<MainViewModel>()
});
_host = builder.Build();
_cancellationTokenSource = new();
try
{
LogStartingMode();
// set and show
desktop.MainWindow = _host.Services.GetRequiredService<MainWindow>();
desktop.ShutdownRequested += OnShutdownRequested;
// startup background services
_ = _host.StartAsync(_cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
// skip
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
ShowMessageBox("Unhandled Error", ex.Message);
CleanUp();
return;
}
}
base.OnFrameworkInitializationCompleted();
}
private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)
=> CleanUp();
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception exception = (Exception)e.ExceptionObject;
Log.Fatal(exception, "Application terminated unexpectedly");
ShowMessageBox("Unhandled Error", exception.Message);
CleanUp();
}
private void ShowMessageBox(string title, string message)
{
MessageBox.Avalonia.BaseWindows.Base.IMsBoxWindow<ButtonResult> messageBoxStandardWindow = MessageBoxManager
.GetMessageBoxStandardWindow(title, message, ButtonEnum.Ok, Icon.Stop);
messageBoxStandardWindow.Show();
}
private void LogStartingMode()
{
// Get the Launch mode
bool isDevelopment = string.Equals(Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES"), "debug",
StringComparison.InvariantCultureIgnoreCase);
// initialize a logger & EventId
ILogger<App> logger = _host!.Services.GetRequiredService<ILogger<App>>();
EventId eventId = new EventId(id: 0, name: Assembly.GetEntryAssembly()!.GetName().Name);
// log a test pattern for each log level
logger.TestPattern(eventId: eventId);
// log that we have started...
logger.Emit(eventId, LogLevel.Information, $"Running in {(isDevelopment ? "Debug" : "Release")} mode");
}
private void CleanUp()
{
// tell the background services that we are shutting down
_ = _host!.StopAsync(_cancellationTokenSource!.Token);
// flush logs
Log.CloseAndFlush();
}
#endregion
}
@@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.Production.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.0" />
<PackageReference Include="Serilog.Enrichers.Process" Version="2.0.2" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.2.2" />
<PackageReference Include="MessageBox.Avalonia" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Resources\Avalonia.Resources\Avalonia.Resources.vbproj" />
<ProjectReference Include="..\..\Background Services\RandomLogging.Service\RandomLogging.Service.csproj" />
<ProjectReference Include="..\..\Controls\LogViewer.Avalonia\LogViewer.Avalonia.csproj" />
<ProjectReference Include="..\..\Core\Common.Core\Common.Core.csproj" />
<ProjectReference Include="..\..\Core\Serilog.Sinks.LogView.Core\Serilog.Sinks.LogView.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,20 @@
using Avalonia;
using System;
namespace AvaloniaSerilogDI;
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
@@ -0,0 +1,19 @@
{
"profiles": {
"Development": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
},
"Staging": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Staging"
}
},
"Production": {
"commandName": "Project"
}
}
}
@@ -0,0 +1,27 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using AvaloniaSerilogDI.ViewModels;
using System;
namespace AvaloniaSerilogDI;
public class ViewLocator : IDataTemplate
{
public IControl Build(object data)
{
string name = data.GetType().FullName!.Replace("ViewModel", "View");
Type? type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object data)
{
return data is ViewModelBase;
}
}
@@ -0,0 +1,19 @@
using LogViewer.Core.ViewModels;
namespace AvaloniaSerilogDI.ViewModels;
public class MainViewModel : ViewModelBase
{
#region Constructor
public MainViewModel(LogViewerControlViewModel logViewer)
=> LogViewer = logViewer;
#endregion
#region Properties
public LogViewerControlViewModel LogViewer { get; }
#endregion
}
@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace AvaloniaSerilogDI.ViewModels;
public class ViewModelBase : ObservableObject
{
}
@@ -0,0 +1,19 @@
<Window x:Class="AvaloniaSerilogDI.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
xmlns:control="clr-namespace:LogViewer.Avalonia;assembly=LogViewer.Avalonia"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Title="C# AVALONIA SeriLog | LogViewer Control Example - Dot Net 7.0"
Icon="avares://Avalonia.Resources/Assets/avalonia-logo.ico"
WindowStartupLocation="CenterScreen" Height="634" Width="600">
<control:LogViewerControl DataContext="{Binding LogViewer}" />
</Window>
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace AvaloniaSerilogDI.Views;
public partial class MainWindow : Window
{
public MainWindow() => InitializeComponent();
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
@@ -0,0 +1,45 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System.Net.Http.HttpClient": "Trace"
}
},
"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"LevelSwitches": { "controlSwitch": "Verbose" },
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Verbose"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {EventId} | {Message:lj} {NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.txt",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"outputTemplate": "{Timestamp:G} {Message}{NewLine:1}{Exception:1}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.json",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"formatter": "Serilog.Formatting.Json.JsonFormatter"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ]
}
}
@@ -0,0 +1,46 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
},
"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"LevelSwitches": { "controlSwitch": "Warning" },
"MinimumLevel": {
"Default": "Warning",
"Override": {
"Microsoft": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {EventId.Name} | {Message:lj} {NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.txt",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"outputTemplate": "{Timestamp:G} {Message}{NewLine:1}{Exception:1}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.json",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"formatter": "Serilog.Formatting.Json.JsonFormatter"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ]
}
}
@@ -0,0 +1,46 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System.Net.Http.HttpClient": "Information"
}
},
"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"LevelSwitches": { "controlSwitch": "Information" },
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {EventId.Name} | {Message:lj} {NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.txt",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"outputTemplate": "{Timestamp:G} {Message}{NewLine:1}{Exception:1}"
}
},
{
"Name": "File",
"Args": {
"path": "c:\\WIP\\LogData\\log-.json",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"formatter": "Serilog.Formatting.Json.JsonFormatter"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ]
}
}