@@ -28,6 +28,18 @@ lazy_static! {
2828 pub static ref SPINNER : Arc <Mutex <Option <ProgressBar >>> = Arc :: new( Mutex :: new( None ) ) ;
2929 pub static ref IS_TTY : bool = std:: io:: IsTerminal :: is_terminal( & std:: io:: stdout( ) ) ;
3030 static ref CURRENT_GROUP_NAME : Arc <Mutex <Option <String >>> = Arc :: new( Mutex :: new( None ) ) ;
31+
32+ /// Log records deferred while the rolling buffer owns the terminal.
33+ /// Flushed in `draw_frame` before each redraw.
34+ static ref DEFERRED_LOGS : Mutex <Vec <DeferredLog >> = Mutex :: new( Vec :: new( ) ) ;
35+ }
36+
37+ /// A snapshot of a log record that can be stored across the rolling-buffer
38+ /// lifetime (the original `log::Record` borrows data and cannot be kept).
39+ struct DeferredLog {
40+ level : log:: Level ,
41+ message : String ,
42+ target : String ,
3143}
3244
3345/// Hide the progress bar temporarily, execute `f`, then redraw the progress bar.
@@ -125,6 +137,25 @@ impl Log for LocalLogger {
125137 return ;
126138 }
127139
140+ // When the rolling buffer is active it owns the terminal region and uses
141+ // cursor manipulation to redraw. Any direct stderr output would corrupt
142+ // the display, so we defer log records and flush them before each redraw.
143+ {
144+ use rolling_buffer:: ROLLING_BUFFER ;
145+ if let Ok ( guard) = ROLLING_BUFFER . try_lock ( ) {
146+ if guard. as_ref ( ) . is_some_and ( |rb| rb. is_active ( ) ) {
147+ if let Ok ( mut deferred) = DEFERRED_LOGS . try_lock ( ) {
148+ deferred. push ( DeferredLog {
149+ level : record. level ( ) ,
150+ message : format ! ( "{}" , record. args( ) ) ,
151+ target : record. target ( ) . to_string ( ) ,
152+ } ) ;
153+ }
154+ return ;
155+ }
156+ }
157+ }
158+
128159 suspend_progress_bar ( || print_record ( record) ) ;
129160 }
130161
@@ -183,36 +214,69 @@ fn indent_lines(s: &str, indent: &str) -> String {
183214
184215/// Print a log record to the console with the appropriate style
185216fn print_record ( record : & log:: Record ) {
186- match record. level ( ) {
217+ eprintln ! (
218+ "{}" ,
219+ format_log(
220+ record. level( ) ,
221+ & format!( "{}" , record. args( ) ) ,
222+ record. target( ) ,
223+ )
224+ ) ;
225+ }
226+
227+ /// Format a log entry with the appropriate style for its level.
228+ fn format_log ( level : log:: Level , message : & str , target : & str ) -> String {
229+ match level {
187230 log:: Level :: Error => {
188231 let prefix = style ( "\u{f00d} " ) . red ( ) . bold ( ) ;
189- let msg = indent_lines ( & format ! ( "{}" , record . args ( ) ) , " " ) ;
232+ let msg = indent_lines ( message , " " ) ;
190233 let msg = Style :: new ( ) . red ( ) . apply_to ( msg) ;
191- eprintln ! ( " {prefix} {msg}" ) ;
234+ format ! ( " {prefix} {msg}" )
192235 }
193236 log:: Level :: Warn => {
194237 let prefix = style ( "\u{f071} " ) . yellow ( ) ;
195- let msg = indent_lines ( & format ! ( "{}" , record . args ( ) ) , " " ) ;
238+ let msg = indent_lines ( message , " " ) ;
196239 let msg = Style :: new ( ) . yellow ( ) . apply_to ( msg) ;
197- eprintln ! ( " {prefix} {msg}" ) ;
240+ format ! ( " {prefix} {msg}" )
198241 }
199242 log:: Level :: Info => {
200- let msg = indent_lines ( & format ! ( "{}" , record . args ( ) ) , " " ) ;
243+ let msg = indent_lines ( message , " " ) ;
201244 let msg = Style :: new ( ) . white ( ) . apply_to ( msg) ;
202- eprintln ! ( " {msg}" ) ;
245+ format ! ( " {msg}" )
203246 }
204247 log:: Level :: Debug => {
205248 let prefix = style ( "\u{00B7} " ) . dim ( ) ;
206- let msg = indent_lines ( & format ! ( "{}" , record . args ( ) ) , " " ) ;
249+ let msg = indent_lines ( message , " " ) ;
207250 let msg = Style :: new ( ) . blue ( ) . dim ( ) . apply_to ( msg) ;
208- eprintln ! ( " {prefix} {msg}" ) ;
251+ format ! ( " {prefix} {msg}" )
209252 }
210253 log:: Level :: Trace => {
211- let raw = format ! ( "[TRACE::{}] {}" , record . target ( ) , record . args ( ) ) ;
254+ let raw = format ! ( "[TRACE::{target }] {message}" ) ;
212255 let msg = indent_lines ( & raw , " " ) ;
213256 let msg = Style :: new ( ) . black ( ) . dim ( ) . apply_to ( msg) ;
214- eprintln ! ( " {msg}" ) ;
257+ format ! ( " {msg}" )
258+ }
259+ }
260+ }
261+
262+ /// Flush all log records that were deferred while the rolling buffer was active.
263+ /// Each line is cleared before writing to avoid leftover characters from the
264+ /// rolling buffer frame being overwritten.
265+ pub ( crate ) fn flush_deferred_logs ( term : & console:: Term ) {
266+ let logs: Vec < DeferredLog > = {
267+ match DEFERRED_LOGS . try_lock ( ) {
268+ Ok ( mut deferred) => std:: mem:: take ( & mut * deferred) ,
269+ Err ( _) => return ,
215270 }
271+ } ;
272+ if !logs. is_empty ( ) {
273+ // Clear from cursor to end of screen so that wrapped lines from the
274+ // rolling buffer frame don't leave artifacts behind deferred log output.
275+ term. clear_to_end_of_screen ( ) . ok ( ) ;
276+ }
277+ for log in & logs {
278+ let formatted = format_log ( log. level , & log. message , & log. target ) ;
279+ term. write_line ( & formatted) . ok ( ) ;
216280 }
217281}
218282
0 commit comments