Add debug logging with Show Log button
- DebugLogger class saves logs to %TEMP%\CMM\debug.log - Show Log button appears after 3s if loading takes too long - Log viewer with Copy to Clipboard and Open Log File options - Detailed logging throughout LoadMonitors for debugging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
77
DellMonitorControl/DebugLogger.cs
Normal file
77
DellMonitorControl/DebugLogger.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace DellMonitorControl;
|
||||||
|
|
||||||
|
public static class DebugLogger
|
||||||
|
{
|
||||||
|
private static readonly List<string> _logs = new();
|
||||||
|
private static readonly object _lock = new();
|
||||||
|
private static readonly string _logFilePath;
|
||||||
|
|
||||||
|
static DebugLogger()
|
||||||
|
{
|
||||||
|
var tempPath = Path.Combine(Path.GetTempPath(), "CMM");
|
||||||
|
Directory.CreateDirectory(tempPath);
|
||||||
|
_logFilePath = Path.Combine(tempPath, "debug.log");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Log(string message)
|
||||||
|
{
|
||||||
|
var entry = $"[{DateTime.Now:HH:mm:ss.fff}] {message}";
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_logs.Add(entry);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.AppendAllText(_logFilePath, entry + Environment.NewLine);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogError(string message, Exception? ex = null)
|
||||||
|
{
|
||||||
|
var entry = ex != null
|
||||||
|
? $"[{DateTime.Now:HH:mm:ss.fff}] ERROR: {message} - {ex.GetType().Name}: {ex.Message}"
|
||||||
|
: $"[{DateTime.Now:HH:mm:ss.fff}] ERROR: {message}";
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_logs.Add(entry);
|
||||||
|
if (ex != null)
|
||||||
|
_logs.Add($" Stack: {ex.StackTrace}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.AppendAllText(_logFilePath, entry + Environment.NewLine);
|
||||||
|
if (ex != null)
|
||||||
|
File.AppendAllText(_logFilePath, $" Stack: {ex.StackTrace}" + Environment.NewLine);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetLogs()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return string.Join(Environment.NewLine, _logs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Clear()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_logs.Clear();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(_logFilePath, "");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LogFilePath => _logFilePath;
|
||||||
|
}
|
||||||
@@ -215,6 +215,9 @@
|
|||||||
</Ellipse>
|
</Ellipse>
|
||||||
<TextBlock Name="loadingText" Text="Loading..." Foreground="LightGray" FontSize="12"
|
<TextBlock Name="loadingText" Text="Loading..." Foreground="LightGray" FontSize="12"
|
||||||
HorizontalAlignment="Center"/>
|
HorizontalAlignment="Center"/>
|
||||||
|
<Button Name="showLogButton" Content="Show Log" Margin="0,10,0,0"
|
||||||
|
Style="{StaticResource DarkButton}" FontSize="10"
|
||||||
|
Click="ShowLogButton_Click" Visibility="Collapsed"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Windows;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
namespace DellMonitorControl;
|
namespace DellMonitorControl;
|
||||||
|
|
||||||
@@ -16,15 +17,18 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
private List<(XMonitor Monitor, List<InputSourceOption> Options)> _loadedMonitors = new();
|
private List<(XMonitor Monitor, List<InputSourceOption> Options)> _loadedMonitors = new();
|
||||||
private Storyboard? _spinnerStoryboard;
|
private Storyboard? _spinnerStoryboard;
|
||||||
|
private DispatcherTimer? _showLogButtonTimer;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_spinnerStoryboard = (Storyboard)FindResource("SpinnerAnimation");
|
_spinnerStoryboard = (Storyboard)FindResource("SpinnerAnimation");
|
||||||
|
DebugLogger.Log("MainWindow initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowNearTray()
|
public void ShowNearTray()
|
||||||
{
|
{
|
||||||
|
DebugLogger.Log("ShowNearTray called");
|
||||||
var workArea = SystemParameters.WorkArea;
|
var workArea = SystemParameters.WorkArea;
|
||||||
Left = workArea.Right - Width - 10;
|
Left = workArea.Right - Width - 10;
|
||||||
Top = workArea.Bottom - 350;
|
Top = workArea.Bottom - 350;
|
||||||
@@ -34,10 +38,25 @@ public partial class MainWindow : Window
|
|||||||
// Start spinner
|
// Start spinner
|
||||||
_spinnerStoryboard?.Begin(this, true);
|
_spinnerStoryboard?.Begin(this, true);
|
||||||
|
|
||||||
|
// Show log button after 3 seconds if still loading
|
||||||
|
showLogButton.Visibility = Visibility.Collapsed;
|
||||||
|
_showLogButtonTimer?.Stop();
|
||||||
|
_showLogButtonTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(3) };
|
||||||
|
_showLogButtonTimer.Tick += (s, e) =>
|
||||||
|
{
|
||||||
|
_showLogButtonTimer.Stop();
|
||||||
|
if (loadingPanel.Visibility == Visibility.Visible || sp.Children.Contains(loadingPanel))
|
||||||
|
{
|
||||||
|
showLogButton.Visibility = Visibility.Visible;
|
||||||
|
DebugLogger.Log("Show Log button displayed (loading took > 3s)");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_showLogButtonTimer.Start();
|
||||||
|
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
{
|
{
|
||||||
Top = workArea.Bottom - ActualHeight - 10;
|
Top = workArea.Bottom - ActualHeight - 10;
|
||||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
}), DispatcherPriority.Loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Deactivated(object sender, EventArgs e)
|
private void Window_Deactivated(object sender, EventArgs e)
|
||||||
@@ -51,18 +70,86 @@ public partial class MainWindow : Window
|
|||||||
Application.Current.Shutdown();
|
Application.Current.Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShowLogButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var logWindow = new Window
|
||||||
|
{
|
||||||
|
Title = "Debug Log",
|
||||||
|
Width = 600,
|
||||||
|
Height = 400,
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||||
|
Background = new SolidColorBrush(Color.FromRgb(45, 45, 45))
|
||||||
|
};
|
||||||
|
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||||
|
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||||
|
|
||||||
|
var textBox = new TextBox
|
||||||
|
{
|
||||||
|
Text = DebugLogger.GetLogs(),
|
||||||
|
IsReadOnly = true,
|
||||||
|
Background = new SolidColorBrush(Color.FromRgb(30, 30, 30)),
|
||||||
|
Foreground = Brushes.LightGray,
|
||||||
|
FontFamily = new FontFamily("Consolas"),
|
||||||
|
FontSize = 11,
|
||||||
|
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||||
|
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||||
|
TextWrapping = TextWrapping.NoWrap,
|
||||||
|
Margin = new Thickness(10)
|
||||||
|
};
|
||||||
|
Grid.SetRow(textBox, 0);
|
||||||
|
grid.Children.Add(textBox);
|
||||||
|
|
||||||
|
var buttonPanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(10) };
|
||||||
|
|
||||||
|
var copyBtn = new Button { Content = "Copy to Clipboard", Padding = new Thickness(10, 5, 10, 5), Margin = new Thickness(0, 0, 10, 0) };
|
||||||
|
copyBtn.Click += (s, args) =>
|
||||||
|
{
|
||||||
|
Clipboard.SetText(DebugLogger.GetLogs());
|
||||||
|
MessageBox.Show("Log copied to clipboard!", "Debug Log", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
};
|
||||||
|
buttonPanel.Children.Add(copyBtn);
|
||||||
|
|
||||||
|
var openFileBtn = new Button { Content = "Open Log File", Padding = new Thickness(10, 5, 10, 5), Margin = new Thickness(0, 0, 10, 0) };
|
||||||
|
openFileBtn.Click += (s, args) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{DebugLogger.LogFilePath}\"");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
};
|
||||||
|
buttonPanel.Children.Add(openFileBtn);
|
||||||
|
|
||||||
|
var closeBtn = new Button { Content = "Close", Padding = new Thickness(10, 5, 10, 5) };
|
||||||
|
closeBtn.Click += (s, args) => logWindow.Close();
|
||||||
|
buttonPanel.Children.Add(closeBtn);
|
||||||
|
|
||||||
|
Grid.SetRow(buttonPanel, 1);
|
||||||
|
grid.Children.Add(buttonPanel);
|
||||||
|
|
||||||
|
logWindow.Content = grid;
|
||||||
|
logWindow.Show();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task LoadMonitors()
|
public async Task LoadMonitors()
|
||||||
{
|
{
|
||||||
|
DebugLogger.Log("LoadMonitors started");
|
||||||
var newChildren = new List<UIElement>();
|
var newChildren = new List<UIElement>();
|
||||||
_loadedMonitors.Clear();
|
_loadedMonitors.Clear();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
DebugLogger.Log("Scanning for monitors...");
|
||||||
await CMMCommand.ScanMonitor();
|
await CMMCommand.ScanMonitor();
|
||||||
|
DebugLogger.Log("Scan complete, reading monitor data...");
|
||||||
var monitors = (await CMMCommand.ReadMonitorsData()).ToList();
|
var monitors = (await CMMCommand.ReadMonitorsData()).ToList();
|
||||||
|
DebugLogger.Log($"Found {monitors.Count} monitor(s)");
|
||||||
|
|
||||||
if (!monitors.Any())
|
if (!monitors.Any())
|
||||||
{
|
{
|
||||||
|
DebugLogger.Log("No DDC/CI monitors detected");
|
||||||
newChildren.Add(new TextBlock
|
newChildren.Add(new TextBlock
|
||||||
{
|
{
|
||||||
Text = "No DDC/CI monitors detected",
|
Text = "No DDC/CI monitors detected",
|
||||||
@@ -76,16 +163,33 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
foreach (var m in monitors)
|
foreach (var m in monitors)
|
||||||
{
|
{
|
||||||
|
DebugLogger.Log($"Processing monitor: {m.MonitorName} (SN: {m.SerialNumber})");
|
||||||
|
|
||||||
|
DebugLogger.Log($" Getting brightness...");
|
||||||
var brightness = await CMMCommand.GetBrightness(m.SerialNumber) ?? 50;
|
var brightness = await CMMCommand.GetBrightness(m.SerialNumber) ?? 50;
|
||||||
|
DebugLogger.Log($" Brightness: {brightness}");
|
||||||
|
|
||||||
|
DebugLogger.Log($" Getting contrast...");
|
||||||
var contrast = await CMMCommand.GetContrast(m.SerialNumber) ?? 50;
|
var contrast = await CMMCommand.GetContrast(m.SerialNumber) ?? 50;
|
||||||
|
DebugLogger.Log($" Contrast: {contrast}");
|
||||||
|
|
||||||
|
DebugLogger.Log($" Getting input source...");
|
||||||
var inputSource = await CMMCommand.GetInputSource(m.SerialNumber);
|
var inputSource = await CMMCommand.GetInputSource(m.SerialNumber);
|
||||||
|
DebugLogger.Log($" Input source: {inputSource}");
|
||||||
|
|
||||||
|
DebugLogger.Log($" Getting input options...");
|
||||||
var inputOptions = await CMMCommand.GetInputSourceOptions(m.SerialNumber);
|
var inputOptions = await CMMCommand.GetInputSourceOptions(m.SerialNumber);
|
||||||
|
DebugLogger.Log($" Input options count: {inputOptions.Count}");
|
||||||
|
|
||||||
|
DebugLogger.Log($" Getting power status...");
|
||||||
var powerStatus = await CMMCommand.GetMonPowerStatus(m.SerialNumber) ?? "Unknown";
|
var powerStatus = await CMMCommand.GetMonPowerStatus(m.SerialNumber) ?? "Unknown";
|
||||||
|
DebugLogger.Log($" Power status: {powerStatus}");
|
||||||
|
|
||||||
_loadedMonitors.Add((m, inputOptions));
|
_loadedMonitors.Add((m, inputOptions));
|
||||||
|
|
||||||
// Apply config to filter hidden ports and use custom labels
|
// Apply config to filter hidden ports and use custom labels
|
||||||
var filteredOptions = MonitorConfigManager.ApplyConfigToOptions(m.SerialNumber, inputOptions);
|
var filteredOptions = MonitorConfigManager.ApplyConfigToOptions(m.SerialNumber, inputOptions);
|
||||||
|
DebugLogger.Log($" Filtered options count: {filteredOptions.Count}");
|
||||||
|
|
||||||
// Monitor name header with Config button
|
// Monitor name header with Config button
|
||||||
newChildren.Add(CreateMonitorHeader(m, inputOptions));
|
newChildren.Add(CreateMonitorHeader(m, inputOptions));
|
||||||
@@ -100,6 +204,8 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
// Separator
|
// Separator
|
||||||
newChildren.Add(new Border { Height = 1, Background = new SolidColorBrush(Color.FromRgb(80, 80, 80)), Margin = new Thickness(0, 10, 0, 2) });
|
newChildren.Add(new Border { Height = 1, Background = new SolidColorBrush(Color.FromRgb(80, 80, 80)), Margin = new Thickness(0, 10, 0, 2) });
|
||||||
|
|
||||||
|
DebugLogger.Log($" Monitor {m.MonitorName} processing complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove last separator
|
// Remove last separator
|
||||||
@@ -108,27 +214,33 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load quick-switch toolbar
|
// Load quick-switch toolbar
|
||||||
|
DebugLogger.Log("Loading quick-switch toolbar...");
|
||||||
LoadQuickSwitchToolbar();
|
LoadQuickSwitchToolbar();
|
||||||
|
DebugLogger.Log("Quick-switch toolbar loaded");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
DebugLogger.LogError("LoadMonitors failed", ex);
|
||||||
newChildren.Clear();
|
newChildren.Clear();
|
||||||
newChildren.Add(new TextBlock { Text = $"Error: {ex.Message}", Foreground = Brushes.OrangeRed, FontSize = 11, TextWrapping = TextWrapping.Wrap });
|
newChildren.Add(new TextBlock { Text = $"Error: {ex.Message}", Foreground = Brushes.OrangeRed, FontSize = 11, TextWrapping = TextWrapping.Wrap });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop spinner
|
// Stop spinner and timer
|
||||||
_spinnerStoryboard?.Stop(this);
|
_spinnerStoryboard?.Stop(this);
|
||||||
|
_showLogButtonTimer?.Stop();
|
||||||
|
|
||||||
sp.VerticalAlignment = VerticalAlignment.Top;
|
sp.VerticalAlignment = VerticalAlignment.Top;
|
||||||
sp.Children.Clear();
|
sp.Children.Clear();
|
||||||
foreach (var child in newChildren)
|
foreach (var child in newChildren)
|
||||||
sp.Children.Add(child);
|
sp.Children.Add(child);
|
||||||
|
|
||||||
|
DebugLogger.Log("LoadMonitors complete, UI updated");
|
||||||
|
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
{
|
{
|
||||||
var workArea = SystemParameters.WorkArea;
|
var workArea = SystemParameters.WorkArea;
|
||||||
Top = workArea.Bottom - ActualHeight - 10;
|
Top = workArea.Bottom - ActualHeight - 10;
|
||||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
}), DispatcherPriority.Loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadQuickSwitchToolbar()
|
private void LoadQuickSwitchToolbar()
|
||||||
|
|||||||
Reference in New Issue
Block a user