Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
49 changes: 47 additions & 2 deletions Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,20 +175,65 @@ private static string FileLevel(LogLevel level)
};
}

/// <summary>
/// Strips ANSI colour sequences. A package's own output is written for a terminal
/// and carries escape codes that nothing renders once they are in a log file.
/// </summary>
internal static string StripDecoration(string message)
=> System.Text.RegularExpressions.Regex.Replace(message, @"\x1b\[[0-9;]*m", string.Empty);

/// <summary>
/// Writes one stamped line per line of <paramref name="message"/>.
/// </summary>
/// <remarks>
/// A multi-line message used to be written with a single stamp on the front, so only
/// its first line carried a timestamp and level and the rest landed in the log as bare
/// text. That is how a package's captured stdout ended up sitting unstamped between
/// two properly formatted lines. Blank lines are dropped: captured output is full of
/// them and they carry nothing.
/// </remarks>
private static void WriteToFile(LogLevel level, string message)
{
if (string.IsNullOrEmpty(LogFile)) return;

try
{
File.AppendAllText(LogFile, FormatLine(level, message, DateTime.Now) + Environment.NewLine);
var now = DateTime.Now;
var builder = new System.Text.StringBuilder();
foreach (var line in StripDecoration(message).Split('\n'))
{
var text = line.TrimEnd('\r');
if (string.IsNullOrWhiteSpace(text)) continue;
builder.Append(FormatLine(level, text, now)).Append(Environment.NewLine);
}

if (builder.Length > 0)
File.AppendAllText(LogFile, builder.ToString());
}
catch
{
// Silent fail for file logging to not disrupt main process
}
}

/// <summary>
/// Records output captured from a package's own process: stdout at INFO, stderr at
/// WARN, one stamped line each, tagged so a reader can tell the package's words from
/// BootstrapMate's own. The tag is uppercase in brackets at the start of the message,
/// matching [PROGRESS] and [SUCCESS], which the log viewer renders as a pill.
/// </summary>
public static void WriteCapturedOutput(string package, string output, bool isError = false)
{
if (string.IsNullOrWhiteSpace(output)) return;
var level = isError ? LogLevel.Warning : LogLevel.Info;
foreach (var line in StripDecoration(output).Split('\n'))
{
var text = line.TrimEnd('\r');
if (string.IsNullOrWhiteSpace(text)) continue;
WriteToFile(level, $"[OUTPUT] {package}: {text}");
}
}

private static void WriteToConsole(LogLevel level, string message)
{
// Skip console output in silent mode
Expand Down
24 changes: 18 additions & 6 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,18 @@ static bool VerifyInstallerSignature(string filePath, string type, JsonElement p
return false;
}

static string PackageLabel(JsonElement packageInfo)
{
if (packageInfo.ValueKind == JsonValueKind.Object &&
packageInfo.TryGetProperty("name", out var nameProp) &&
nameProp.ValueKind == JsonValueKind.String)
{
var name = nameProp.GetString();
if (!string.IsNullOrWhiteSpace(name)) return name!;
}
return "package";
}

static async Task RunPowerShellScript(string scriptPath, JsonElement packageInfo)
{
var args = GetArguments(packageInfo);
Expand Down Expand Up @@ -1267,7 +1279,7 @@ static async Task RunPowerShellScript(string scriptPath, JsonElement packageInfo
string output = await process.StandardOutput.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(output))
{
WriteLog($"PowerShell output: {output}");
Logger.WriteCapturedOutput(PackageLabel(packageInfo), output);
}
}

Expand All @@ -1276,7 +1288,7 @@ static async Task RunPowerShellScript(string scriptPath, JsonElement packageInfo
string error = await process.StandardError.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(error))
{
WriteLog($"PowerShell error: {error}");
Logger.WriteCapturedOutput(PackageLabel(packageInfo), error, isError: true);
}
}

Expand Down Expand Up @@ -1571,7 +1583,7 @@ static async Task RunExecutable(string exePath, JsonElement packageInfo)
string output = await process.StandardOutput.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(output))
{
WriteLog($"Executable output: {output}");
Logger.WriteCapturedOutput(PackageLabel(packageInfo), output);
}
}

Expand All @@ -1580,7 +1592,7 @@ static async Task RunExecutable(string exePath, JsonElement packageInfo)
string error = await process.StandardError.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(error))
{
WriteLog($"Executable error: {error}");
Logger.WriteCapturedOutput(PackageLabel(packageInfo), error, isError: true);
}
}

Expand Down Expand Up @@ -2636,12 +2648,12 @@ static async Task RunChocolateyInstall(string nupkgPath, JsonElement packageInfo
// Always log ALL output for debugging - this is critical for troubleshooting
if (!string.IsNullOrWhiteSpace(stdout))
{
Logger.Debug($"Chocolatey stdout: {stdout.Trim()}");
Logger.WriteCapturedOutput("Chocolatey", stdout);
}

if (!string.IsNullOrWhiteSpace(stderr))
{
Logger.Debug($"Chocolatey stderr: {stderr.Trim()}");
Logger.WriteCapturedOutput("Chocolatey", stderr, isError: true);
}

if (process.ExitCode != 0)
Expand Down
Loading