diff --git a/ILSpy.Tests/Diagnostics/StartupPerfTests.cs b/ILSpy.Tests/Diagnostics/StartupPerfTests.cs index 14395d88b..dd004e008 100644 --- a/ILSpy.Tests/Diagnostics/StartupPerfTests.cs +++ b/ILSpy.Tests/Diagnostics/StartupPerfTests.cs @@ -138,7 +138,7 @@ public class StartupPerfTests // Surface the StartupLog elapsed (ms since process start) so the test output also // captures the big-picture timing alongside the per-phase deltas above. - StartupLog.Mark("StartupPerfTests benchmark completed"); + AppLog.Mark("StartupPerfTests benchmark completed"); } finally { diff --git a/ILSpy/App.axaml.cs b/ILSpy/App.axaml.cs index 89ec8c9c1..f2b3d5c70 100644 --- a/ILSpy/App.axaml.cs +++ b/ILSpy/App.axaml.cs @@ -45,7 +45,7 @@ namespace ILSpy public override void OnFrameworkInitializationCompleted() { - StartupLog.Mark("App.OnFrameworkInitializationCompleted entered"); + AppLog.Mark("App.OnFrameworkInitializationCompleted entered"); ILSpyTraceListener.Install(); GlobalExceptionHandler.Install(); @@ -70,7 +70,7 @@ namespace ILSpy try { - using var _ = StartupLog.Phase("AppComposition.Initialize"); + using var _ = AppLog.Phase("AppComposition.Initialize"); Composition = AppComposition.Initialize(); } catch (Exception ex) @@ -86,10 +86,10 @@ namespace ILSpy if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - StartupLog.Mark("MainWindow about to be resolved from MEF"); + AppLog.Mark("MainWindow about to be resolved from MEF"); desktop.MainWindow = Composition?.GetExport() ?? new MainWindow(); - StartupLog.Mark("MainWindow assigned to desktop.MainWindow"); + AppLog.Mark("MainWindow assigned to desktop.MainWindow"); desktop.Exit += (_, _) => { try { Composition?.GetExport().Save(); } diff --git a/ILSpy/AppEnv/AppLog.cs b/ILSpy/AppEnv/AppLog.cs new file mode 100644 index 000000000..2e1405e59 --- /dev/null +++ b/ILSpy/AppEnv/AppLog.cs @@ -0,0 +1,166 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.IO; + +namespace ILSpy.AppEnv +{ + /// + /// Categorized diagnostic log. Two surfaces: + /// + /// / — startup-timing helpers. Each line + /// gets a [startup +Nms] prefix where N is milliseconds since the first + /// reference (process-wide). The + /// category is default-enabled so these emit without any opt-in. + /// / — + /// general category-based logging. Off by default; enable with the + /// ILSPY_LOG=Cat1,Cat2 environment variable at process start, or + /// at runtime. + /// + /// Output goes to both (so DbgView / IDE + /// Debug Output captures it) and %TEMP%\ilspy-avalonia.log (truncated at + /// the first write of each process). Use the overload of + /// for messages that interpolate non- + /// trivial state — the factory is only invoked when the category is enabled, + /// keeping the call site zero-cost when off. + /// + public static class AppLog + { + /// + /// Well-known category names. Free-form strings work too — these constants exist + /// so call sites are typo-resistant and so adding a new category doesn't require + /// updating callers (just add a constant + start writing). + /// + public static class Category + { + /// Process startup timeline. Default on so / emit without env-var opt-in. + public const string Startup = "Startup"; + + /// Dock chrome activity — view-recycling cache, layout save/load, drag/drop transitions. + public const string Docking = "Docking"; + } + + static readonly Stopwatch sw = Stopwatch.StartNew(); + static readonly ConcurrentDictionary enabled = LoadFromEnvironment(); + static readonly object fileLock = new(); + static readonly string logFilePath = Path.Combine(Path.GetTempPath(), "ilspy-avalonia.log"); + static bool fileTruncated; + + /// Returns true if the given category is currently enabled. + public static bool IsEnabled(string category) + => enabled.TryGetValue(category, out var on) && on; + + /// Enables a category. Idempotent. + public static void Enable(string category) => enabled[category] = true; + + /// Disables a category. Idempotent. + public static void Disable(string category) => enabled[category] = false; + + /// + /// Writes a message under if enabled. Prefer the + /// overload for messages built with + /// string interpolation — this overload pays formatting cost at the call site + /// regardless of whether the category is on. + /// + public static void Write(string category, string message) + { + if (!IsEnabled(category)) + return; + EmitLine($"[{DateTime.Now:HH:mm:ss.fff}] [{category}] {message}"); + } + + /// + /// Writes a message under if enabled. The factory + /// delegate is only invoked when the category is on, so the caller's string + /// interpolation is zero-cost when off. + /// + public static void Write(string category, Func messageFactory) + { + if (!IsEnabled(category)) + return; + EmitLine($"[{DateTime.Now:HH:mm:ss.fff}] [{category}] {messageFactory()}"); + } + + /// + /// Emits a single startup-timing line: [startup +Nms] message, where N + /// is milliseconds since the process started. No-op if + /// has been disabled. + /// + public static void Mark(string message) + { + if (!IsEnabled(Category.Startup)) + return; + EmitLine($"[startup +{sw.ElapsedMilliseconds,5}ms] {message}"); + } + + /// + /// Brackets a scope with BEGIN/END startup markers carrying the duration spent + /// inside. Use with using var _ = AppLog.Phase("...");. No-op if + /// has been disabled (the returned disposable is + /// still safe to use; its Dispose is a no-op). + /// + public static IDisposable Phase(string name) + { + Mark($"BEGIN {name}"); + return new PhaseScope(name, Stopwatch.StartNew()); + } + + sealed class PhaseScope(string name, Stopwatch local) : IDisposable + { + public void Dispose() => Mark($"END {name} ({local.ElapsedMilliseconds}ms)"); + } + + static void EmitLine(string line) + { + Debug.WriteLine(line); + lock (fileLock) + { + try + { + if (!fileTruncated) + { + File.WriteAllText(logFilePath, $"[AppLog started {DateTime.Now:O}]" + Environment.NewLine); + fileTruncated = true; + } + File.AppendAllText(logFilePath, line + Environment.NewLine); + } + catch + { + // Logging failures must not bubble — diagnostic only. + } + } + } + + static ConcurrentDictionary LoadFromEnvironment() + { + var dict = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + // Startup category is default-on so the existing pattern of unconditional + // Mark/Phase calls in the startup path keeps working without env-var setup. + dict[Category.Startup] = true; + var env = Environment.GetEnvironmentVariable("ILSPY_LOG"); + if (string.IsNullOrWhiteSpace(env)) + return dict; + foreach (var raw in env.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + dict[raw] = true; + return dict; + } + } +} diff --git a/ILSpy/AppEnv/StartupLog.cs b/ILSpy/AppEnv/StartupLog.cs deleted file mode 100644 index 74037fe54..000000000 --- a/ILSpy/AppEnv/StartupLog.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Diagnostics; - -namespace ILSpy.AppEnv -{ - /// - /// Lightweight timing log for the startup path. Entries land on - /// so they show up in the IDE Debug Output - /// when running under a debugger; release builds elide them. Times are millisecond - /// elapsed since the first call (process-wide), so different log lines are directly - /// comparable. - /// - internal static class StartupLog - { - static readonly Stopwatch sw = Stopwatch.StartNew(); - - public static void Mark(string message) - => Debug.WriteLine($"[startup +{sw.ElapsedMilliseconds,5}ms] {message}"); - - /// - /// Brackets a scope with BEGIN/END markers carrying the duration spent inside. - /// Use with using var _ = StartupLog.Phase("...");. - /// - public static IDisposable Phase(string name) - { - Mark($"BEGIN {name}"); - var local = Stopwatch.StartNew(); - return new PhaseScope(name, local); - } - - sealed class PhaseScope(string name, Stopwatch local) : IDisposable - { - public void Dispose() => Mark($"END {name} ({local.ElapsedMilliseconds}ms)"); - } - } -} diff --git a/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs b/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs index a50e928fc..13b3d039f 100644 --- a/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs +++ b/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs @@ -51,11 +51,11 @@ namespace ILSpy.AssemblyTree public AssemblyListPane() { - AppEnv.StartupLog.Mark("AssemblyListPane ctor entered"); + AppEnv.AppLog.Mark("AssemblyListPane ctor entered"); InitializeComponent(); - AttachedToVisualTree += (_, _) => AppEnv.StartupLog.Mark("AssemblyListPane attached to visual tree"); + AttachedToVisualTree += (_, _) => AppEnv.AppLog.Mark("AssemblyListPane attached to visual tree"); Loaded += (_, _) => { - AppEnv.StartupLog.Mark("AssemblyListPane Loaded"); + AppEnv.AppLog.Mark("AssemblyListPane Loaded"); if (DataContext is AssemblyTreeModel m) m.MarkTreeReady(); }; @@ -64,7 +64,7 @@ namespace ILSpy.AssemblyTree // output appearing. void OnFirstRowLoaded(object? sender, DataGridRowEventArgs args) { - AppEnv.StartupLog.Mark("Assembly tree DataGrid: first row realised"); + AppEnv.AppLog.Mark("Assembly tree DataGrid: first row realised"); TreeGrid.LoadingRow -= OnFirstRowLoaded; } TreeGrid.LoadingRow += OnFirstRowLoaded; @@ -377,7 +377,6 @@ namespace ILSpy.AssemblyTree () => syncingSelection = false, DispatcherPriority.Background); - void OnTreeGridDoubleTapped(object? sender, TappedEventArgs e) { if (TreeGrid.HierarchicalModel is not IHierarchicalModel model) @@ -443,13 +442,13 @@ namespace ILSpy.AssemblyTree protected override void OnDataContextChanged(System.EventArgs e) { - using var _ = AppEnv.StartupLog.Phase("AssemblyListPane.OnDataContextChanged"); + using var _ = AppEnv.AppLog.Phase("AssemblyListPane.OnDataContextChanged"); base.OnDataContextChanged(e); if (DataContext is AssemblyTreeModel model) { model.PropertyChanged += Model_PropertyChanged; - AppEnv.StartupLog.Mark($"AssemblyListPane DataContext is AssemblyTreeModel; Root={(model.Root != null ? "set" : "null")}"); + AppEnv.AppLog.Mark($"AssemblyListPane DataContext is AssemblyTreeModel; Root={(model.Root != null ? "set" : "null")}"); if (model.Root != null) { BindTree(model.Root); @@ -491,7 +490,7 @@ namespace ILSpy.AssemblyTree void BindTree(SharpTreeNode root) { - using var _ = AppEnv.StartupLog.Phase("AssemblyListPane.BindTree"); + using var _ = AppEnv.AppLog.Phase("AssemblyListPane.BindTree"); var settings = languageSettings; var options = new HierarchicalOptions { ChildrenSelector = node => { diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs index b799f62cc..6b9518b90 100644 --- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -103,7 +103,7 @@ namespace ILSpy.AssemblyTree [ImportingConstructor] public AssemblyTreeModel(SettingsService settingsService, LanguageService languageService) { - AppEnv.StartupLog.Mark("AssemblyTreeModel ctor entered"); + AppEnv.AppLog.Mark("AssemblyTreeModel ctor entered"); this.settingsService = settingsService; this.languageService = languageService; languageService.PropertyChanged += (_, e) => { @@ -119,7 +119,7 @@ namespace ILSpy.AssemblyTree Id = PaneContentId; Title = "Assemblies"; CanClose = false; - AppEnv.StartupLog.Mark("AssemblyTreeModel ctor exited"); + AppEnv.AppLog.Mark("AssemblyTreeModel ctor exited"); } void OnNavigateToReference(object? sender, Util.NavigateToReferenceEventArgs e) @@ -197,14 +197,14 @@ namespace ILSpy.AssemblyTree internal void MarkTreeReady() { if (treeReadyTcs.TrySetResult(true)) - AppEnv.StartupLog.Mark("AssemblyTreeModel.TreeReady completed"); + AppEnv.AppLog.Mark("AssemblyTreeModel.TreeReady completed"); } public void Initialize() { - using var _ = AppEnv.StartupLog.Phase("AssemblyTreeModel.Initialize body"); + using var _ = AppEnv.AppLog.Phase("AssemblyTreeModel.Initialize body"); listManager = settingsService.AssemblyListManager; - using (AppEnv.StartupLog.Phase("CreateDefaultAssemblyLists")) + using (AppEnv.AppLog.Phase("CreateDefaultAssemblyLists")) listManager.CreateDefaultAssemblyLists(); SyncListNames(); @@ -244,7 +244,7 @@ namespace ILSpy.AssemblyTree async Task RestoreSelectedPathAsync(string[] path) { - using var _ = AppEnv.StartupLog.Phase($"RestoreSelectedPathAsync ({path.Length} segments)"); + using var _ = AppEnv.AppLog.Phase($"RestoreSelectedPathAsync ({path.Length} segments)"); try { if (Root == null) @@ -262,10 +262,10 @@ namespace ILSpy.AssemblyTree // Once the load completes the .GetAwaiter().GetResult() returns instantly. if (node is AssemblyTreeNode asm) { - using (AppEnv.StartupLog.Phase($"await GetLoadResultAsync ({asm.LoadedAssembly.ShortName})")) + using (AppEnv.AppLog.Phase($"await GetLoadResultAsync ({asm.LoadedAssembly.ShortName})")) await asm.LoadedAssembly.GetLoadResultAsync().ConfigureAwait(true); } - using (AppEnv.StartupLog.Phase($"EnsureLazyChildren ({node.GetType().Name} \"{element}\")")) + using (AppEnv.AppLog.Phase($"EnsureLazyChildren ({node.GetType().Name} \"{element}\")")) node.EnsureLazyChildren(); node = node.Children.FirstOrDefault(c => c.ToString() == element); } @@ -275,7 +275,7 @@ namespace ILSpy.AssemblyTree // BEFORE the assembly tree has rendered — a confusing reverse order. // The 5-second timeout is a safety net for environments where the pane // never loads (headless tests, design-time previews). - using (AppEnv.StartupLog.Phase("await TreeReady before SelectedItem assignment")) + using (AppEnv.AppLog.Phase("await TreeReady before SelectedItem assignment")) { await Task.WhenAny(TreeReady, Task.Delay(TimeSpan.FromSeconds(5))) .ConfigureAwait(true); @@ -294,7 +294,7 @@ namespace ILSpy.AssemblyTree if (listManager == null) return; AssemblyList list; - using (AppEnv.StartupLog.Phase($"LoadList({name})")) + using (AppEnv.AppLog.Phase($"LoadList({name})")) list = listManager.LoadList(name); if (AssemblyList == null || list.ListName != AssemblyList.ListName) ShowAssemblyList(list); @@ -302,7 +302,7 @@ namespace ILSpy.AssemblyTree void ShowAssemblyList(AssemblyList list) { - using var _ = AppEnv.StartupLog.Phase("ShowAssemblyList(list)"); + using var _ = AppEnv.AppLog.Phase("ShowAssemblyList(list)"); // Detach the previous list's collection-changed wiring so the MessageBus // republisher and the navigation-history pruning don't fire against a stale // list. Re-attach on the new list so panes (DockWorkspace, SearchPaneModel) @@ -314,14 +314,14 @@ namespace ILSpy.AssemblyTree list.CollectionChanged += OnActiveAssemblyListCollectionChanged; if (list.GetAssemblies().Length == 0 && list.ListName == AssemblyListManager.DefaultListName) { - using (AppEnv.StartupLog.Phase("LoadInitialAssemblies")) + using (AppEnv.AppLog.Phase("LoadInitialAssemblies")) LoadInitialAssemblies(list); } - AppEnv.StartupLog.Mark($"AssemblyList contains {list.GetAssemblies().Length} assemblies"); - using (AppEnv.StartupLog.Phase("new AssemblyListTreeNode")) + AppEnv.AppLog.Mark($"AssemblyList contains {list.GetAssemblies().Length} assemblies"); + using (AppEnv.AppLog.Phase("new AssemblyListTreeNode")) assemblyListTreeNode = new AssemblyListTreeNode(list); Root = assemblyListTreeNode; - AppEnv.StartupLog.Mark("Root assigned"); + AppEnv.AppLog.Mark("Root assigned"); ScheduleBackgroundLoadSweep(list); } @@ -349,7 +349,7 @@ namespace ILSpy.AssemblyTree // — kicking off 122 metadata loads the same frame the tree appears would // steal cycles from the layout pass that just brought it on screen. await Task.Delay(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false); - AppEnv.StartupLog.Mark("Background-load sweep starting"); + AppEnv.AppLog.Mark("Background-load sweep starting"); foreach (var assembly in list.GetAssemblies()) { // Calling GetLoadResultAsync triggers the Lazy creation. We don't @@ -357,7 +357,7 @@ namespace ILSpy.AssemblyTree // on the thread pool, just delayed past the active-assembly window. _ = assembly.GetLoadResultAsync(); } - AppEnv.StartupLog.Mark("Background-load sweep dispatched"); + AppEnv.AppLog.Mark("Background-load sweep dispatched"); } catch (Exception ex) { diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index bb4415dfb..edc5200dd 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -136,7 +136,7 @@ namespace ILSpy.Docking ToolPaneRegistry toolPaneRegistry, LanguageService languageService) { - ILSpy.AppEnv.StartupLog.Mark("DockWorkspace ctor entered"); + ILSpy.AppEnv.AppLog.Mark("DockWorkspace ctor entered"); this.assemblyTreeModel = assemblyTreeModel; this.languageService = languageService; NavigateBackCommand = new RelayCommand(NavigateBack, () => history.CanNavigateBack); @@ -144,7 +144,7 @@ namespace ILSpy.Docking NavigateToHistoryCommand = new RelayCommand(NavigateToHistory, entry => entry != null && (history.BackEntries.Contains(entry) || history.ForwardEntries.Contains(entry))); ShowSearchCommand = new RelayCommand(ExecuteShowSearch); - using (ILSpy.AppEnv.StartupLog.Phase("ILSpyDockFactory ctor + CreateLayout")) + using (ILSpy.AppEnv.AppLog.Phase("ILSpyDockFactory ctor + CreateLayout")) { factory = new ILSpyDockFactory(toolPaneRegistry); // Prefer the user's saved layout (ILSpy.Layout.json sidecar next to @@ -177,7 +177,7 @@ namespace ILSpy.Docking // next via the assembly tree. Mirrors WPF's DockWorkspace.CurrentAssemblyList_Changed. ILSpy.Util.MessageBus.Subscribers += OnAssemblyListChanged; - ILSpy.AppEnv.StartupLog.Mark("DockWorkspace ctor exited"); + ILSpy.AppEnv.AppLog.Mark("DockWorkspace ctor exited"); } void OnAssemblyListChanged(object? sender, ILSpy.Util.CurrentAssemblyListChangedEventArgs e) diff --git a/ILSpy/TextView/DecompilerTabPageModel.cs b/ILSpy/TextView/DecompilerTabPageModel.cs index fd705117d..d86727d8e 100644 --- a/ILSpy/TextView/DecompilerTabPageModel.cs +++ b/ILSpy/TextView/DecompilerTabPageModel.cs @@ -63,7 +63,7 @@ namespace ILSpy.TextView if (!string.IsNullOrEmpty(newValue) && System.Threading.Interlocked.Exchange(ref firstTextMarked, 1) == 0) { - ILSpy.AppEnv.StartupLog.Mark("DecompilerTabPageModel: first non-empty Text set"); + ILSpy.AppEnv.AppLog.Mark("DecompilerTabPageModel: first non-empty Text set"); } } diff --git a/ILSpy/ViewModels/MainWindowViewModel.cs b/ILSpy/ViewModels/MainWindowViewModel.cs index 9bec91c36..184db113f 100644 --- a/ILSpy/ViewModels/MainWindowViewModel.cs +++ b/ILSpy/ViewModels/MainWindowViewModel.cs @@ -57,7 +57,7 @@ namespace ILSpy.ViewModels SettingsService settingsService, UpdatePanelViewModel updatePanel) { - AppEnv.StartupLog.Mark("MainWindowViewModel ctor entered (deps already resolved)"); + AppEnv.AppLog.Mark("MainWindowViewModel ctor entered (deps already resolved)"); AssemblyTreeModel = assemblyTreeModel; LanguageService = languageService; DockWorkspace = dockWorkspace; @@ -68,7 +68,7 @@ namespace ILSpy.ViewModels // startup isn't blocked on the HTTP call. The panel stays hidden unless an // actual update is available. _ = updatePanel.CheckIfUpdatesAvailableAsync(); - AppEnv.StartupLog.Mark("MainWindowViewModel ctor exited"); + AppEnv.AppLog.Mark("MainWindowViewModel ctor exited"); } } } diff --git a/ILSpy/Views/MainWindow.axaml.cs b/ILSpy/Views/MainWindow.axaml.cs index 784ffb0fd..2720ee073 100644 --- a/ILSpy/Views/MainWindow.axaml.cs +++ b/ILSpy/Views/MainWindow.axaml.cs @@ -41,30 +41,30 @@ namespace ILSpy.Views // Layout.CanDrag, Layout.CanDrop, Layout.DockGroup template bindings) sees a // non-null source on first evaluation. Without that ordering, every binding // throws + logs at startup, which adds up to ~30 errors per launch. - StartupLog.Mark("MainWindow parameterless ctor entered (XAML inflation about to start)"); + AppLog.Mark("MainWindow parameterless ctor entered (XAML inflation about to start)"); InitializeComponent(); - StartupLog.Mark("MainWindow parameterless ctor exited (XAML inflation done)"); + AppLog.Mark("MainWindow parameterless ctor exited (XAML inflation done)"); } [ImportingConstructor] public MainWindow(MainWindowViewModel viewModel, SettingsService settingsService) { - StartupLog.Mark("MainWindow ctor entered"); + AppLog.Mark("MainWindow ctor entered"); this.settingsService = settingsService; DataContext = viewModel; - StartupLog.Mark("MainWindow XAML inflation about to start (DataContext set)"); + AppLog.Mark("MainWindow XAML inflation about to start (DataContext set)"); InitializeComponent(); - StartupLog.Mark("MainWindow XAML inflation done"); + AppLog.Mark("MainWindow XAML inflation done"); ApplySessionSettings(settingsService.SessionSettings); Opened += async (_, _) => { - StartupLog.Mark("MainWindow.Opened fired"); - using (StartupLog.Phase("AssemblyTreeModel.Initialize")) + AppLog.Mark("MainWindow.Opened fired"); + using (AppLog.Phase("AssemblyTreeModel.Initialize")) viewModel.AssemblyTreeModel.Initialize(); - StartupLog.Mark("MainWindow.Opened handler returning"); + AppLog.Mark("MainWindow.Opened handler returning"); if (App.CommandLineArguments is { } args) await viewModel.AssemblyTreeModel.HandleCommandLineArgumentsAsync(args); }; - StartupLog.Mark("MainWindow ctor exited"); + AppLog.Mark("MainWindow ctor exited"); } void ApplySessionSettings(SessionSettings session)