Fehlerbehandlung, Logging und feldbezogene Validierung (Kapitel 13.2)

Zentrale Exception-Behandlung (Dispatcher.UIThread.UnhandledException,
AppDomain, TaskScheduler) verhindert Abstürze und protokolliert Fehler
über AppLogger in eine rotierende Log-Datei im App-Datenverzeichnis.
Toast-Benachrichtigungen zeigen Erfolg/Fehler global an. Alle Dialoge
mit Formularfeldern zeigen Validierungsmeldungen jetzt direkt am
betroffenen Feld statt in einem Sammel-Label.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 12:38:30 +02:00
co-authored by Claude Sonnet 5
parent 273f459119
commit db1afff7e4
27 changed files with 578 additions and 102 deletions
+77
View File
@@ -0,0 +1,77 @@
namespace LehrerApp.Core.Services;
public enum LogLevel { Info, Warning, Error }
/// <summary>
/// Schreibt Log-Zeilen in eine tägliche Datei im App-Datenverzeichnis
/// (<c>&lt;AppData&gt;/LehrerApp/logs/lehrerapp-JJJJ-MM-TT.log</c>).
/// Beim Start werden Dateien entfernt, die älter als <see cref="RetentionDays"/> sind.
/// </summary>
public class AppLogger
{
private const int RetentionDays = 14;
private readonly object _lock = new();
private readonly string _logDirectory;
public string LogDirectory => _logDirectory;
public AppLogger(string appDataPath)
{
_logDirectory = Path.Combine(appDataPath, "logs");
Directory.CreateDirectory(_logDirectory);
RotateOldFiles();
}
public void Info(string message) => Write(LogLevel.Info, message, null);
public void Warn(string message) => Write(LogLevel.Warning, message, null);
public void Error(string message, Exception? exception = null) => Write(LogLevel.Error, message, exception);
private void Write(LogLevel level, string message, Exception? exception)
{
var levelLabel = level switch
{
LogLevel.Info => "INFO ",
LogLevel.Warning => "WARN ",
LogLevel.Error => "ERROR",
_ => "? ",
};
var line = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{levelLabel}] {message}";
if (exception is not null)
line += Environment.NewLine + exception;
lock (_lock)
{
try
{
File.AppendAllText(CurrentLogFile(), line + Environment.NewLine);
}
catch
{
// Logging darf die App nie zum Absturz bringen (z.B. Datenträger voll/gesperrt).
}
}
}
private string CurrentLogFile() =>
Path.Combine(_logDirectory, $"lehrerapp-{DateTime.Now:yyyy-MM-dd}.log");
private void RotateOldFiles()
{
var cutoff = DateTime.Now.AddDays(-RetentionDays);
try
{
foreach (var file in Directory.GetFiles(_logDirectory, "lehrerapp-*.log"))
{
if (File.GetLastWriteTime(file) < cutoff)
{
try { File.Delete(file); }
catch { /* nächster Start versucht es erneut */ }
}
}
}
catch
{
// Verzeichnis nicht lesbar o.ä. — Logging soll trotzdem weiterlaufen können.
}
}
}