@@ -2,6 +2,7 @@ use anyhow::{Context, Result};
22use chrono:: { DateTime , Utc } ;
33use devenv_eval_cache:: internal_log:: { ActivityType , Field , InternalLog } ;
44use devenv_tui:: { OperationId , init_tui} ;
5+ use std:: collections:: HashMap ;
56use std:: fs:: File ;
67use std:: io:: { BufRead , BufReader } ;
78use std:: path:: PathBuf ;
@@ -11,16 +12,19 @@ use std::sync::{Arc, Mutex};
1112use tokio:: time:: sleep;
1213use tokio_shutdown:: Shutdown ;
1314use tracing:: { debug_span, info, warn} ;
15+ use tracing:: span:: EnteredSpan ;
1416
1517/// Simple replay processor that emits tracing events for Nix logs
1618struct NixLogReplayProcessor {
1719 current_operation_id : Arc < Mutex < Option < OperationId > > > ,
20+ active_spans : Arc < Mutex < HashMap < u64 , EnteredSpan > > > ,
1821}
1922
2023impl NixLogReplayProcessor {
2124 fn new ( ) -> Self {
2225 Self {
2326 current_operation_id : Arc :: new ( Mutex :: new ( None ) ) ,
27+ active_spans : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
2428 }
2529 }
2630
@@ -99,9 +103,10 @@ impl NixLogReplayProcessor {
99103 machine = ?machine
100104 ) ;
101105
102- span. in_scope ( || {
103- info ! ( devenv. log = true , "Building {}" , derivation_name) ;
104- } ) ;
106+ let entered_span = span. entered ( ) ;
107+ if let Ok ( mut spans) = self . active_spans . lock ( ) {
108+ spans. insert ( activity_id, entered_span) ;
109+ }
105110 }
106111 ActivityType :: CopyPath => {
107112 if let ( Some ( Field :: String ( store_path) ) , Some ( Field :: String ( substituter) ) ) =
@@ -121,9 +126,10 @@ impl NixLogReplayProcessor {
121126 substituter = %substituter
122127 ) ;
123128
124- span. in_scope ( || {
125- info ! ( devenv. log = true , "Downloading {}" , package_name) ;
126- } ) ;
129+ let entered_span = span. entered ( ) ;
130+ if let Ok ( mut spans) = self . active_spans . lock ( ) {
131+ spans. insert ( activity_id, entered_span) ;
132+ }
127133 }
128134 }
129135 ActivityType :: QueryPathInfo => {
@@ -145,9 +151,10 @@ impl NixLogReplayProcessor {
145151 substituter = %substituter
146152 ) ;
147153
148- span. in_scope ( || {
149- info ! ( devenv. log = true , "Querying {}" , package_name) ;
150- } ) ;
154+ let entered_span = span. entered ( ) ;
155+ if let Ok ( mut spans) = self . active_spans . lock ( ) {
156+ spans. insert ( activity_id, entered_span) ;
157+ }
151158 }
152159 }
153160 ActivityType :: FetchTree => {
@@ -162,9 +169,10 @@ impl NixLogReplayProcessor {
162169 message = %text
163170 ) ;
164171
165- span. in_scope ( || {
166- info ! ( devenv. log = true , "Fetching {}" , text) ;
167- } ) ;
172+ let entered_span = span. entered ( ) ;
173+ if let Ok ( mut spans) = self . active_spans . lock ( ) {
174+ spans. insert ( activity_id, entered_span) ;
175+ }
168176 }
169177 _ => {
170178 // For other activity types, emit a debug event
@@ -173,9 +181,11 @@ impl NixLogReplayProcessor {
173181 }
174182 }
175183
176- fn handle_activity_stop ( & self , _activity_id : u64 , _success : bool ) {
177- // Activity stop is handled automatically when the span drops
178- // The DevenvTuiLayer will generate the appropriate end events
184+ fn handle_activity_stop ( & self , activity_id : u64 , _success : bool ) {
185+ // Remove the span from active spans - dropping it will close the span
186+ if let Ok ( mut spans) = self . active_spans . lock ( ) {
187+ spans. remove ( & activity_id) ;
188+ }
179189 }
180190
181191 fn handle_activity_result (
@@ -193,51 +203,50 @@ impl NixLogReplayProcessor {
193203 ( fields. first ( ) , fields. get ( 1 ) )
194204 {
195205 let total_bytes = match total_opt {
196- Some ( Field :: Int ( total) ) => Some ( total) ,
206+ Some ( Field :: Int ( total) ) => Some ( * total) ,
197207 _ => None ,
198208 } ;
199209
200- let span = debug_span ! (
210+ // Emit progress event (not a span)
211+ tracing:: event!(
201212 target: "devenv.nix.download" ,
202- "nix_download_progress" ,
213+ tracing :: Level :: DEBUG ,
203214 devenv. ui. message = "download progress" ,
204- devenv. ui. type = "download" ,
215+ devenv. ui. id = "progress" ,
216+ activity_id = activity_id,
217+ bytes_downloaded = * downloaded,
218+ total_bytes = ?total_bytes,
219+ "Download progress: {} / {:?} bytes" ,
220+ downloaded,
221+ total_bytes
222+ ) ;
223+ }
224+ }
225+ ResultType :: SetPhase => {
226+ if let Some ( Field :: String ( phase) ) = fields. first ( ) {
227+ // Emit phase change event
228+ tracing:: event!(
229+ target: "devenv.nix.build" ,
230+ tracing:: Level :: DEBUG ,
231+ devenv. ui. message = "build phase" ,
232+ devenv. ui. id = "phase" ,
205233 activity_id = activity_id,
206- bytes_downloaded = downloaded ,
207- total_bytes = ?total_bytes
234+ phase = phase . as_str ( ) ,
235+ "Build phase: {}" , phase
208236 ) ;
209- span. in_scope ( || {
210- if let Some ( total) = total_bytes {
211- let percent = ( * downloaded as f64 / * total as f64 ) * 100.0 ;
212- tracing:: debug!(
213- "Download progress: {} / {} bytes ({:.1}%)" ,
214- downloaded,
215- total,
216- percent
217- ) ;
218- } else {
219- tracing:: debug!( "Download progress: {} bytes" , downloaded) ;
220- }
221- } ) ;
222237 }
223238 }
224239 ResultType :: BuildLogLine => {
225240 if let Some ( Field :: String ( log_line) ) = fields. first ( ) {
226- let span = debug_span ! (
241+ // Emit build log event (not a span)
242+ tracing:: event!(
227243 target: "devenv.nix.build" ,
228- "build_log" ,
244+ tracing :: Level :: INFO ,
229245 devenv. ui. message = "build log" ,
230- devenv. ui. type = "build" ,
231246 activity_id = activity_id,
232- line = %log_line
247+ line = %log_line,
248+ "Build log: {}" , log_line
233249 ) ;
234- span. in_scope ( || {
235- info ! (
236- target: "devenv.ui.log" ,
237- stdout = %log_line,
238- "Build output: {}" , log_line
239- ) ;
240- } ) ;
241250 }
242251 }
243252 _ => {
@@ -317,10 +326,12 @@ fn parse_log_line(line: &str) -> Result<LogEntry> {
317326async fn main ( ) -> Result < ( ) > {
318327 let shutdown = Shutdown :: new ( ) ;
319328
320- tokio:: select! {
321- _ = run_replay( shutdown. clone( ) ) => { }
322- _ = shutdown. wait_for_shutdown( ) => { }
323- }
329+ // Run replay and then wait for shutdown
330+ run_replay ( shutdown. clone ( ) ) . await ?;
331+
332+ // Keep running until Ctrl+C
333+ eprintln ! ( "Replay complete. Press Ctrl+C to exit." ) ;
334+ shutdown. wait_for_shutdown ( ) . await ;
324335
325336 Ok ( ( ) )
326337}
@@ -372,18 +383,23 @@ async fn run_replay(shutdown: Arc<Shutdown>) -> Result<()> {
372383
373384 // Start TUI in background
374385 let tui_shutdown = shutdown. clone ( ) ;
375- tokio:: spawn ( async move { devenv_tui:: app:: run_app ( model, tui_shutdown) . await } ) ;
386+ let tui_handle = tokio:: spawn ( async move {
387+ match devenv_tui:: app:: run_app ( model, tui_shutdown) . await {
388+ Ok ( _) => eprintln ! ( "TUI exited normally" ) ,
389+ Err ( e) => eprintln ! ( "TUI error: {}" , e) ,
390+ }
391+ } ) ;
376392
377393 // Create main operation
378394 let main_op_id = OperationId :: new ( "replay" ) ;
379395
380- // Start replay operation via tracing
381- info ! (
382- target: "devenv.ui" ,
383- id = %main_op_id,
384- message = format!( "Replaying {} log entries" , entries. len( ) ) ,
385- "Starting log replay"
396+ // Start replay operation via tracing - using a span so the TUI layer can capture it
397+ let replay_span = debug_span ! (
398+ "replay_operation" ,
399+ devenv. ui. message = format!( "Replaying {} log entries" , entries. len( ) ) ,
400+ devenv. ui. id = %main_op_id,
386401 ) ;
402+ let _guard = replay_span. enter ( ) ;
387403
388404 // Set current operation for Nix processor
389405 nix_processor. set_current_operation ( main_op_id. clone ( ) ) ;
@@ -441,9 +457,14 @@ async fn run_replay(shutdown: Arc<Shutdown>) -> Result<()> {
441457 }
442458 }
443459
444- // Give TUI a moment to display the final state
445- sleep ( Duration :: from_millis ( 100 ) ) . await ;
460+ // Keep the span open and let activities continue to be displayed
461+ // Don't drop _guard here - keep the replay operation active
462+ info ! ( "Replay finished. Activities are still visible in the TUI." ) ;
463+
464+ // Check if TUI is still running
465+ if tui_handle. is_finished ( ) {
466+ eprintln ! ( "Warning: TUI task has already exited!" ) ;
467+ }
446468
447- // Span will close automatically when _guard is dropped, completing the operation
448469 Ok ( ( ) )
449470}
0 commit comments