Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 22 additions & 39 deletions src/Aspire.Cli/Commands/PipelineCommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -412,27 +412,14 @@ public async Task<bool> ProcessPublishingActivitiesDebugAsync(IAsyncEnumerable<P
else if (activity.Type == PublishingActivityTypes.Log)
{
// Log activity - display the log message
var logLevel = activity.Data.LogLevel ?? "Information";
var (parsedLogLevel, logPrefix) = ParseLogLevel(activity.Data.LogLevel);
var message = ConvertTextWithMarkdownFlag(activity.Data.StatusText, activity.Data);
var timestamp = activity.Data.Timestamp?.ToString("HH:mm:ss", CultureInfo.InvariantCulture) ?? DateTimeOffset.UtcNow.ToString("HH:mm:ss", CultureInfo.InvariantCulture);

// Use 3-letter prefixes for log levels
var logPrefix = logLevel.ToUpperInvariant() switch
{
"DEBUG" => "DBG",
"TRACE" => "TRC",
"INFORMATION" => "INF",
"WARNING" => "WRN",
"ERROR" => "ERR",
"CRITICAL" => "CRT",
_ => "INF"
};

// Make debug and trace logs more subtle
var formattedMessage = logLevel.ToUpperInvariant() switch
var formattedMessage = parsedLogLevel switch
{
"DEBUG" => $"[[{timestamp}]] [dim][[{logPrefix}]] {message}[/]",
"TRACE" => $"[[{timestamp}]] [dim][[{logPrefix}]] {message}[/]",
LogLevel.Debug or LogLevel.Trace => $"[[{timestamp}]] [dim][[{logPrefix}]] {message}[/]",
_ => $"[[{timestamp}]] [[{logPrefix}]] {message}"
};

Expand Down Expand Up @@ -550,42 +537,27 @@ public async Task<bool> ProcessAndDisplayPublishingActivitiesAsync(IAsyncEnumera
var stepId = activity.Data.StepId;
if (stepId != null && steps.TryGetValue(stepId, out var stepInfo))
{
var logLevel = activity.Data.LogLevel ?? "Information";
var (parsedLogLevel, logPrefix) = ParseLogLevel(activity.Data.LogLevel);
var message = ConvertTextWithMarkdownFlag(activity.Data.StatusText, activity.Data);

// Add 3-letter prefix to message for consistency
var logPrefix = logLevel.ToUpperInvariant() switch
{
"DEBUG" => "DBG",
"TRACE" => "TRC",
"INFORMATION" => "INF",
"WARNING" => "WRN",
"ERROR" => "ERR",
"CRITICAL" => "CRT",
_ => "INF"
};

var prefixedMessage = $"[[{logPrefix}]] {message}";

// Map log levels to appropriate console logger methods
switch (logLevel.ToUpperInvariant())
switch (parsedLogLevel)
{
case "ERROR":
case "CRITICAL":
case LogLevel.Error:
case LogLevel.Critical:
logger.Failure(stepInfo.Id, prefixedMessage);
break;
case "WARNING":
case "WARN":
case LogLevel.Warning:
logger.Warning(stepInfo.Id, prefixedMessage);
break;
case "DEBUG":
case "TRACE":
case LogLevel.Debug:
case LogLevel.Trace:
// Use a more subtle approach for debug/trace - prefix with dim formatting
var subtleMessage = $"[dim]{prefixedMessage}[/]";
logger.Info(stepInfo.Id, subtleMessage);
break;
case "INFORMATION":
case "INFO":
case LogLevel.Information:
default:
logger.Info(stepInfo.Id, prefixedMessage);
break;
Expand Down Expand Up @@ -908,6 +880,17 @@ private static bool ParseBooleanValue(string? value)
return bool.TryParse(value, out var result) && result;
}

private static (LogLevel Level, string Prefix) ParseLogLevel(string? logLevel) => logLevel?.ToUpperInvariant() switch
{
"TRACE" => (LogLevel.Trace, "TRC"),
"DEBUG" => (LogLevel.Debug, "DBG"),
"INFORMATION" or "INFO" => (LogLevel.Information, "INF"),
"WARNING" or "WARN" => (LogLevel.Warning, "WRN"),
"ERROR" => (LogLevel.Error, "ERR"),
"CRITICAL" => (LogLevel.Critical, "CRT"),
null or _ => (LogLevel.Information, "INF")
};

private class StepInfo
{
public string Id { get; set; } = string.Empty;
Expand Down
7 changes: 4 additions & 3 deletions src/Aspire.Cli/Commands/RenderCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,8 @@ private int RenderPublishSummaryScenarios(IEnumerable<string> scenarioKeys)
"publish-summary-markup" => new(
"Markup characters in names and failures",
[
new("root", "Build [web] frontend", ConsoleActivityLogger.ActivityState.Success, TimeSpan.FromMilliseconds(120), null, null, 0, 1, TimeSpan.Zero, TimeSpan.FromMilliseconds(120)),
new("child", "Deploy [api] service", ConsoleActivityLogger.ActivityState.Failure, TimeSpan.FromMilliseconds(35), "Failure while parsing [resource] => {bad}", "root", 1, 2, TimeSpan.FromMilliseconds(60), TimeSpan.FromMilliseconds(95)),
new("root", "Build [[web]] frontend", ConsoleActivityLogger.ActivityState.Success, TimeSpan.FromMilliseconds(120), null, null, 0, 1, TimeSpan.Zero, TimeSpan.FromMilliseconds(120)),
new("child", "Deploy [[api]] service", ConsoleActivityLogger.ActivityState.Failure, TimeSpan.FromMilliseconds(35), "Failure while parsing [[resource]] => {bad}", "root", 1, 2, TimeSpan.FromMilliseconds(60), TimeSpan.FromMilliseconds(95)),
new("sibling", "Notify [[observers]]", ConsoleActivityLogger.ActivityState.Warning, TimeSpan.FromMilliseconds(12), null, "root", 1, 3, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(112)),
], false),
"publish-summary-mixed-hierarchy" => new(
Expand All @@ -344,7 +344,8 @@ private int RenderPublishSummaryScenarios(IEnumerable<string> scenarioKeys)
new("orphan", "Orphaned child falls back to root ordering", ConsoleActivityLogger.ActivityState.Success, TimeSpan.FromSeconds(2), null, "missing-parent", 1, 2, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3)),
new("root-b", "Publish", ConsoleActivityLogger.ActivityState.Warning, TimeSpan.FromSeconds(4), null, null, 0, 3, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(9)),
new("child-b", "Package", ConsoleActivityLogger.ActivityState.Success, TimeSpan.FromSeconds(1), null, "root-b", 1, 4, TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(7)),
new("root-c", "Validate", ConsoleActivityLogger.ActivityState.Success, TimeSpan.FromSeconds(2), null, null, 0, 5, TimeSpan.FromSeconds(9), TimeSpan.FromSeconds(11)),
new("info-step", "Using cached configuration", ConsoleActivityLogger.ActivityState.Info, TimeSpan.FromSeconds(0), null, "root-b", 1, 5, TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(7)),
new("root-c", "Validate", ConsoleActivityLogger.ActivityState.Success, TimeSpan.FromSeconds(2), null, null, 0, 6, TimeSpan.FromSeconds(9), TimeSpan.FromSeconds(11)),
]),
"publish-summary-duration-extremes" => new(
"Duration extremes",
Expand Down
12 changes: 6 additions & 6 deletions src/Aspire.Cli/Utils/ConsoleActivityLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ internal sealed class ConsoleActivityLogger

// No raw ANSI escape codes; rely on Spectre.Console markup tokens.

private const string SuccessSymbol = "✓";
private const string FailureSymbol = "✗";
private const string WarningSymbol = "";
private const string InProgressSymbol = "→";
private const string SuccessSymbol = "✓\uFE0E";
private const string FailureSymbol = "✗\uFE0E";
private const string WarningSymbol = "";
private const string InProgressSymbol = "→\uFE0E";
private const string InfoSymbol = "i";
private const int SummaryTimelineWidth = 28;
private const int SummaryTimelineTicks = 4;
Expand Down Expand Up @@ -410,10 +410,10 @@ private void WriteStepDurationsSummary(IReadOnlyList<StepDurationRecord> records
ActivityState.Failure => _enableColor ? "[red]" + FailureSymbol + "[/]" : FailureSymbol,
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

ActivityState.Info isn’t handled in the step-summary symbol mapping, so an info record will render with the in-progress arrow (and cyan) rather than using InfoSymbol/dim styling. Add an explicit ActivityState.Info arm (and consider matching the summary bar colorization) so the new render scenario actually exercises the info symbol as intended.

Suggested change
ActivityState.Failure => _enableColor ? "[red]" + FailureSymbol + "[/]" : FailureSymbol,
ActivityState.Failure => _enableColor ? "[red]" + FailureSymbol + "[/]" : FailureSymbol,
ActivityState.Info => _enableColor ? "[dim]" + InfoSymbol + "[/]" : InfoSymbol,

Copilot uses AI. Check for mistakes.
_ => _enableColor ? "[cyan]" + InProgressSymbol + "[/]" : InProgressSymbol
};
// DisplayName and FailureReason are already Spectre-safe (pre-processed
// through ConvertTextWithMarkdownFlag which escapes or converts markdown).
var displayName = GetIndentedDisplayName(rec);
var name = (renderTimeline ? displayName.PadRight(nameWidth) : displayName).EscapeMarkup();

// FailureReason is already Spectre-safe (pre-processed through ConvertTextWithMarkdownFlag which escapes or converts markdown).
var reason = rec.State == ActivityState.Failure && !string.IsNullOrEmpty(rec.FailureReason)
? (_enableColor ? $" [red]— {HighlightMessage(rec.FailureReason!)}[/]" : $" — {rec.FailureReason!}")
: string.Empty;
Expand Down
Loading