-
Notifications
You must be signed in to change notification settings - Fork 30
Add more logging to CLI invocations on CI and ensure CLI invocations have timeouts on CI #1964
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -4,20 +4,32 @@ | |||||||||||||||||||||||
|
||||||||||||||||||||||||
using System.IO.Abstractions; | ||||||||||||||||||||||||
using Elastic.Documentation.Diagnostics; | ||||||||||||||||||||||||
using Microsoft.Extensions.Logging; | ||||||||||||||||||||||||
using ProcNet; | ||||||||||||||||||||||||
using ProcNet.Std; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
namespace Elastic.Documentation.ExternalCommands; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
public abstract class ExternalCommandExecutor(IDiagnosticsCollector collector, IDirectoryInfo workingDirectory) | ||||||||||||||||||||||||
public abstract class ExternalCommandExecutor(IDiagnosticsCollector collector, IDirectoryInfo workingDirectory, TimeSpan? timeout = null) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
protected abstract ILogger Logger { get; } | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
private void Log(Action<ILogger> logAction) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CI"))) | ||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
logAction(Logger); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
protected IDirectoryInfo WorkingDirectory => workingDirectory; | ||||||||||||||||||||||||
protected IDiagnosticsCollector Collector => collector; | ||||||||||||||||||||||||
protected void ExecIn(Dictionary<string, string> environmentVars, string binary, params string[] args) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
var arguments = new ExecArguments(binary, args) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
WorkingDirectory = workingDirectory.FullName, | ||||||||||||||||||||||||
Environment = environmentVars | ||||||||||||||||||||||||
Environment = environmentVars, | ||||||||||||||||||||||||
Timeout = timeout | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
var result = Proc.Exec(arguments); | ||||||||||||||||||||||||
if (result != 0) | ||||||||||||||||||||||||
|
@@ -30,99 +42,83 @@ protected void ExecInSilent(Dictionary<string, string> environmentVars, string b | |||||||||||||||||||||||
{ | ||||||||||||||||||||||||
Environment = environmentVars, | ||||||||||||||||||||||||
WorkingDirectory = workingDirectory.FullName, | ||||||||||||||||||||||||
ConsoleOutWriter = NoopConsoleWriter.Instance | ||||||||||||||||||||||||
ConsoleOutWriter = NoopConsoleWriter.Instance, | ||||||||||||||||||||||||
Timeout = timeout | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
var result = Proc.Start(arguments); | ||||||||||||||||||||||||
if (result.ExitCode != 0) | ||||||||||||||||||||||||
collector.EmitError("", $"Exit code: {result.ExitCode} while executing {binary} {string.Join(" ", args)} in {workingDirectory}"); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
protected string[] CaptureMultiple(string binary, params string[] args) => CaptureMultiple(false, 10, binary, args); | ||||||||||||||||||||||||
protected string[] CaptureMultiple(bool muteExceptions, int attempts, string binary, params string[] args) | ||||||||||||||||||||||||
protected string[] CaptureMultiple(int attempts, string binary, params string[] args) => CaptureMultiple(false, attempts, binary, args); | ||||||||||||||||||||||||
private string[] CaptureMultiple(bool muteExceptions, int attempts, string binary, params string[] args) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
// Try 10 times to capture the output of the command, if it fails, we'll throw an exception on the last try | ||||||||||||||||||||||||
Exception? e = null; | ||||||||||||||||||||||||
for (var i = 1; i <= attempts; i++) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
try | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
return CaptureOutput(); | ||||||||||||||||||||||||
return CaptureOutput(e, i, attempts); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
catch (Exception ex) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
collector.EmitWarning("", $"An exception occurred on attempt {i} to capture output of {binary}: {ex?.Message}"); | ||||||||||||||||||||||||
collector.EmitGlobalWarning($"An exception occurred on attempt {i} to capture output of {binary}: {ex?.Message}"); | ||||||||||||||||||||||||
if (ex is not null) | ||||||||||||||||||||||||
e = ex; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if (e is not null && !muteExceptions) | ||||||||||||||||||||||||
collector.EmitError("", "failure capturing stdout", e); | ||||||||||||||||||||||||
if (e is not null) | ||||||||||||||||||||||||
Log(l => l.LogError(e, "[{Binary} {Args}] failure capturing stdout executing in {WorkingDirectory}", binary, string.Join(" ", args), workingDirectory.FullName)); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
return []; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
string[] CaptureOutput() | ||||||||||||||||||||||||
string[] CaptureOutput(Exception? previousException, int iteration, int max) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
var arguments = new StartArguments(binary, args) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
WorkingDirectory = workingDirectory.FullName, | ||||||||||||||||||||||||
Timeout = TimeSpan.FromSeconds(3), | ||||||||||||||||||||||||
WaitForExit = TimeSpan.FromSeconds(3), | ||||||||||||||||||||||||
Comment on lines
82
to
86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The hardcoded 3-second timeout in CaptureOutput conflicts with the configurable timeout parameter. Consider using the configurable timeout or a derived value to maintain consistency.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is by design, capturing is retried and is expected to run quickly. |
||||||||||||||||||||||||
ConsoleOutWriter = NoopConsoleWriter.Instance | ||||||||||||||||||||||||
ConsoleOutWriter = new ConsoleOutWriter() | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
var result = Proc.Start(arguments); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
var output = (result.ExitCode, muteExceptions) switch | ||||||||||||||||||||||||
string[]? output; | ||||||||||||||||||||||||
switch (result.ExitCode, muteExceptions) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
(0, _) or (not 0, true) => result.ConsoleOut.Select(x => x.Line).ToArray() ?? throw new Exception($"No output captured for {binary}: {workingDirectory}"), | ||||||||||||||||||||||||
(not 0, false) => throw new Exception($"Exit code is not 0. Received {result.ExitCode} from {binary}: {workingDirectory}") | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
case (0, _) or (not 0, true): | ||||||||||||||||||||||||
output = result.ConsoleOut.Select(x => x.Line).ToArray(); | ||||||||||||||||||||||||
if (output.Length == 0) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
Log(l => l.LogInformation("[{Binary} {Args}] captured no output. ({Iteration}/{MaxIteration}) pwd: {WorkingDirectory}", | ||||||||||||||||||||||||
binary, string.Join(" ", args), iteration, max, workingDirectory.FullName) | ||||||||||||||||||||||||
); | ||||||||||||||||||||||||
throw new Exception($"No output captured executing in pwd: {workingDirectory} from {binary} {string.Join(" ", args)}", previousException); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
break; | ||||||||||||||||||||||||
case (not 0, false): | ||||||||||||||||||||||||
Log(l => l.LogInformation("[{Binary} {Args}] Exit code is not 0 but {ExitCode}. ({Iteration}/{MaxIteration}) pwd: {WorkingDirectory}", | ||||||||||||||||||||||||
binary, string.Join(" ", args), result.ExitCode, iteration, max, workingDirectory.FullName) | ||||||||||||||||||||||||
); | ||||||||||||||||||||||||
throw new Exception($"Exit code not 0. Received {result.ExitCode} in pwd: {workingDirectory} from {binary} {string.Join(" ", args)}", previousException); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
return output; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
protected string CaptureQuiet(string binary, params string[] args) => Capture(true, 10, binary, args); | ||||||||||||||||||||||||
protected string Capture(string binary, params string[] args) => Capture(false, 10, binary, args); | ||||||||||||||||||||||||
protected string Capture(bool muteExceptions, string binary, params string[] args) => Capture(muteExceptions, 10, binary, args); | ||||||||||||||||||||||||
protected string Capture(bool muteExceptions, int attempts, string binary, params string[] args) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
// Try 10 times to capture the output of the command, if it fails, we'll throw an exception on the last try | ||||||||||||||||||||||||
Exception? e = null; | ||||||||||||||||||||||||
for (var i = 1; i <= attempts; i++) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
try | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
return CaptureOutput(); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
catch (Exception ex) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
if (ex is not null) | ||||||||||||||||||||||||
e = ex; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if (e is not null && !muteExceptions) | ||||||||||||||||||||||||
collector.EmitError("", "failure capturing stdout", e); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
return string.Empty; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
string CaptureOutput() | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
var arguments = new StartArguments(binary, args) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
WorkingDirectory = workingDirectory.FullName, | ||||||||||||||||||||||||
Timeout = TimeSpan.FromSeconds(3), | ||||||||||||||||||||||||
WaitForExit = TimeSpan.FromSeconds(3), | ||||||||||||||||||||||||
ConsoleOutWriter = NoopConsoleWriter.Instance, | ||||||||||||||||||||||||
OnlyPrintBinaryInExceptionMessage = false | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
var result = Proc.Start(arguments); | ||||||||||||||||||||||||
var line = (result.ExitCode, muteExceptions) switch | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
(0, _) or (not 0, true) => result.ConsoleOut.FirstOrDefault()?.Line ?? throw new Exception($"No output captured for {binary}: {workingDirectory}"), | ||||||||||||||||||||||||
(not 0, false) => throw new Exception($"Exit code is not 0. Received {result.ExitCode} from {binary}: {workingDirectory}") | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
return line; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
private string Capture(bool muteExceptions, int attempts, string binary, params string[] args) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
var lines = CaptureMultiple(muteExceptions, attempts, binary, args); | ||||||||||||||||||||||||
return lines.FirstOrDefault() ?? | ||||||||||||||||||||||||
(muteExceptions ? string.Empty : throw new Exception($"[{binary} {string.Join(" ", args)}] No output captured executing in : {workingDirectory}")); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The timeout is only applied when environmentVars is provided, but not in the regular Exec methods without environment variables. This creates inconsistent timeout behavior across different execution paths.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is false timeout is always provided for
ExecIn*