44
55using System . IO . Abstractions ;
66using Elastic . Documentation . Diagnostics ;
7+ using Microsoft . Extensions . Logging ;
78using ProcNet ;
9+ using ProcNet . Std ;
810
911namespace Elastic . Documentation . ExternalCommands ;
1012
11- public abstract class ExternalCommandExecutor ( IDiagnosticsCollector collector , IDirectoryInfo workingDirectory )
13+ public abstract class ExternalCommandExecutor ( IDiagnosticsCollector collector , IDirectoryInfo workingDirectory , TimeSpan ? timeout = null )
1214{
15+ protected abstract ILogger Logger { get ; }
16+
17+ private void Log ( Action < ILogger > logAction )
18+ {
19+ if ( string . IsNullOrWhiteSpace ( Environment . GetEnvironmentVariable ( "CI" ) ) )
20+ return ;
21+ logAction ( Logger ) ;
22+ }
23+
1324 protected IDirectoryInfo WorkingDirectory => workingDirectory ;
1425 protected IDiagnosticsCollector Collector => collector ;
1526 protected void ExecIn ( Dictionary < string , string > environmentVars , string binary , params string [ ] args )
1627 {
1728 var arguments = new ExecArguments ( binary , args )
1829 {
1930 WorkingDirectory = workingDirectory . FullName ,
20- Environment = environmentVars
31+ Environment = environmentVars ,
32+ Timeout = timeout
2133 } ;
2234 var result = Proc . Exec ( arguments ) ;
2335 if ( result != 0 )
@@ -30,99 +42,83 @@ protected void ExecInSilent(Dictionary<string, string> environmentVars, string b
3042 {
3143 Environment = environmentVars ,
3244 WorkingDirectory = workingDirectory . FullName ,
33- ConsoleOutWriter = NoopConsoleWriter . Instance
45+ ConsoleOutWriter = NoopConsoleWriter . Instance ,
46+ Timeout = timeout
3447 } ;
3548 var result = Proc . Start ( arguments ) ;
3649 if ( result . ExitCode != 0 )
3750 collector . EmitError ( "" , $ "Exit code: { result . ExitCode } while executing { binary } { string . Join ( " " , args ) } in { workingDirectory } ") ;
3851 }
3952
4053 protected string [ ] CaptureMultiple ( string binary , params string [ ] args ) => CaptureMultiple ( false , 10 , binary , args ) ;
41- protected string [ ] CaptureMultiple ( bool muteExceptions , int attempts , string binary , params string [ ] args )
54+ protected string [ ] CaptureMultiple ( int attempts , string binary , params string [ ] args ) => CaptureMultiple ( false , attempts , binary , args ) ;
55+ private string [ ] CaptureMultiple ( bool muteExceptions , int attempts , string binary , params string [ ] args )
4256 {
4357 // Try 10 times to capture the output of the command, if it fails, we'll throw an exception on the last try
4458 Exception ? e = null ;
4559 for ( var i = 1 ; i <= attempts ; i ++ )
4660 {
4761 try
4862 {
49- return CaptureOutput ( ) ;
63+ return CaptureOutput ( e , i , attempts ) ;
5064 }
5165 catch ( Exception ex )
5266 {
53- collector . EmitWarning ( "" , $ "An exception occurred on attempt { i } to capture output of { binary } : { ex ? . Message } ") ;
67+ collector . EmitGlobalWarning ( $ "An exception occurred on attempt { i } to capture output of { binary } : { ex ? . Message } ") ;
5468 if ( ex is not null )
5569 e = ex ;
5670 }
5771 }
5872
5973 if ( e is not null && ! muteExceptions )
6074 collector . EmitError ( "" , "failure capturing stdout" , e ) ;
75+ if ( e is not null )
76+ Log ( l => l . LogError ( e , "[{Binary} {Args}] failure capturing stdout executing in {WorkingDirectory}" , binary , string . Join ( " " , args ) , workingDirectory . FullName ) ) ;
6177
6278 return [ ] ;
6379
64- string [ ] CaptureOutput ( )
80+ string [ ] CaptureOutput ( Exception ? previousException , int iteration , int max )
6581 {
6682 var arguments = new StartArguments ( binary , args )
6783 {
6884 WorkingDirectory = workingDirectory . FullName ,
6985 Timeout = TimeSpan . FromSeconds ( 3 ) ,
7086 WaitForExit = TimeSpan . FromSeconds ( 3 ) ,
71- ConsoleOutWriter = NoopConsoleWriter . Instance
87+ ConsoleOutWriter = new ConsoleOutWriter ( )
7288 } ;
7389 var result = Proc . Start ( arguments ) ;
7490
75- var output = ( result . ExitCode , muteExceptions ) switch
91+ string [ ] ? output ;
92+ switch ( result . ExitCode , muteExceptions )
7693 {
77- ( 0 , _ ) or ( not 0 , true ) => result . ConsoleOut . Select ( x => x . Line ) . ToArray ( ) ?? throw new Exception ( $ "No output captured for { binary } : { workingDirectory } ") ,
78- ( not 0 , false ) => throw new Exception ( $ "Exit code is not 0. Received { result . ExitCode } from { binary } : { workingDirectory } ")
79- } ;
94+ case ( 0 , _ ) or ( not 0 , true ) :
95+ output = result . ConsoleOut . Select ( x => x . Line ) . ToArray ( ) ;
96+ if ( output . Length == 0 )
97+ {
98+ Log ( l => l . LogInformation ( "[{Binary} {Args}] captured no output. ({Iteration}/{MaxIteration}) pwd: {WorkingDirectory}" ,
99+ binary , string . Join ( " " , args ) , iteration , max , workingDirectory . FullName )
100+ ) ;
101+ throw new Exception ( $ "No output captured executing in pwd: { workingDirectory } from { binary } { string . Join ( " " , args ) } ", previousException ) ;
102+ }
103+ break ;
104+ case ( not 0 , false ) :
105+ Log ( l => l . LogInformation ( "[{Binary} {Args}] Exit code is not 0 but {ExitCode}. ({Iteration}/{MaxIteration}) pwd: {WorkingDirectory}" ,
106+ binary , string . Join ( " " , args ) , result . ExitCode , iteration , max , workingDirectory . FullName )
107+ ) ;
108+ throw new Exception ( $ "Exit code not 0. Received { result . ExitCode } in pwd: { workingDirectory } from { binary } { string . Join ( " " , args ) } ", previousException ) ;
109+ }
110+
80111 return output ;
81112 }
82113 }
83114
84-
115+ protected string CaptureQuiet ( string binary , params string [ ] args ) => Capture ( true , 10 , binary , args ) ;
85116 protected string Capture ( string binary , params string [ ] args ) => Capture ( false , 10 , binary , args ) ;
86- protected string Capture ( bool muteExceptions , string binary , params string [ ] args ) => Capture ( muteExceptions , 10 , binary , args ) ;
87- protected string Capture ( bool muteExceptions , int attempts , string binary , params string [ ] args )
88- {
89- // Try 10 times to capture the output of the command, if it fails, we'll throw an exception on the last try
90- Exception ? e = null ;
91- for ( var i = 1 ; i <= attempts ; i ++ )
92- {
93- try
94- {
95- return CaptureOutput ( ) ;
96- }
97- catch ( Exception ex )
98- {
99- if ( ex is not null )
100- e = ex ;
101- }
102- }
103-
104- if ( e is not null && ! muteExceptions )
105- collector . EmitError ( "" , "failure capturing stdout" , e ) ;
106-
107- return string . Empty ;
108117
109- string CaptureOutput ( )
110- {
111- var arguments = new StartArguments ( binary , args )
112- {
113- WorkingDirectory = workingDirectory . FullName ,
114- Timeout = TimeSpan . FromSeconds ( 3 ) ,
115- WaitForExit = TimeSpan . FromSeconds ( 3 ) ,
116- ConsoleOutWriter = NoopConsoleWriter . Instance ,
117- OnlyPrintBinaryInExceptionMessage = false
118- } ;
119- var result = Proc . Start ( arguments ) ;
120- var line = ( result . ExitCode , muteExceptions ) switch
121- {
122- ( 0 , _ ) or ( not 0 , true ) => result . ConsoleOut . FirstOrDefault ( ) ? . Line ?? throw new Exception ( $ "No output captured for { binary } : { workingDirectory } ") ,
123- ( not 0 , false ) => throw new Exception ( $ "Exit code is not 0. Received { result . ExitCode } from { binary } : { workingDirectory } ")
124- } ;
125- return line ;
126- }
118+ private string Capture ( bool muteExceptions , int attempts , string binary , params string [ ] args )
119+ {
120+ var lines = CaptureMultiple ( muteExceptions , attempts , binary , args ) ;
121+ return lines . FirstOrDefault ( ) ??
122+ ( muteExceptions ? string . Empty : throw new Exception ( $ "[{ binary } { string . Join ( " " , args ) } ] No output captured executing in : { workingDirectory } ") ) ;
127123 }
128124}
0 commit comments