11//! This module contains utilities for logging and progress bar handling.
22use std:: {
33 borrow:: Cow ,
4+ collections:: hash_map:: DefaultHasher ,
45 future:: Future ,
6+ hash:: { Hash , Hasher } ,
57 io,
68 str:: FromStr ,
79 sync:: { Arc , Mutex } ,
810 time:: { Duration , Instant } ,
911} ;
1012
1113use clap_verbosity_flag:: { InfoLevel , Verbosity } ;
12- use console:: style;
14+ use console:: { Style , style} ;
1315use indicatif:: {
1416 HumanBytes , HumanDuration , MultiProgress , ProgressBar , ProgressState , ProgressStyle ,
1517} ;
@@ -29,6 +31,29 @@ use tracing_subscriber::{
2931
3032use crate :: consts;
3133
34+ /// A palette of colors used for different package builds.
35+ /// These are chosen to be visually distinct and readable on both light and dark terminals.
36+ const SPAN_COLOR_PALETTE : & [ console:: Color ] = & [
37+ console:: Color :: Cyan ,
38+ console:: Color :: Green ,
39+ console:: Color :: Yellow ,
40+ console:: Color :: Blue ,
41+ console:: Color :: Magenta ,
42+ console:: Color :: Color256 ( 208 ) , // Orange
43+ console:: Color :: Color256 ( 141 ) , // Light purple
44+ console:: Color :: Color256 ( 43 ) , // Teal
45+ ] ;
46+
47+ /// Returns a deterministic color for a given package identifier.
48+ /// The color is chosen by hashing the identifier and selecting from the palette.
49+ fn get_span_color ( identifier : & str ) -> Style {
50+ let mut hasher = DefaultHasher :: new ( ) ;
51+ identifier. hash ( & mut hasher) ;
52+ let hash = hasher. finish ( ) ;
53+ let color_index = ( hash as usize ) % SPAN_COLOR_PALETTE . len ( ) ;
54+ Style :: new ( ) . fg ( SPAN_COLOR_PALETTE [ color_index] )
55+ }
56+
3257/// A custom formatter for tracing events.
3358pub struct TracingFormatter ;
3459
@@ -56,30 +81,44 @@ where
5681 }
5782}
5883
59- #[ derive( Debug ) ]
6084struct SpanInfo {
6185 id : Id ,
6286 start_time : Instant ,
6387 header : String ,
6488 header_printed : bool ,
89+ /// The color style used for this span's tree characters.
90+ /// This is inherited from parent spans or computed from the package identifier.
91+ color : Style ,
6592}
6693
67- #[ derive( Debug , Default ) ]
94+ #[ derive( Default ) ]
6895struct SharedState {
6996 span_stack : Vec < SpanInfo > ,
7097 warnings : Vec < String > ,
7198}
7299
100+ impl std:: fmt:: Debug for SharedState {
101+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
102+ f. debug_struct ( "SharedState" )
103+ . field ( "span_stack_len" , & self . span_stack . len ( ) )
104+ . field ( "warnings" , & self . warnings )
105+ . finish ( )
106+ }
107+ }
108+
73109struct CustomVisitor < ' a > {
74110 writer : & ' a mut dyn io:: Write ,
75111 result : io:: Result < ( ) > ,
112+ /// Captures the span_color field for deterministic color computation.
113+ span_color : Option < String > ,
76114}
77115
78116impl < ' a > CustomVisitor < ' a > {
79117 fn new ( writer : & ' a mut dyn io:: Write ) -> Self {
80118 Self {
81119 writer,
82120 result : Ok ( ( ) ) ,
121+ span_color : None ,
83122 }
84123 }
85124}
@@ -90,6 +129,11 @@ impl field::Visit for CustomVisitor<'_> {
90129 return ;
91130 }
92131
132+ // Capture span_color field for deterministic color computation
133+ if field. name ( ) == "span_color" {
134+ self . span_color = Some ( value. to_string ( ) ) ;
135+ }
136+
93137 self . record_debug ( field, & format_args ! ( "{}" , value) )
94138 }
95139
@@ -146,12 +190,13 @@ fn chunk_string_without_ansi(input: &str, max_chunk_length: usize) -> Vec<String
146190 chunks
147191}
148192
149- fn indent_levels ( indent : usize ) -> String {
193+ /// Creates an indentation string with vertical bars colored according to each span's color.
194+ fn indent_levels_colored ( span_stack : & [ SpanInfo ] ) -> String {
150195 let mut s = String :: new ( ) ;
151- for _ in 0 ..indent {
152- s. push_str ( " │" ) ;
196+ for span_info in span_stack {
197+ s. push_str ( & format ! ( " {}" , span_info . color . apply_to ( "│" ) ) ) ;
153198 }
154- format ! ( "{}" , style ( s ) . cyan ( ) )
199+ s
155200}
156201
157202impl < S > Layer < S > for LoggingOutputHandler
@@ -168,24 +213,45 @@ where
168213
169214 if let Some ( span) = ctx. span ( id) {
170215 let mut s = Vec :: new ( ) ;
171- let mut w = io:: Cursor :: new ( & mut s) ;
172- attrs. record ( & mut CustomVisitor :: new ( & mut w) ) ;
173- let s = String :: from_utf8_lossy ( w. get_ref ( ) ) ;
216+ let color_key = {
217+ let mut w = io:: Cursor :: new ( & mut s) ;
218+ let mut visitor = CustomVisitor :: new ( & mut w) ;
219+ attrs. record ( & mut visitor) ;
220+ visitor. span_color
221+ } ;
222+ let s = String :: from_utf8_lossy ( & s) ;
174223
175224 let name = if s. is_empty ( ) {
176225 span. name ( ) . to_string ( )
177226 } else {
178227 format ! ( "{}{}" , span. name( ) , s)
179228 } ;
180229
181- let indent = indent_levels ( state. span_stack . len ( ) ) ;
182- let header = format ! ( "{indent}\n {indent} {} {}" , style( "╭─" ) . cyan( ) , name) ;
230+ // Determine the color for this span:
231+ // - If there's a span_color field, compute color from it
232+ // - Otherwise, inherit from parent span
233+ // - If no parent, use gray (for initial/setup output)
234+ let span_color = if let Some ( ref key) = color_key {
235+ get_span_color ( key)
236+ } else if let Some ( parent) = state. span_stack . last ( ) {
237+ parent. color . clone ( )
238+ } else {
239+ Style :: new ( ) . dim ( )
240+ } ;
241+
242+ let indent = indent_levels_colored ( & state. span_stack ) ;
243+ let header = format ! (
244+ "{indent}\n {indent} {} {}" ,
245+ span_color. apply_to( "╭─" ) ,
246+ name
247+ ) ;
183248
184249 state. span_stack . push ( SpanInfo {
185250 id : id. clone ( ) ,
186251 start_time : Instant :: now ( ) ,
187252 header,
188253 header_printed : false ,
254+ color : span_color,
189255 } ) ;
190256 }
191257 }
@@ -196,26 +262,34 @@ where
196262 if let Some ( pos) = state. span_stack . iter ( ) . position ( |info| & info. id == id) {
197263 let elapsed = state. span_stack [ pos] . start_time . elapsed ( ) ;
198264 let header_printed = state. span_stack [ pos] . header_printed ;
265+ let span_color = state. span_stack [ pos] . color . clone ( ) ;
266+
267+ // Get the indent before truncating (parent spans only)
268+ let indent = indent_levels_colored ( & state. span_stack [ ..pos] ) ;
269+ // For indent_plus_one, we need to include this span's color too
270+ let indent_plus_one = format ! (
271+ "{} {}" ,
272+ indent,
273+ span_color. apply_to( "│" )
274+ ) ;
275+
199276 state. span_stack . truncate ( pos) ;
200277
201278 if !header_printed {
202279 return ;
203280 }
204281
205- let indent = indent_levels ( pos) ;
206- let indent_plus_one = indent_levels ( pos + 1 ) ;
207-
208282 eprintln ! (
209283 "{indent_plus_one}\n {indent} {} (took {})" ,
210- style ( "╰───────────────────" ) . cyan ( ) ,
284+ span_color . apply_to ( "╰───────────────────" ) ,
211285 HumanDuration ( elapsed)
212286 ) ;
213287 }
214288 }
215289
216290 fn on_event ( & self , event : & Event < ' _ > , _ctx : Context < ' _ , S > ) {
217291 let mut state = self . state . lock ( ) . unwrap ( ) ;
218- let indent = indent_levels ( state. span_stack . len ( ) ) ;
292+ let indent = indent_levels_colored ( & state. span_stack ) ;
219293
220294 // Print pending headers
221295 for span_info in & mut state. span_stack {
@@ -299,10 +373,10 @@ impl Default for LoggingOutputHandler {
299373
300374impl LoggingOutputHandler {
301375 /// Return a string with the current indentation level (bars added to the
302- /// front of the string).
376+ /// front of the string), colored according to each span's color .
303377 pub fn with_indent_levels ( & self , template : & str ) -> String {
304378 let state = self . state . lock ( ) . unwrap ( ) ;
305- let indent_str = indent_levels ( state. span_stack . len ( ) ) ;
379+ let indent_str = indent_levels_colored ( & state. span_stack ) ;
306380 format ! ( "{} {}" , indent_str, template)
307381 }
308382
0 commit comments