Browse Source

Merge StartupLog into a categorized AppLog

Generalises the existing internal StartupLog into a public AppLog with
named categories. The Startup category (default-on) keeps the existing
[startup +Nms] format and Mark/Phase API, so all 44 call sites get a
mechanical StartupLog. -> AppLog. rename without behavioural change.

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
d66c7d6b31
  1. 2
      ILSpy.Tests/Diagnostics/StartupPerfTests.cs
  2. 8
      ILSpy/App.axaml.cs
  3. 166
      ILSpy/AppEnv/AppLog.cs
  4. 54
      ILSpy/AppEnv/StartupLog.cs
  5. 15
      ILSpy/AssemblyTree/AssemblyListPane.axaml.cs
  6. 34
      ILSpy/AssemblyTree/AssemblyTreeModel.cs
  7. 6
      ILSpy/Docking/DockWorkspace.cs
  8. 2
      ILSpy/TextView/DecompilerTabPageModel.cs
  9. 4
      ILSpy/ViewModels/MainWindowViewModel.cs
  10. 18
      ILSpy/Views/MainWindow.axaml.cs

2
ILSpy.Tests/Diagnostics/StartupPerfTests.cs

@ -138,7 +138,7 @@ public class StartupPerfTests @@ -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
{

8
ILSpy/App.axaml.cs

@ -45,7 +45,7 @@ namespace ILSpy @@ -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 @@ -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 @@ -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<MainWindow>()
?? new MainWindow();
StartupLog.Mark("MainWindow assigned to desktop.MainWindow");
AppLog.Mark("MainWindow assigned to desktop.MainWindow");
desktop.Exit += (_, _) => {
try
{ Composition?.GetExport<SettingsService>().Save(); }

166
ILSpy/AppEnv/AppLog.cs

@ -0,0 +1,166 @@ @@ -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
{
/// <summary>
/// Categorized diagnostic log. Two surfaces:
/// <list type="bullet">
/// <item><see cref="Mark"/> / <see cref="Phase"/> — startup-timing helpers. Each line
/// gets a <c>[startup +Nms]</c> prefix where N is milliseconds since the first
/// <see cref="AppLog"/> reference (process-wide). The <see cref="Category.Startup"/>
/// category is default-enabled so these emit without any opt-in.</item>
/// <item><see cref="Write(string, string)"/> / <see cref="Write(string, Func{string})"/> —
/// general category-based logging. Off by default; enable with the
/// <c>ILSPY_LOG=Cat1,Cat2</c> environment variable at process start, or
/// <see cref="Enable(string)"/> at runtime.</item>
/// </list>
/// Output goes to both <see cref="Debug.WriteLine(string)"/> (so DbgView / IDE
/// Debug Output captures it) and <c>%TEMP%\ilspy-avalonia.log</c> (truncated at
/// the first write of each process). Use the <see cref="Func{T}"/> overload of
/// <see cref="Write(string, Func{string})"/> 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.
/// </summary>
public static class AppLog
{
/// <summary>
/// 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).
/// </summary>
public static class Category
{
/// <summary>Process startup timeline. Default on so <see cref="Mark"/> / <see cref="Phase"/> emit without env-var opt-in.</summary>
public const string Startup = "Startup";
/// <summary>Dock chrome activity — view-recycling cache, layout save/load, drag/drop transitions.</summary>
public const string Docking = "Docking";
}
static readonly Stopwatch sw = Stopwatch.StartNew();
static readonly ConcurrentDictionary<string, bool> enabled = LoadFromEnvironment();
static readonly object fileLock = new();
static readonly string logFilePath = Path.Combine(Path.GetTempPath(), "ilspy-avalonia.log");
static bool fileTruncated;
/// <summary>Returns true if the given category is currently enabled.</summary>
public static bool IsEnabled(string category)
=> enabled.TryGetValue(category, out var on) && on;
/// <summary>Enables a category. Idempotent.</summary>
public static void Enable(string category) => enabled[category] = true;
/// <summary>Disables a category. Idempotent.</summary>
public static void Disable(string category) => enabled[category] = false;
/// <summary>
/// Writes a message under <paramref name="category"/> if enabled. Prefer the
/// <see cref="Write(string, Func{string})"/> overload for messages built with
/// string interpolation — this overload pays formatting cost at the call site
/// regardless of whether the category is on.
/// </summary>
public static void Write(string category, string message)
{
if (!IsEnabled(category))
return;
EmitLine($"[{DateTime.Now:HH:mm:ss.fff}] [{category}] {message}");
}
/// <summary>
/// Writes a message under <paramref name="category"/> if enabled. The factory
/// delegate is only invoked when the category is on, so the caller's string
/// interpolation is zero-cost when off.
/// </summary>
public static void Write(string category, Func<string> messageFactory)
{
if (!IsEnabled(category))
return;
EmitLine($"[{DateTime.Now:HH:mm:ss.fff}] [{category}] {messageFactory()}");
}
/// <summary>
/// Emits a single startup-timing line: <c>[startup +Nms] message</c>, where N
/// is milliseconds since the process started. No-op if <see cref="Category.Startup"/>
/// has been disabled.
/// </summary>
public static void Mark(string message)
{
if (!IsEnabled(Category.Startup))
return;
EmitLine($"[startup +{sw.ElapsedMilliseconds,5}ms] {message}");
}
/// <summary>
/// Brackets a scope with BEGIN/END startup markers carrying the duration spent
/// inside. Use with <c>using var _ = AppLog.Phase("...");</c>. No-op if
/// <see cref="Category.Startup"/> has been disabled (the returned disposable is
/// still safe to use; its Dispose is a no-op).
/// </summary>
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<string, bool> LoadFromEnvironment()
{
var dict = new ConcurrentDictionary<string, bool>(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;
}
}
}

54
ILSpy/AppEnv/StartupLog.cs

@ -1,54 +0,0 @@ @@ -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
{
/// <summary>
/// Lightweight timing log for the startup path. Entries land on
/// <see cref="Debug.WriteLine(string)"/> 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.
/// </summary>
internal static class StartupLog
{
static readonly Stopwatch sw = Stopwatch.StartNew();
public static void Mark(string message)
=> Debug.WriteLine($"[startup +{sw.ElapsedMilliseconds,5}ms] {message}");
/// <summary>
/// Brackets a scope with BEGIN/END markers carrying the duration spent inside.
/// Use with <c>using var _ = StartupLog.Phase("...");</c>.
/// </summary>
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)");
}
}
}

15
ILSpy/AssemblyTree/AssemblyListPane.axaml.cs

@ -51,11 +51,11 @@ namespace ILSpy.AssemblyTree @@ -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 @@ -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 @@ -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 @@ -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 @@ -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<SharpTreeNode> {
ChildrenSelector = node => {

34
ILSpy/AssemblyTree/AssemblyTreeModel.cs

@ -103,7 +103,7 @@ namespace ILSpy.AssemblyTree @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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<Task> creation. We don't
@ -357,7 +357,7 @@ namespace ILSpy.AssemblyTree @@ -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)
{

6
ILSpy/Docking/DockWorkspace.cs

@ -136,7 +136,7 @@ namespace ILSpy.Docking @@ -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 @@ -144,7 +144,7 @@ namespace ILSpy.Docking
NavigateToHistoryCommand = new RelayCommand<NavigationEntry>(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 @@ -177,7 +177,7 @@ namespace ILSpy.Docking
// next via the assembly tree. Mirrors WPF's DockWorkspace.CurrentAssemblyList_Changed.
ILSpy.Util.MessageBus<ILSpy.Util.CurrentAssemblyListChangedEventArgs>.Subscribers
+= OnAssemblyListChanged;
ILSpy.AppEnv.StartupLog.Mark("DockWorkspace ctor exited");
ILSpy.AppEnv.AppLog.Mark("DockWorkspace ctor exited");
}
void OnAssemblyListChanged(object? sender, ILSpy.Util.CurrentAssemblyListChangedEventArgs e)

2
ILSpy/TextView/DecompilerTabPageModel.cs

@ -63,7 +63,7 @@ namespace ILSpy.TextView @@ -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");
}
}

4
ILSpy/ViewModels/MainWindowViewModel.cs

@ -57,7 +57,7 @@ namespace ILSpy.ViewModels @@ -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 @@ -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");
}
}
}

18
ILSpy/Views/MainWindow.axaml.cs

@ -41,30 +41,30 @@ namespace ILSpy.Views @@ -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)

Loading…
Cancel
Save