77using DevProxy . Proxy ;
88using DevProxy . State ;
99using System . Diagnostics ;
10+ using System . Globalization ;
1011using System . Net ;
1112using System . Runtime . InteropServices ;
1213using System . Text ;
14+ using System . Text . Json ;
15+ using System . Text . Json . Serialization ;
1316
1417// Handle detached mode - spawn a child process and exit
1518// Only applies to root command (starting the proxy), not subcommands
4649
4750static async Task < int > StartDetachedProcessAsync ( string [ ] args )
4851{
52+ var isJsonOutput = IsJsonOutputRequested ( args ) ;
53+
4954 // Check if an instance is already running
5055 if ( await StateManager . IsInstanceRunningAsync ( ) )
5156 {
5257 var existingState = await StateManager . LoadStateAsync ( ) ;
53- await Console . Error . WriteLineAsync ( $ "Dev Proxy is already running (PID: { existingState ? . Pid } ).") ;
54- await Console . Error . WriteLineAsync ( "Use 'devproxy stop' to stop it first." ) ;
58+ if ( isJsonOutput )
59+ {
60+ await Console . Error . WriteLineAsync (
61+ FormatJsonLogEntry ( "error" , $ "Dev Proxy is already running (PID: { existingState ? . Pid } ). Use 'devproxy stop' to stop it first.") ) ;
62+ }
63+ else
64+ {
65+ await Console . Error . WriteLineAsync ( $ "Dev Proxy is already running (PID: { existingState ? . Pid } ).") ;
66+ await Console . Error . WriteLineAsync ( "Use 'devproxy stop' to stop it first." ) ;
67+ }
5568 return 1 ;
5669 }
5770
@@ -68,7 +81,15 @@ static async Task<int> StartDetachedProcessAsync(string[] args)
6881 var executablePath = Environment . ProcessPath ;
6982 if ( string . IsNullOrEmpty ( executablePath ) )
7083 {
71- await Console . Error . WriteLineAsync ( "Could not determine executable path." ) ;
84+ if ( isJsonOutput )
85+ {
86+ await Console . Error . WriteLineAsync (
87+ FormatJsonLogEntry ( "error" , "Could not determine executable path." ) ) ;
88+ }
89+ else
90+ {
91+ await Console . Error . WriteLineAsync ( "Could not determine executable path." ) ;
92+ }
7293 return 1 ;
7394 }
7495
@@ -94,7 +115,15 @@ static async Task<int> StartDetachedProcessAsync(string[] args)
94115 var process = Process . Start ( startInfo ) ;
95116 if ( process == null )
96117 {
97- await Console . Error . WriteLineAsync ( "Failed to start Dev Proxy process." ) ;
118+ if ( isJsonOutput )
119+ {
120+ await Console . Error . WriteLineAsync (
121+ FormatJsonLogEntry ( "error" , "Failed to start Dev Proxy process." ) ) ;
122+ }
123+ else
124+ {
125+ await Console . Error . WriteLineAsync ( "Failed to start Dev Proxy process." ) ;
126+ }
98127 return 1 ;
99128 }
100129
@@ -109,15 +138,27 @@ static async Task<int> StartDetachedProcessAsync(string[] args)
109138 var state = await StateManager . LoadStateAsync ( ) ;
110139 if ( state != null )
111140 {
112- await Console . Out . WriteLineAsync ( "Dev Proxy started in background." ) ;
113- await Console . Out . WriteLineAsync ( ) ;
114- await Console . Out . WriteLineAsync ( $ " PID: { state . Pid } ") ;
115- await Console . Out . WriteLineAsync ( $ " API URL: { state . ApiUrl } ") ;
116- await Console . Out . WriteLineAsync ( $ " Log file: { state . LogFile } ") ;
117- await Console . Out . WriteLineAsync ( ) ;
118- await Console . Out . WriteLineAsync ( "Use 'devproxy status' to check status." ) ;
119- await Console . Out . WriteLineAsync ( "Use 'devproxy logs' to view logs." ) ;
120- await Console . Out . WriteLineAsync ( "Use 'devproxy stop' to stop." ) ;
141+ if ( isJsonOutput )
142+ {
143+ await Console . Out . WriteLineAsync ( FormatJsonResultEntry ( new
144+ {
145+ state . Pid ,
146+ state . ApiUrl ,
147+ state . LogFile
148+ } ) ) ;
149+ }
150+ else
151+ {
152+ await Console . Out . WriteLineAsync ( "Dev Proxy started in background." ) ;
153+ await Console . Out . WriteLineAsync ( ) ;
154+ await Console . Out . WriteLineAsync ( $ " PID: { state . Pid } ") ;
155+ await Console . Out . WriteLineAsync ( $ " API URL: { state . ApiUrl } ") ;
156+ await Console . Out . WriteLineAsync ( $ " Log file: { state . LogFile } ") ;
157+ await Console . Out . WriteLineAsync ( ) ;
158+ await Console . Out . WriteLineAsync ( "Use 'devproxy status' to check status." ) ;
159+ await Console . Out . WriteLineAsync ( "Use 'devproxy logs' to view logs." ) ;
160+ await Console . Out . WriteLineAsync ( "Use 'devproxy stop' to stop." ) ;
161+ }
121162 return 0 ;
122163 }
123164
@@ -127,30 +168,120 @@ static async Task<int> StartDetachedProcessAsync(string[] args)
127168 var errorOutput = await process . StandardError . ReadToEndAsync ( ) ;
128169 var standardOutput = await process . StandardOutput . ReadToEndAsync ( ) ;
129170
130- await Console . Error . WriteLineAsync ( "Dev Proxy failed to start." ) ;
131- if ( ! string . IsNullOrEmpty ( errorOutput ) )
171+ if ( isJsonOutput )
132172 {
133- await Console . Error . WriteLineAsync ( errorOutput ) ;
173+ var message = "Dev Proxy failed to start." ;
174+ if ( ! string . IsNullOrEmpty ( errorOutput ) )
175+ {
176+ message += $ " { errorOutput . Trim ( ) } ";
177+ }
178+ if ( ! string . IsNullOrEmpty ( standardOutput ) )
179+ {
180+ message += $ " { standardOutput . Trim ( ) } ";
181+ }
182+ await Console . Error . WriteLineAsync ( FormatJsonLogEntry ( "error" , message ) ) ;
134183 }
135- if ( ! string . IsNullOrEmpty ( standardOutput ) )
184+ else
136185 {
137- await Console . Error . WriteLineAsync ( standardOutput ) ;
186+ await Console . Error . WriteLineAsync ( "Dev Proxy failed to start." ) ;
187+ if ( ! string . IsNullOrEmpty ( errorOutput ) )
188+ {
189+ await Console . Error . WriteLineAsync ( errorOutput ) ;
190+ }
191+ if ( ! string . IsNullOrEmpty ( standardOutput ) )
192+ {
193+ await Console . Error . WriteLineAsync ( standardOutput ) ;
194+ }
138195 }
139196 return process . ExitCode ;
140197 }
141198 }
142199
143- await Console . Error . WriteLineAsync ( "Timeout waiting for Dev Proxy to start." ) ;
144- await Console . Error . WriteLineAsync ( $ "Check the log folder: { StateManager . GetLogsFolder ( ) } ") ;
200+ if ( isJsonOutput )
201+ {
202+ await Console . Error . WriteLineAsync (
203+ FormatJsonLogEntry ( "error" , $ "Timeout waiting for Dev Proxy to start. Check the log folder: { StateManager . GetLogsFolder ( ) } ") ) ;
204+ }
205+ else
206+ {
207+ await Console . Error . WriteLineAsync ( "Timeout waiting for Dev Proxy to start." ) ;
208+ await Console . Error . WriteLineAsync ( $ "Check the log folder: { StateManager . GetLogsFolder ( ) } ") ;
209+ }
145210 return 1 ;
146211 }
147212 catch ( Exception ex )
148213 {
149- await Console . Error . WriteLineAsync ( $ "Failed to start Dev Proxy: { ex . Message } ") ;
214+ if ( isJsonOutput )
215+ {
216+ await Console . Error . WriteLineAsync (
217+ FormatJsonLogEntry ( "error" , $ "Failed to start Dev Proxy: { ex . Message } ") ) ;
218+ }
219+ else
220+ {
221+ await Console . Error . WriteLineAsync ( $ "Failed to start Dev Proxy: { ex . Message } ") ;
222+ }
150223 return 1 ;
151224 }
152225}
153226
227+ /// <summary>
228+ /// Formats a result entry matching the JSONL envelope produced by JsonConsoleFormatter:
229+ /// {"type":"result","data":{...},"timestamp":"..."}
230+ /// </summary>
231+ static string FormatJsonResultEntry ( object data )
232+ {
233+ #pragma warning disable CA1869 // Called at most once per process lifetime
234+ var jsonlOptions = new JsonSerializerOptions
235+ {
236+ PropertyNamingPolicy = JsonNamingPolicy . CamelCase ,
237+ DefaultIgnoreCondition = JsonIgnoreCondition . WhenWritingNull
238+ } ;
239+ #pragma warning restore CA1869
240+ var entry = new
241+ {
242+ type = "result" ,
243+ data = JsonSerializer . SerializeToElement ( data , jsonlOptions ) ,
244+ timestamp = DateTime . UtcNow . ToString ( "O" , CultureInfo . InvariantCulture )
245+ } ;
246+ return JsonSerializer . Serialize ( entry , jsonlOptions ) ;
247+ }
248+
249+ /// <summary>
250+ /// Formats a log entry matching the JSONL envelope produced by JsonConsoleFormatter:
251+ /// {"type":"log","level":"...","message":"...","timestamp":"..."}
252+ /// </summary>
253+ static string FormatJsonLogEntry ( string level , string message )
254+ {
255+ #pragma warning disable CA1869 // Called at most once per process lifetime
256+ var jsonlOptions = new JsonSerializerOptions
257+ {
258+ PropertyNamingPolicy = JsonNamingPolicy . CamelCase ,
259+ DefaultIgnoreCondition = JsonIgnoreCondition . WhenWritingNull
260+ } ;
261+ #pragma warning restore CA1869
262+ var entry = new
263+ {
264+ type = "log" ,
265+ level ,
266+ message ,
267+ timestamp = DateTime . UtcNow . ToString ( "O" , CultureInfo . InvariantCulture )
268+ } ;
269+ return JsonSerializer . Serialize ( entry , jsonlOptions ) ;
270+ }
271+
272+ static bool IsJsonOutputRequested ( string [ ] args )
273+ {
274+ for ( var i = 0 ; i < args . Length ; i ++ )
275+ {
276+ if ( string . Equals ( args [ i ] , DevProxyCommand . OutputOptionName , StringComparison . OrdinalIgnoreCase ) &&
277+ i + 1 < args . Length )
278+ {
279+ return string . Equals ( args [ i + 1 ] , "json" , StringComparison . OrdinalIgnoreCase ) ;
280+ }
281+ }
282+ return false ;
283+ }
284+
154285static string EscapeArgument ( string arg )
155286{
156287 // Simple escaping for command line arguments
0 commit comments