From 23223fe0be6c6a07ccb3550068445e70256b29f9 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Mon, 8 Jun 2026 13:17:53 +0200 Subject: [PATCH] Add persistent log file and "Protokoll" viewer button Tray-Meldungen verschwinden - daher ein dauerhaftes Protokoll. - Logger: threadsicheres Datei-Log in %AppData%\StarfaceOutlookSync\sync.log mit Rotation bei 2 MB. - MainForm protokolliert Sync-Start, Ergebnis, Konflikte (lokal + von anderen Arbeitsplaetzen), Fehler und uebersprungene Laeufe (Sperre). - Neuer Button "Protokoll" oeffnet LogViewerForm (Aktualisieren/Leeren/Ordner oeffnen, scrollt ans Ende). - README/CHANGELOG aktualisiert. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 6 ++ README.md | 2 + src/StarfaceOutlookSync/Services/Logger.cs | 80 ++++++++++++++++++ src/StarfaceOutlookSync/UI/LogViewerForm.cs | 90 +++++++++++++++++++++ src/StarfaceOutlookSync/UI/MainForm.cs | 29 ++++++- 5 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 src/StarfaceOutlookSync/Services/Logger.cs create mode 100644 src/StarfaceOutlookSync/UI/LogViewerForm.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ffb4c0a..06ee6a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,12 @@ Versionsschema ist `x.x.x.x` (siehe `release.sh`). ### Hinzugefuegt +- **Protokoll (Logdatei) + "Protokoll"-Button.** Syncs, Ergebnisse, Konflikte + (lokal und von anderen Arbeitsplaetzen) und Fehler werden dauerhaft in + `%AppData%\StarfaceOutlookSync\sync.log` festgehalten - so ist auch nach dem + Verschwinden einer Tray-Meldung nachvollziehbar, wer/was/wann. Im Hauptfenster + oeffnet der Button "Protokoll" einen Betrachter (Aktualisieren/Leeren/Ordner + oeffnen). Die Datei rotiert bei 2 MB. - **Clientuebergreifende Konflikt-Hinweise (Mehrplatz).** Wird ein echter Feld-Konflikt aufgeloest, legt der Client eine Notiz im gemeinsamen Verzeichnis (`conflicts/`) ab - zugeordnet nach Kontakt (StarfaceId). Ein anderer diff --git a/README.md b/README.md index 81fd0cf0..f843f33f 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,8 @@ StarfaceOutlookSync.exe Alle Daten werden lokal pro Benutzer gespeichert: - `%AppData%\StarfaceOutlookSync\profiles.json` - Sync-Profile mit Zugangsdaten - `%AppData%\StarfaceOutlookSync\mappings\` - Kontakt-Zuordnungen pro Profil +- `%AppData%\StarfaceOutlookSync\sync.log` - Protokoll (Syncs, Konflikte, Fehler); + im Hauptfenster ueber den Button **Protokoll** einsehbar ### SSL-Zertifikate diff --git a/src/StarfaceOutlookSync/Services/Logger.cs b/src/StarfaceOutlookSync/Services/Logger.cs new file mode 100644 index 00000000..e0f3f9c4 --- /dev/null +++ b/src/StarfaceOutlookSync/Services/Logger.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; +using System.Text; + +namespace StarfaceOutlookSync.Services +{ + /// + /// Einfaches, threadsicheres Datei-Protokoll. Haelt fest, was bei Syncs + /// passiert (Ergebnisse, Konflikte, Fehler) - dauerhaft nachlesbar, auch + /// nachdem eine Tray-Meldung verschwunden ist. + /// + public static class Logger + { + private static readonly object _lock = new object(); + private const long MaxBytes = 2 * 1024 * 1024; // 2 MB, dann Rotation + + public static string LogFilePath { get; } = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "StarfaceOutlookSync", "sync.log"); + + public static void Log(string message) + { + try + { + lock (_lock) + { + Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath)); + Rotate(); + var line = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}{Environment.NewLine}"; + File.AppendAllText(LogFilePath, line, Encoding.UTF8); + } + } + catch { } + } + + private static void Rotate() + { + try + { + var fi = new FileInfo(LogFilePath); + if (fi.Exists && fi.Length > MaxBytes) + { + var bak = LogFilePath + ".1"; + if (File.Exists(bak)) File.Delete(bak); + File.Move(LogFilePath, bak); + } + } + catch { } + } + + public static string ReadAll() + { + try + { + lock (_lock) + { + if (!File.Exists(LogFilePath)) return ""; + using (var fs = new FileStream(LogFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var sr = new StreamReader(fs, Encoding.UTF8)) + return sr.ReadToEnd(); + } + } + catch { return ""; } + } + + public static void Clear() + { + try + { + lock (_lock) + { + if (File.Exists(LogFilePath)) File.Delete(LogFilePath); + var bak = LogFilePath + ".1"; + if (File.Exists(bak)) File.Delete(bak); + } + } + catch { } + } + } +} diff --git a/src/StarfaceOutlookSync/UI/LogViewerForm.cs b/src/StarfaceOutlookSync/UI/LogViewerForm.cs new file mode 100644 index 00000000..034c3760 --- /dev/null +++ b/src/StarfaceOutlookSync/UI/LogViewerForm.cs @@ -0,0 +1,90 @@ +using System; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Windows.Forms; +using StarfaceOutlookSync.Services; + +namespace StarfaceOutlookSync.UI +{ + /// Zeigt das Sync-Protokoll an (read-only) mit Aktualisieren/Leeren. + public class LogViewerForm : Form + { + private TextBox _txt; + private Button _btnClose; + private Panel _panel; + + public LogViewerForm() + { + Text = "Protokoll"; + Size = new Size(720, 500); + StartPosition = FormStartPosition.CenterParent; + Font = new Font("Segoe UI", 9); + + _txt = new TextBox + { + Multiline = true, + ReadOnly = true, + ScrollBars = ScrollBars.Both, + WordWrap = false, + Dock = DockStyle.Fill, + BackColor = Color.White, + Font = new Font("Consolas", 9) + }; + + _panel = new Panel { Dock = DockStyle.Bottom, Height = 44 }; + + var btnRefresh = new Button { Text = "Aktualisieren", Left = 10, Top = 8, Width = 100, Height = 28 }; + btnRefresh.Click += (s, e) => LoadLog(); + + var btnClear = new Button { Text = "Leeren", Left = 118, Top = 8, Width = 80, Height = 28 }; + btnClear.Click += (s, e) => + { + if (MessageBox.Show("Protokoll wirklich leeren?", "Protokoll", + MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) + { + Logger.Clear(); + LoadLog(); + } + }; + + var btnFolder = new Button { Text = "Ordner oeffnen", Left = 206, Top = 8, Width = 120, Height = 28 }; + btnFolder.Click += (s, e) => OpenFolder(); + + _btnClose = new Button + { + Text = "Schliessen", Top = 8, Width = 100, Height = 28, + Anchor = AnchorStyles.Top | AnchorStyles.Right + }; + _btnClose.Click += (s, e) => Close(); + + _panel.Controls.AddRange(new Control[] { btnRefresh, btnClear, btnFolder, _btnClose }); + + Controls.Add(_txt); + Controls.Add(_panel); + + Load += (s, e) => + { + _btnClose.Left = _panel.ClientSize.Width - _btnClose.Width - 10; + LoadLog(); + }; + } + + private void LoadLog() + { + _txt.Text = Logger.ReadAll(); + _txt.SelectionStart = _txt.TextLength; + _txt.ScrollToCaret(); + } + + private void OpenFolder() + { + try + { + var dir = Path.GetDirectoryName(Logger.LogFilePath); + Process.Start(new ProcessStartInfo { FileName = dir, UseShellExecute = true }); + } + catch { } + } + } +} diff --git a/src/StarfaceOutlookSync/UI/MainForm.cs b/src/StarfaceOutlookSync/UI/MainForm.cs index 466921a7..063df882 100644 --- a/src/StarfaceOutlookSync/UI/MainForm.cs +++ b/src/StarfaceOutlookSync/UI/MainForm.cs @@ -20,7 +20,7 @@ namespace StarfaceOutlookSync.UI private NotifyIcon _trayIcon; private ContextMenuStrip _trayMenu; private ListView _profileList; - private Button _btnNew, _btnEdit, _btnDelete, _btnSync, _btnReset, _btnSettings, _btnInfo; + private Button _btnNew, _btnEdit, _btnDelete, _btnSync, _btnReset, _btnSettings, _btnLog, _btnInfo; private StatusStrip _statusBar; private ToolStripStatusLabel _statusLabel; private Timer _autoSyncTimer; @@ -129,10 +129,13 @@ namespace StarfaceOutlookSync.UI _btnSettings = new Button { Text = "Einstellungen", Width = 95, Height = 30 }; _btnSettings.Click += (s, e) => ShowSettings(); + _btnLog = new Button { Text = "Protokoll", Width = 80, Height = 30 }; + _btnLog.Click += (s, e) => ShowLog(); + _btnInfo = new Button { Text = "Info", Width = 50, Height = 30 }; _btnInfo.Click += (s, e) => ShowAbout(); - buttonPanel.Controls.AddRange(new Control[] { _btnNew, _btnEdit, _btnDelete, _btnSync, _btnReset, _btnSettings, _btnInfo }); + buttonPanel.Controls.AddRange(new Control[] { _btnNew, _btnEdit, _btnDelete, _btnSync, _btnReset, _btnSettings, _btnLog, _btnInfo }); // Statusbar _statusBar = new StatusStrip(); @@ -358,9 +361,12 @@ namespace StarfaceOutlookSync.UI if (crossLock == null) { SetStatus("Anderer Arbeitsplatz synchronisiert gerade - uebersprungen."); + Logger.Log($"Sync '{profile.Name}' uebersprungen: anderer Arbeitsplatz synct gerade."); return; } + Logger.Log($"Sync gestartet: '{profile.Name}' (Richtung: {profile.SyncDirection})"); + // Konflikt-Hinweise von ANDEREN Arbeitsplaetzen anzeigen, BEVOR der // Sync den eigenen Stand auf den Gewinner-Wert aktualisiert (sonst // koennte man nicht mehr erkennen, ob man betroffen war). @@ -379,6 +385,10 @@ namespace StarfaceOutlookSync.UI _trayIcon.ShowBalloonTip(3000, "Starface Sync", msg, result.Errors > 0 ? ToolTipIcon.Warning : ToolTipIcon.Info); + Logger.Log($"Sync fertig: {msg}"); + foreach (var em in result.ErrorMessages) + Logger.Log($" Fehler: {em}"); + // Echte Feld-Konflikte gesondert melden, damit der Benutzer weiss, // dass ein Wert ueberschrieben wurde. if (result.Conflicts.Count > 0) @@ -389,6 +399,9 @@ namespace StarfaceOutlookSync.UI _trayIcon.ShowBalloonTip(10000, $"Konflikt bei {result.Conflicts.Count} Kontakt(en)", detail, ToolTipIcon.Warning); + foreach (var c in result.Conflicts) + Logger.Log($" KONFLIKT: {c}"); + // Andere Arbeitsplaetze ueber das gemeinsame Verzeichnis informieren. _conflictNotifier.Write(sharedDir, result.Conflicts); } @@ -400,6 +413,7 @@ namespace StarfaceOutlookSync.UI _trayIcon.ShowBalloonTip(3000, "Starface Sync Fehler", ex.Message, ToolTipIcon.Error); SetStatus($"Fehler: {ex.Message}"); + Logger.Log($"Sync FEHLER '{profile.Name}': {ex.Message}"); } finally { @@ -461,6 +475,9 @@ namespace StarfaceOutlookSync.UI var pending = _conflictNotifier.GetPending(sharedDir, MyContactsByStarfaceId()); if (pending.Count == 0) return; + foreach (var p in pending) + Logger.Log($" KONFLIKT (anderer Arbeitsplatz): {p}"); + var detail = string.Join("\n", pending.Take(5).Select(p => p.ToString())); if (pending.Count > 5) detail += $"\n... und {pending.Count - 5} weitere"; @@ -513,6 +530,14 @@ namespace StarfaceOutlookSync.UI } } + private void ShowLog() + { + using (var log = new LogViewerForm()) + { + log.ShowDialog(this); + } + } + private void ExitApplication() { _autoSyncTimer?.Stop();