Skip to content

separate the tracking parts of TL into a reusable base, so that TL becomes a 'renderer'#12587

Closed
baronfel wants to merge 1 commit intomainfrom
tl-logger-base
Closed

separate the tracking parts of TL into a reusable base, so that TL becomes a 'renderer'#12587
baronfel wants to merge 1 commit intomainfrom
tl-logger-base

Conversation

@baronfel
Copy link
Copy Markdown
Member

@baronfel baronfel commented Oct 1, 2025

This makes it easier to implement other Loggers - you just need to pick out what kinds of data you need from evaluation/projects, and then render warnings, errors, etc however you want during certain lifecycle events. The thought is that all of the data required to track/associate/trigger the lifecycle warnings is kept by the 'tracker', so your use-case-specific logger can mostly ignore that.

This is low-priority ATM, but something that may be useful for us as we think about how Loggers might need to change to more easily react to LLMs and other tools. For example, @richlander has a prototype logger that emits markdown - I'm pretty sure that logger doesn't understand the relationship between 'outer' builds and 'inner' like TL does. If it built on this base and then it could more easily get that understanding for free.

I kind of think of this interface as a step towards a more user-friendly Logger programming model - for loggers that care about rendering the 'intent' of the build and reshaping/papering over MSBuild specifics in favor of a more user-centric model this base can hide a lot of the guts.

@baronfel baronfel added the Area: Terminal Logger Problems with the livelogger/fancylogger/terminallogger -tl functionality. label Oct 1, 2025
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the TerminalLogger to extract common project tracking logic into a new base class ProjectTrackingLoggerBase. The goal is to enable reusability for other logger implementations that need to track build state.

Key changes:

  • Introduces ProjectTrackingLoggerBase<TEvalData, TNodeData, TProjectData, TBuildData> as a generic base class for loggers that track project state
  • Makes several internal types public to support extensibility: TerminalColor, TerminalMessageSeverity, TerminalBuildMessage, TerminalNodeStatus, TerminalProjectInfo, EvalProjectInfo, StopwatchAbstraction
  • Creates a new TerminalBuildData class to encapsulate build-level state
  • Refactors TerminalLogger to inherit from the base class and override virtual methods

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
src/Framework/Logging/TerminalColor.cs Changed enum from internal to public for API extensibility
src/Build/Microsoft.Build.csproj Added ProjectTrackingLoggerBase.cs to compilation
src/Build/Logging/TerminalLogger/TerminalProjectInfo.cs Removed ProjectContext parameter and Id property, simplified to use EvalProjectInfo directly
src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs Changed class from internal to public for API extensibility
src/Build/Logging/TerminalLogger/TerminalMessageSeverity.cs Changed enum from internal to public for API extensibility
src/Build/Logging/TerminalLogger/TerminalLogger.cs Refactored to inherit from ProjectTrackingLoggerBase and override virtual methods instead of implementing direct event handlers
src/Build/Logging/TerminalLogger/TerminalBuildMessage.cs Changed record from internal to public for API extensibility
src/Build/Logging/TerminalLogger/TerminalBuildData.cs New class to encapsulate build-level state (error counts, warnings, restore state)
src/Build/Logging/TerminalLogger/StopwatchAbstraction.cs Changed class from internal to public for API extensibility
src/Build/Logging/ProjectTrackingLoggerBase.cs New abstract base class providing common project tracking infrastructure for loggers

/// </summary>
private void BuildFinishedHandler(object sender, BuildFinishedEventArgs e)
{
OnBuildFinished(e, _projectDataByProjectContextId.Values.ToArray(), _buildData!);
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using LINQ's .ToArray() on dictionary values can cause unnecessary allocations. According to the coding guidelines, performance is critical and LINQ should be avoided. Consider using a manual array copy or passing the Values collection directly if the consumer can accept an ICollection<TProjectData> instead.

Copilot uses AI. Check for mistakes.
}
}

public void YieldNode(BuildEventArgs e)
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The YieldNode method is declared as public but appears to be an internal implementation detail. Consider making it private or protected to avoid exposing internal node management to external consumers.

Suggested change
public void YieldNode(BuildEventArgs e)
private void YieldNode(BuildEventArgs e)

Copilot uses AI. Check for mistakes.

#region Protected helpers

protected int? GetNodeIdForEvent(BuildEventArgs args) => args?.BuildEventContext is null ? null : NodeIndexForContext(args.BuildEventContext);
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method GetNodeIdForEvent returns a nullable int without documentation explaining that null indicates an invalid node, and doesn't validate that the computed index is within bounds. Callers like StartNode and YieldNode in TerminalLogger use this to access _nodes[nodeId] without bounds checking. Consider adding range validation or documenting the expected behavior.

Suggested change
protected int? GetNodeIdForEvent(BuildEventArgs args) => args?.BuildEventContext is null ? null : NodeIndexForContext(args.BuildEventContext);
/// <summary>
/// Returns the zero-based node index for the given event, or <c>null</c> if the event or its context is <c>null</c>
/// or if the node ID is not valid (less than or equal to zero).
/// Callers should check for <c>null</c> before using the result as an array index.
/// </summary>
/// <param name="args">The build event arguments.</param>
/// <returns>
/// The zero-based node index, or <c>null</c> if the event/context is <c>null</c> or the node ID is invalid.
/// </returns>
protected int? GetNodeIdForEvent(BuildEventArgs args)
{
var context = args?.BuildEventContext;
if (context is null || context.NodeId <= 0)
{
return null;
}
return NodeIndexForContext(context);
}

Copilot uses AI. Check for mistakes.
private TerminalNodeStatus? GetNodeForEvent(BuildEventArgs e)
{
var node = GetNodeIdForEvent(e);
if (node is int nodeId && _nodes[nodeId] is TerminalNodeStatus status)
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array access _nodes[nodeId] can throw IndexOutOfRangeException if nodeId is outside the valid range. While NodeIndexForContext subtracts 1 from context.NodeId, there's no validation that the result is within [0, NodeCount). Add bounds checking before array access to prevent runtime exceptions.

Suggested change
if (node is int nodeId && _nodes[nodeId] is TerminalNodeStatus status)
if (node is int nodeId
&& nodeId >= 0
&& nodeId < _nodes.Length
&& _nodes[nodeId] is TerminalNodeStatus status)

Copilot uses AI. Check for mistakes.
var node = GetNodeIdForEvent(e);
if (node is int nodeId)
{
_nodes[nodeId] = status;
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array access _nodes[nodeId] can throw IndexOutOfRangeException if nodeId is outside the valid range. While NodeIndexForContext subtracts 1 from context.NodeId, there's no validation that the result is within [0, NodeCount). Add bounds checking before array access to prevent runtime exceptions.

Copilot uses AI. Check for mistakes.
/// Number of build warnings.
/// </summary>
private int _buildWarningsCount;
// Tracking dictionaries and node array are now inherited from BuildTrackerLogger
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment references 'BuildTrackerLogger' but the actual base class name is 'ProjectTrackingLoggerBase'. This could confuse developers reading the code.

Copilot uses AI. Check for mistakes.
/// True if restore failed and this failure has already been reported.
/// </summary>
private bool _restoreFailed;
// BuildStartTime is now inherited from BuildTrackerLogger
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment references 'BuildTrackerLogger' but the actual base class name is 'ProjectTrackingLoggerBase'. This could confuse developers reading the code.

Suggested change
// BuildStartTime is now inherited from BuildTrackerLogger
// BuildStartTime is now inherited from ProjectTrackingLoggerBase

Copilot uses AI. Check for mistakes.
/// not restoring.
/// </summary>
private ProjectContext? _restoreContext;
// Build error/warning counts and restore state are now inherited from BuildTrackerLogger
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment references 'BuildTrackerLogger' but the actual base class name is 'ProjectTrackingLoggerBase'. This could confuse developers reading the code.

Copilot uses AI. Check for mistakes.

// Mark node idle until something uses it again
if (_restoreContext is null)
if (buildData.RestoreContext is null)
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable buildData may be null at this access as suggested by this null check.

Copilot uses AI. Check for mistakes.
Comment on lines +503 to +504
string duration = (e.Timestamp - buildData.BuildStartTime).TotalSeconds.ToString("F1");
string buildResult = GetBuildResultString(e.Succeeded, buildData.BuildErrorsCount, buildData.BuildWarningsCount);
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable buildData may be null at this access as suggested by this null check.

Suggested change
string duration = (e.Timestamp - buildData.BuildStartTime).TotalSeconds.ToString("F1");
string buildResult = GetBuildResultString(e.Succeeded, buildData.BuildErrorsCount, buildData.BuildWarningsCount);
string duration;
string buildResult;
if (buildData != null)
{
duration = (e.Timestamp - buildData.BuildStartTime).TotalSeconds.ToString("F1");
buildResult = GetBuildResultString(e.Succeeded, buildData.BuildErrorsCount, buildData.BuildWarningsCount);
}
else
{
duration = "0.0";
buildResult = GetBuildResultString(e.Succeeded, 0, 0);
}

Copilot uses AI. Check for mistakes.
@richlander
Copy link
Copy Markdown
Member

@copilot Please resolve the merge conflicts. There are apparently 21 separate conflicts.

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 29, 2026

@richlander I've opened a new pull request, #13138, to work on those changes. Once the pull request is ready, I'll request review from you.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 6, 2026

This pull request has been automatically closed because it has been open for more than 180 days with no recent activity.

If you believe this work is still relevant, please feel free to reopen or create a new pull request. Thank you for your contribution!

Generated by Close Stale Pull Requests

@github-actions github-actions Bot closed this Apr 6, 2026
@baronfel baronfel reopened this Apr 6, 2026
@github-actions
Copy link
Copy Markdown
Contributor

This pull request has been automatically closed because it has been open for more than 180 days with no recent activity.

If you believe this work is still relevant, please feel free to reopen or create a new pull request. Thank you for your contribution!

Note

🔒 Integrity filter blocked 39 items

The following items were blocked because they don't meet the GitHub integrity level.

  • #13528 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13527 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13523 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13516 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13511 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13495 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13488 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13486 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13480 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13477 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13474 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13458 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13457 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13442 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13429 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13428 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • ... and 23 more items

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by Close Stale Pull Requests · ● 844.8K

@github-actions github-actions Bot closed this Apr 13, 2026
@baronfel baronfel reopened this Apr 13, 2026
@jankratochvilcz jankratochvilcz marked this pull request as draft April 20, 2026 11:20
@github-actions
Copy link
Copy Markdown
Contributor

This pull request has been automatically closed because it has been open for more than 180 days with no recent activity.

If you believe this work is still relevant, please feel free to reopen or create a new pull request. Thank you for your contribution!

Note

🔒 Integrity filter blocked 43 items

The following items were blocked because they don't meet the GitHub integrity level.

  • #12231 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #12868 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #12991 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #12994 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13027 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13094 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13177 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13222 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13250 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13267 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13269 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13273 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13285 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13288 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13319 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13330 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • ... and 27 more items

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by Close Stale Pull Requests · ● 963.5K

@github-actions github-actions Bot closed this Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Terminal Logger Problems with the livelogger/fancylogger/terminallogger -tl functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants