Skip to content
Open
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
45 changes: 18 additions & 27 deletions src/ModularPipelines/Context/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ private async Task<CommandResult> Of(
var standardError = string.Empty;

var inputToLog = GetInputToLog(command, execOpts);
var loggingFailures = new DeferredCommandLoggingFailures();

// Only create timeout token if ExecutionTimeout is specified to avoid unnecessary allocations
using var timeoutCancellationToken = CreateTimeoutCancellationToken(execOpts);
Expand All @@ -133,6 +134,12 @@ private async Task<CommandResult> Of(

var registration = linkedCancellationToken.Token.Register(
() => ScheduleForcefulCancellation(forcefulCancellationToken, execOpts.GracefulShutdownTimeout));
loggingFailures.Capture(
() => _commandLogger.LogCommandStart(
options,
execOpts,
inputToLog,
command.WorkingDirPath));
await using (registration.ConfigureAwait(false))
{
CliWrap.CommandResult result;
Expand Down Expand Up @@ -177,8 +184,7 @@ await WaitForForcefulCancellationAsync(
standardOutput = standardOutputBuffer.ToString();
standardError = standardErrorBuffer.ToString();

var failure = CombineWithCompletionFailure(
e,
loggingFailures.Capture(
() => LogCommandCompletion(
options,
execOpts,
Expand All @@ -191,6 +197,7 @@ await WaitForForcefulCancellationAsync(
completeStandardErrorBuffer,
deferredOutputLogger,
command.WorkingDirPath));
var failure = loggingFailures.CombineWith(e);

throw new CommandException(
CreateFailureResult(
Expand All @@ -213,8 +220,7 @@ await WaitForForcefulCancellationAsync(
standardOutput = standardOutputBuffer.ToString();
standardError = standardErrorBuffer.ToString();

var failure = CombineWithCompletionFailure(
e,
loggingFailures.Capture(
() => LogCommandCompletion(
options,
execOpts,
Expand All @@ -227,6 +233,7 @@ await WaitForForcefulCancellationAsync(
completeStandardErrorBuffer,
deferredOutputLogger,
command.WorkingDirPath));
var failure = loggingFailures.CombineWith(e);

if (ShouldPreserveCallerCancellation(e, failure, cancellationToken))
{
Expand Down Expand Up @@ -254,9 +261,8 @@ await WaitForForcefulCancellationAsync(
standardOutput,
standardError);

try
{
LogCommandCompletion(
loggingFailures.Capture(
() => LogCommandCompletion(
options,
execOpts,
inputToLog,
Expand All @@ -267,39 +273,24 @@ await WaitForForcefulCancellationAsync(
completeStandardOutputBuffer,
completeStandardErrorBuffer,
deferredOutputLogger,
command.WorkingDirPath);
}
catch (Exception completionFailure) when (commandFailure is not null)
command.WorkingDirPath));
if (commandFailure is not null && loggingFailures.HasFailures)
{
throw new CommandException(
commandFailure.Result,
new AggregateException(commandFailure, completionFailure));
loggingFailures.CombineWith(commandFailure));
}

if (commandFailure is not null)
{
throw commandFailure;
}

loggingFailures.Throw();
return new CommandResult(command, result, standardOutput, standardError);
}
}

private static Exception CombineWithCompletionFailure(
Exception executionFailure,
Action complete)
{
try
{
complete();
return executionFailure;
}
catch (Exception completionFailure)
{
return new AggregateException(executionFailure, completionFailure);
}
}

private static bool ShouldPreserveCallerCancellation(
Exception executionFailure,
Exception combinedFailure,
Expand Down Expand Up @@ -945,7 +936,7 @@ private void LogCommandCompletion(
{
var deferredOutput = deferredOutputLogger?.Complete();
var hasStreamedOutput = deferredOutput?.HasStreamedOutput == true;
_commandLogger.Log(
_commandLogger.LogCommandCompletion(
options,
executionOptions,
input,
Expand Down
75 changes: 63 additions & 12 deletions src/ModularPipelines/Context/LoggingCommandLineExecutor.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using ModularPipelines.Context.Domains.Shell;
using ModularPipelines.Logging;
using ModularPipelines.Models;
Expand Down Expand Up @@ -26,7 +27,7 @@ internal sealed class LoggingCommandLineExecutor : ICommandLineExecutor
private readonly ICommandLogger _commandLogger;

/// <summary>
/// Initializes a new instance of the <see cref="LoggingCommandLineExecutor"/> class.
/// Initialises a new instance of the <see cref="LoggingCommandLineExecutor"/> class.
/// </summary>
/// <param name="inner">The inner executor to wrap.</param>
/// <param name="commandLogger">The command logger for consistent logging.</param>
Expand All @@ -46,20 +47,50 @@ public async Task<CommandResult> ExecuteAsync(
{
var workingDirectory = options?.WorkingDirectory ?? Environment.CurrentDirectory;
var inputToLog = GetInputToLog(commandLine, options);
var stopwatch = Stopwatch.StartNew();
var loggingFailures = new DeferredCommandLoggingFailures();
loggingFailures.Capture(
() => _commandLogger.LogCommandStart(
options: null,
execOpts: options,
inputToLog: inputToLog,
commandWorkingDirPath: workingDirectory));

var result = await _inner.ExecuteAsync(commandLine, options, cancellationToken).ConfigureAwait(false);
CommandResult result;
try
{
result = await _inner.ExecuteAsync(commandLine, options, cancellationToken).ConfigureAwait(false);
}
catch (Exception executionFailure)
{
loggingFailures.Capture(
() => LogCompletion(
options,
inputToLog,
workingDirectory,
exitCode: -1,
runTime: stopwatch.Elapsed,
standardOutput: string.Empty,
standardError: executionFailure.Message));

// Delegate to CommandLogger for consistent logging across all command execution paths
_commandLogger.Log(
options: null, // Raw command line execution doesn't use CommandLineToolOptions
execOpts: options,
inputToLog: inputToLog,
exitCode: result.ExitCode,
runTime: result.Duration,
standardOutput: result.StandardOutput,
standardError: result.StandardError,
commandWorkingDirPath: workingDirectory);
if (!loggingFailures.HasFailures)
{
throw;
}

throw loggingFailures.CombineWith(executionFailure);
}

loggingFailures.Capture(
() => LogCompletion(
options,
inputToLog,
workingDirectory,
result.ExitCode,
result.Duration,
result.StandardOutput,
result.StandardError));
loggingFailures.Throw();
return result;
}

Expand All @@ -70,4 +101,24 @@ private static string GetInputToLog(CommandLine commandLine, CommandExecutionOpt
? options.InputLoggingManipulator(input)
: input;
}

private void LogCompletion(
CommandExecutionOptions? options,
string inputToLog,
string workingDirectory,
int exitCode,
TimeSpan runTime,
string standardOutput,
string standardError)
{
_commandLogger.LogCommandCompletion(
options: null,
execOpts: options,
inputToLog: inputToLog,
exitCode: exitCode,
runTime: runTime,
standardOutput: standardOutput,
standardError: standardError,
commandWorkingDirPath: workingDirectory);
}
}
Loading
Loading