[FancyZones Editor] Logger (#13928)

This commit is contained in:
Seraphima Zykova 2021-10-25 10:56:00 +03:00 committed by GitHub
parent a93dc423f0
commit 91ed50d993
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 193 additions and 206 deletions

View File

@ -3,16 +3,12 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using FancyZonesEditor.Logs;
using FancyZonesEditor.Utils;
using ManagedCommon;
using Microsoft.PowerToys.Common.UI;
@ -25,26 +21,7 @@ namespace FancyZonesEditor
public partial class App : Application, IDisposable
{
// Non-localizable strings
private const string CrashReportLogFile = "FZEditorCrashLog.txt";
private const string ErrorReportLogFile = "FZEditorErrorLog.txt";
private const string PowerToysIssuesURL = "https://aka.ms/powerToysReportBug";
private const string CrashReportExceptionTag = "Exception";
private const string CrashReportSourceTag = "Source: ";
private const string CrashReportTargetAssemblyTag = "TargetAssembly: ";
private const string CrashReportTargetModuleTag = "TargetModule: ";
private const string CrashReportTargetSiteTag = "TargetSite: ";
private const string CrashReportEnvironmentTag = "Environment";
private const string CrashReportCommandLineTag = "* Command Line: ";
private const string CrashReportTimestampTag = "* Timestamp: ";
private const string CrashReportOSVersionTag = "* OS Version: ";
private const string CrashReportIntPtrLengthTag = "* IntPtr Length: ";
private const string CrashReportx64Tag = "* x64: ";
private const string CrashReportCLRVersionTag = "* CLR Version: ";
private const string CrashReportAssembliesTag = "Assemblies - ";
private const string CrashReportDynamicAssemblyTag = "dynamic assembly doesn't have location";
private const string CrashReportLocationNullTag = "location is null or empty";
private const string ParsingErrorReportTag = "Settings parsing error";
private const string ParsingErrorDataTag = "Data: ";
@ -89,9 +66,12 @@ namespace FancyZonesEditor
_eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, interop.Constants.FZEExitEvent());
if (_eventHandle.WaitOne())
{
Logger.LogInfo("FancyZones disabled, exit");
Environment.Exit(0);
}
}).Start();
Logger.LogInfo("FancyZones Editor started");
}
private void OnStartup(object sender, StartupEventArgs e)
@ -100,6 +80,7 @@ namespace FancyZonesEditor
RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () =>
{
Logger.LogInfo("Runner exited");
Environment.Exit(0);
});
@ -139,15 +120,7 @@ namespace FancyZonesEditor
// Error message if parsing failed
if (!parseResult.Result)
{
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("## " + ParsingErrorReportTag);
sb.AppendLine();
sb.AppendLine(parseResult.Message);
sb.AppendLine();
sb.AppendLine(ParsingErrorDataTag);
sb.AppendLine(parseResult.MalformedData);
Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData);
MessageBox.Show(parseResult.Message, FancyZonesEditor.Properties.Resources.Error_Parsing_Zones_Settings_Title, MessageBoxButton.OK);
}
@ -163,6 +136,8 @@ namespace FancyZonesEditor
{
_eventHandle.Set();
}
Logger.LogInfo("FancyZones Editor exited");
}
public void App_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
@ -197,126 +172,20 @@ namespace FancyZonesEditor
MessageBox.Show(fullMessage, FancyZonesEditor.Properties.Resources.Error_Exception_Message_Box_Title);
}
public static void ShowExceptionReportMessageBox(string reportData)
{
var fileStream = File.OpenWrite(ErrorReportLogFile);
using (var sw = new StreamWriter(fileStream))
{
sw.Write(reportData);
sw.Flush();
}
fileStream.Close();
ShowReportMessageBox(fileStream.Name);
}
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
{
var fileStream = File.OpenWrite(CrashReportLogFile);
using (var sw = new StreamWriter(fileStream))
{
sw.Write(FormatException((Exception)args.ExceptionObject));
}
fileStream.Close();
ShowReportMessageBox(fileStream.Name);
Logger.LogError("Unhandled exception", (Exception)args.ExceptionObject);
ShowReportMessageBox();
}
private static void ShowReportMessageBox(string fileName)
private static void ShowReportMessageBox()
{
MessageBox.Show(
FancyZonesEditor.Properties.Resources.Crash_Report_Message_Box_Text_Part1 +
Path.GetFullPath(fileName) +
"\n" +
FancyZonesEditor.Properties.Resources.Crash_Report_Message_Box_Text_Part2 +
FancyZonesEditor.Properties.Resources.Crash_Report_Message_Box_Text +
PowerToysIssuesURL,
FancyZonesEditor.Properties.Resources.Fancy_Zones_Editor_App_Title);
}
private static string FormatException(Exception ex)
{
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("## " + CrashReportExceptionTag);
sb.AppendLine();
sb.AppendLine("```");
var exlist = new List<StringBuilder>();
while (ex != null)
{
var exsb = new StringBuilder();
exsb.Append(ex.GetType().FullName);
exsb.Append(": ");
exsb.AppendLine(ex.Message);
if (ex.Source != null)
{
exsb.Append(" " + CrashReportSourceTag);
exsb.AppendLine(ex.Source);
}
if (ex.TargetSite != null)
{
exsb.Append(" " + CrashReportTargetAssemblyTag);
exsb.AppendLine(ex.TargetSite.Module.Assembly.ToString());
exsb.Append(" " + CrashReportTargetModuleTag);
exsb.AppendLine(ex.TargetSite.Module.ToString());
exsb.Append(" " + CrashReportTargetSiteTag);
exsb.AppendLine(ex.TargetSite.ToString());
}
exsb.AppendLine(ex.StackTrace);
exlist.Add(exsb);
ex = ex.InnerException;
}
foreach (var result in exlist.Select(o => o.ToString()).Reverse())
{
sb.AppendLine(result);
}
sb.AppendLine("```");
sb.AppendLine();
sb.AppendLine("## " + CrashReportEnvironmentTag);
sb.AppendLine(CrashReportCommandLineTag + Environment.CommandLine);
// Using InvariantCulture since this is used for a timestamp internally
sb.AppendLine(CrashReportTimestampTag + DateTime.Now.ToString(CultureInfo.InvariantCulture));
sb.AppendLine(CrashReportOSVersionTag + Environment.OSVersion.VersionString);
sb.AppendLine(CrashReportIntPtrLengthTag + IntPtr.Size);
sb.AppendLine(CrashReportx64Tag + Environment.Is64BitOperatingSystem);
sb.AppendLine(CrashReportCLRVersionTag + Environment.Version);
sb.AppendLine("## " + CrashReportAssembliesTag + AppDomain.CurrentDomain.FriendlyName);
sb.AppendLine();
foreach (var ass in AppDomain.CurrentDomain.GetAssemblies().OrderBy(o => o.GlobalAssemblyCache ? 50 : 0))
{
sb.Append("* ");
sb.Append(ass.FullName);
sb.Append(" (");
if (ass.IsDynamic)
{
sb.Append(CrashReportDynamicAssemblyTag);
}
else if (string.IsNullOrEmpty(ass.Location))
{
sb.Append(CrashReportLocationNullTag);
}
else
{
sb.Append(ass.Location);
}
sb.AppendLine(")");
}
return sb.ToString();
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
@ -329,6 +198,7 @@ namespace FancyZonesEditor
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_isDisposed = true;
Logger.LogInfo("FancyZones Editor disposed");
}
}

View File

@ -4,6 +4,7 @@
using System.Windows;
using System.Windows.Input;
using FancyZonesEditor.Logs;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
@ -34,11 +35,13 @@ namespace FancyZonesEditor
private void OnAddZone(object sender, RoutedEventArgs e)
{
Logger.LogInfo("Add zone");
_model.AddZone();
}
protected new void OnCancel(object sender, RoutedEventArgs e)
{
Logger.LogInfo("Cancel changes");
base.OnCancel(sender, e);
_stashedModel.RestoreTo(_model);
}

View File

@ -4,6 +4,7 @@
using System;
using System.Windows;
using FancyZonesEditor.Logs;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
@ -12,6 +13,7 @@ namespace FancyZonesEditor
{
protected void OnSaveApplyTemplate(object sender, RoutedEventArgs e)
{
Logger.LogTrace();
var mainEditor = App.Overlay;
if (mainEditor.CurrentDataContext is LayoutModel model)
{

View File

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using FancyZonesEditor.Logs;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
@ -417,6 +418,8 @@ namespace FancyZonesEditor
public void DoMerge(List<int> indices)
{
Logger.LogTrace();
if (indices.Count == 0)
{
return;
@ -455,6 +458,7 @@ namespace FancyZonesEditor
public void Split(int zoneIndex, int position, Orientation orientation)
{
Logger.LogTrace();
if (!CanSplit(zoneIndex, position, orientation))
{
return;
@ -519,6 +523,8 @@ namespace FancyZonesEditor
public void Drag(int resizerIndex, int delta)
{
Logger.LogTrace();
if (!CanDrag(resizerIndex, delta))
{
return;

View File

@ -10,6 +10,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using FancyZonesEditor.Logs;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
@ -316,6 +317,8 @@ namespace FancyZonesEditor
private void OnSplit(object sender, SplitEventArgs args)
{
Logger.LogTrace();
MergeCancelClick(null, null);
var zonePanel = sender as GridZone;
@ -488,6 +491,7 @@ namespace FancyZonesEditor
private void OnMergeComplete(object o, MouseButtonEventArgs e)
{
Logger.LogTrace();
_inMergeDrag = false;
var selectedIndices = new List<int>();

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using FancyZonesEditor.Logs;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
@ -83,6 +84,11 @@ namespace FancyZonesEditor
{
_model = (LayoutModel)DataContext;
if (_model != null)
{
Logger.LogInfo("Loaded " + _model.Name);
}
RenderPreview();
}

View File

@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Reflection;
using interop;
namespace FancyZonesEditor.Logs
{
public static class Logger
{
private static readonly IFileSystem _fileSystem = new FileSystem();
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location).ProductVersion;
private static readonly string ApplicationLogPath = Path.Combine(Constants.AppDataPath(), "FancyZones\\Editor\\Logs\\", Version);
private static readonly string Error = "Error";
private static readonly string Warning = "Warning";
private static readonly string Info = "Info";
private static readonly string Debug = "Debug";
private static readonly string TraceFlag = "Trace";
static Logger()
{
if (!_fileSystem.Directory.Exists(ApplicationLogPath))
{
_fileSystem.Directory.CreateDirectory(ApplicationLogPath);
}
// Using InvariantCulture since this is used for a log file name
var logFilePath = _fileSystem.Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
Trace.AutoFlush = true;
}
public static void LogError(string message)
{
Log(message, Error);
}
public static void LogError(string message, Exception ex)
{
Log(
message + Environment.NewLine +
ex?.Message + Environment.NewLine +
"Inner exception: " + Environment.NewLine +
ex?.InnerException?.Message + Environment.NewLine +
"Stack trace: " + Environment.NewLine +
ex?.StackTrace,
Error);
}
public static void LogWarning(string message)
{
Log(message, Warning);
}
public static void LogInfo(string message)
{
Log(message, Info);
}
public static void LogDebug(string message)
{
Log(message, Debug);
}
public static void LogTrace()
{
Log(string.Empty, TraceFlag);
}
private static void Log(string message, string type)
{
Trace.WriteLine("[" + DateTime.Now.TimeOfDay + "] [" + type + "] " + GetCallerInfo());
Trace.Indent();
if (message != string.Empty)
{
Trace.WriteLine(message);
}
Trace.Unindent();
}
private static string GetCallerInfo()
{
StackTrace stackTrace = new StackTrace();
var methodName = stackTrace.GetFrame(3)?.GetMethod();
var className = methodName?.DeclaringType.Name;
return className + " :: " + methodName?.Name;
}
}
}

View File

@ -11,6 +11,7 @@ using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Input;
using FancyZonesEditor.Logs;
using FancyZonesEditor.Models;
using FancyZonesEditor.Utils;
using Microsoft.PowerToys.Common.UI;
@ -164,6 +165,8 @@ namespace FancyZonesEditor
private async void NewLayoutButton_Click(object sender, RoutedEventArgs e)
{
Logger.LogTrace();
if (_openedDialog != null)
{
// another dialog already opened
@ -195,6 +198,8 @@ namespace FancyZonesEditor
private void DuplicateLayout_Click(object sender, RoutedEventArgs e)
{
Logger.LogTrace();
EditLayoutDialog.Hide();
var mainEditor = App.Overlay;
@ -261,6 +266,7 @@ namespace FancyZonesEditor
private void Apply()
{
Logger.LogTrace();
var mainEditor = App.Overlay;
if (mainEditor.CurrentDataContext is LayoutModel model)
{
@ -272,6 +278,7 @@ namespace FancyZonesEditor
private void OnClosing(object sender, EventArgs e)
{
Logger.LogTrace();
CancelLayoutChanges();
App.FancyZonesEditorIO.SerializeZoneSettings();
@ -281,12 +288,15 @@ namespace FancyZonesEditor
private void DeleteLayout_Click(object sender, RoutedEventArgs e)
{
Logger.LogTrace();
EditLayoutDialog.Hide();
DeleteLayout((FrameworkElement)sender);
}
private async void EditLayout_Click(object sender, RoutedEventArgs e)
{
Logger.LogTrace();
// Avoid trying to open the same dialog twice.
if (_openedDialog != null)
{
@ -310,6 +320,7 @@ namespace FancyZonesEditor
private void EditZones_Click(object sender, RoutedEventArgs e)
{
Logger.LogTrace();
var dataContext = ((FrameworkElement)sender).DataContext;
Select((LayoutModel)dataContext);
EditLayoutDialog.Hide();
@ -342,6 +353,8 @@ namespace FancyZonesEditor
private void NewLayoutDialog_PrimaryButtonClick(ModernWpf.Controls.ContentDialog sender, ModernWpf.Controls.ContentDialogButtonClickEventArgs args)
{
Logger.LogTrace();
LayoutModel selectedLayoutModel;
if (GridLayoutRadioButton.IsChecked == true)
@ -393,6 +406,8 @@ namespace FancyZonesEditor
// EditLayout: Save changes
private void EditLayoutDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
Logger.LogTrace();
var mainEditor = App.Overlay;
if (!(mainEditor.CurrentDataContext is LayoutModel model))
{
@ -415,6 +430,8 @@ namespace FancyZonesEditor
private async void DeleteLayout(FrameworkElement element)
{
Logger.LogTrace();
var dialog = new ContentDialog()
{
Title = Properties.Resources.Are_You_Sure,

View File

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using FancyZonesEditor.Logs;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
@ -134,6 +135,8 @@ namespace FancyZonesEditor
public void Show()
{
Logger.LogTrace();
var mainWindowSettings = ((App)Application.Current).MainWindowSettings;
if (_layoutPreview != null)
{
@ -154,6 +157,8 @@ namespace FancyZonesEditor
public void ShowLayout()
{
Logger.LogTrace();
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
CurrentDataContext = settings.UpdateSelectedLayoutModel();
@ -201,6 +206,8 @@ namespace FancyZonesEditor
public void OpenEditor(LayoutModel model)
{
Logger.LogTrace();
_layoutPreview = null;
if (CurrentDataContext is GridLayoutModel)
{
@ -229,6 +236,8 @@ namespace FancyZonesEditor
public void CloseEditor()
{
Logger.LogTrace();
var mainWindowSettings = ((App)Application.Current).MainWindowSettings;
_editorLayout = null;

View File

@ -150,21 +150,12 @@ namespace FancyZonesEditor.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Error logged to .
/// </summary>
public static string Crash_Report_Message_Box_Text_Part1 {
get {
return ResourceManager.GetString("Crash_Report_Message_Box_Text_Part1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please report the bug to .
/// </summary>
public static string Crash_Report_Message_Box_Text_Part2 {
public static string Crash_Report_Message_Box_Text {
get {
return ResourceManager.GetString("Crash_Report_Message_Box_Text_Part2", resourceCulture);
return ResourceManager.GetString("Crash_Report_Message_Box_Text", resourceCulture);
}
}

View File

@ -135,10 +135,7 @@
<data name="Grid_Layout_Editor" xml:space="preserve">
<value>Grid layout editor</value>
</data>
<data name="Crash_Report_Message_Box_Text_Part1" xml:space="preserve">
<value>Error logged to </value>
</data>
<data name="Crash_Report_Message_Box_Text_Part2" xml:space="preserve">
<data name="Crash_Report_Message_Box_Text" xml:space="preserve">
<value>Please report the bug to </value>
</data>
<data name="Custom" xml:space="preserve">

View File

@ -11,6 +11,7 @@ using System.IO.Abstractions;
using System.Text;
using System.Text.Json;
using System.Windows;
using FancyZonesEditor.Logs;
using FancyZonesEditor.Models;
namespace FancyZonesEditor.Utils
@ -247,6 +248,8 @@ namespace FancyZonesEditor.Utils
// All strings in this function shouldn't be localized.
public static void ParseCommandLineArguments()
{
Logger.LogTrace();
string[] args = Environment.GetCommandLineArgs();
if (args.Length < 2 && !App.DebugMode)
@ -399,8 +402,9 @@ namespace FancyZonesEditor.Utils
App.Overlay.Monitors.Add(monitor);
}
}
catch (Exception)
catch (Exception ex)
{
Logger.LogError("Invalid command line arguments: " + args[1], ex);
MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title);
((App)Application.Current).Shutdown();
}
@ -408,6 +412,8 @@ namespace FancyZonesEditor.Utils
public ParsingResult ParseParams()
{
Logger.LogTrace();
if (_fileSystem.File.Exists(FancyZonesEditorParamsFile))
{
string data = string.Empty;
@ -471,6 +477,7 @@ namespace FancyZonesEditor.Utils
}
catch (Exception ex)
{
Logger.LogError("Editor params parsing error", ex);
return new ParsingResult(false, ex.Message, data);
}
@ -484,6 +491,8 @@ namespace FancyZonesEditor.Utils
public ParsingResult ParseZoneSettings()
{
Logger.LogTrace();
_unusedDevices.Clear();
if (_fileSystem.File.Exists(FancyZonesSettingsFile))
@ -498,6 +507,7 @@ namespace FancyZonesEditor.Utils
}
catch (Exception ex)
{
Logger.LogError("Zone settings parsing error", ex);
return new ParsingResult(false, ex.Message, settingsString);
}
@ -515,6 +525,7 @@ namespace FancyZonesEditor.Utils
}
catch (Exception ex)
{
Logger.LogError("Zone settings parsing error", ex);
return new ParsingResult(false, ex.Message, settingsString);
}
}
@ -524,6 +535,8 @@ namespace FancyZonesEditor.Utils
public void SerializeZoneSettings()
{
Logger.LogTrace();
ZoneSettingsWrapper zoneSettings = new ZoneSettingsWrapper { };
zoneSettings.Devices = new List<DeviceWrapper>();
zoneSettings.CustomZoneSets = new List<CustomLayoutWrapper>();
@ -680,8 +693,9 @@ namespace FancyZonesEditor.Utils
zoneSettings.QuickLayoutKeys.Add(wrapper);
}
catch (Exception)
catch (Exception ex)
{
Logger.LogError("Serialize quick layout keys error", ex);
}
}
}
@ -693,12 +707,15 @@ namespace FancyZonesEditor.Utils
}
catch (Exception ex)
{
Logger.LogError("Serialize zone settings error", ex);
App.ShowExceptionMessageBox(Properties.Resources.Error_Applying_Layout, ex);
}
}
private string ReadFile(string fileName)
{
Logger.LogTrace();
Stream inputStream = _fileSystem.File.Open(fileName, FileMode.Open);
using (StreamReader reader = new StreamReader(inputStream))
{
@ -710,6 +727,8 @@ namespace FancyZonesEditor.Utils
private bool SetDevices(List<DeviceWrapper> devices)
{
Logger.LogTrace();
if (devices == null)
{
return false;
@ -763,6 +782,8 @@ namespace FancyZonesEditor.Utils
private bool SetCustomLayouts(List<CustomLayoutWrapper> customLayouts)
{
Logger.LogTrace();
if (customLayouts == null)
{
return false;
@ -791,9 +812,10 @@ namespace FancyZonesEditor.Utils
layout = ParseGridInfo(zoneSet);
}
}
catch (Exception)
catch (Exception ex)
{
result = false;
Logger.LogError("Parse custom layout error", ex);
continue;
}
@ -813,6 +835,8 @@ namespace FancyZonesEditor.Utils
private bool SetTemplateLayouts(List<TemplateLayoutWrapper> templateLayouts)
{
Logger.LogTrace();
if (templateLayouts == null)
{
return false;
@ -840,6 +864,8 @@ namespace FancyZonesEditor.Utils
private bool SetQuickLayoutSwitchKeys(List<QuickLayoutKeysWrapper> quickSwitchKeys)
{
Logger.LogTrace();
if (quickSwitchKeys == null)
{
return false;
@ -971,50 +997,5 @@ namespace FancyZonesEditor.Utils
return string.Empty;
}
}
private static string ParsingCmdArgsErrorReport(string args, int count, string targetMonitorName, List<NativeMonitorData> monitorData, List<Monitor> monitors)
{
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("```");
sb.AppendLine(" ## Command-line arguments:");
sb.AppendLine();
sb.AppendLine(args);
sb.AppendLine();
sb.AppendLine("```");
sb.AppendLine(" ## Parsed command-line arguments:");
sb.AppendLine();
sb.Append("Span zones across monitors: ");
sb.AppendLine(App.Overlay.SpanZonesAcrossMonitors.ToString());
// using CultureInfo.InvariantCulture since this is for PowerToys team
sb.Append("Monitors count: ");
sb.AppendLine(count.ToString(CultureInfo.InvariantCulture));
sb.Append("Target monitor: ");
sb.AppendLine(targetMonitorName);
sb.AppendLine();
sb.AppendLine(" # Per monitor data:");
sb.AppendLine();
foreach (NativeMonitorData data in monitorData)
{
sb.AppendLine(data.ToString());
}
sb.AppendLine();
sb.AppendLine("```");
sb.AppendLine(" ## Monitors discovered:");
sb.AppendLine();
foreach (Monitor m in monitors)
{
sb.AppendLine(m.Device.ToString());
}
return sb.ToString();
}
}
}