@@ -18,6 +18,7 @@ use bitcoin::secp256k1::PublicKey;
1818use core:: cmp;
1919use core:: fmt;
2020use core:: fmt:: Display ;
21+ use core:: fmt:: Write ;
2122use core:: ops:: Deref ;
2223
2324use crate :: ln:: types:: ChannelId ;
@@ -107,7 +108,8 @@ pub struct Record<$($args)?> {
107108 /// generated.
108109 pub peer_id: Option <PublicKey >,
109110 /// The channel id of the channel pertaining to the logged record. May be a temporary id before
110- /// the channel has been funded.
111+ /// the channel has been funded. Since channel_id is not repeated in the message body,
112+ /// include it in the log output so entries remain clear.
111113 pub channel_id: Option <ChannelId >,
112114 #[ cfg( not( c_bindings) ) ]
113115 /// The message body.
@@ -156,8 +158,63 @@ impl<$($args)?> Record<$($args)?> {
156158
157159impl <$( $args) ?> Display for Record <$( $args) ?> {
158160 fn fmt( & self , f: & mut fmt:: Formatter <' _>) -> fmt:: Result {
159- let context = format!( "{:<5} [{}:{}]" , self . level, self . module_path, self . line) ;
160- write!( f, "{:<48} {}" , context, self . args)
161+ let mut context_formatter = SubstringFormatter :: new( 48 , f) ;
162+ write!( & mut context_formatter, "{:<5} [{}:{}]" , self . level, self . module_path, self . line) ?;
163+ context_formatter. pad_remaining( ) ?;
164+
165+ let mut channel_formatter = SubstringFormatter :: new( 9 , f) ;
166+ if let Some ( channel_id) = self . channel_id {
167+ write!( channel_formatter, "ch:{}" , channel_id) ?;
168+ }
169+ channel_formatter. pad_remaining( ) ?;
170+
171+ #[ cfg( not( test) ) ]
172+ {
173+ let mut peer_formatter = SubstringFormatter :: new( 9 , f) ;
174+ if let Some ( peer_id) = self . peer_id {
175+ write!( peer_formatter, " p:{}" , peer_id) ?;
176+ }
177+ peer_formatter. pad_remaining( ) ?;
178+
179+ let mut payment_formatter = SubstringFormatter :: new( 9 , f) ;
180+ if let Some ( payment_hash) = self . payment_hash {
181+ write!( payment_formatter, " h:{}" , payment_hash) ?;
182+ }
183+ payment_formatter. pad_remaining( ) ?;
184+
185+ write!( f, " {}" , self . args)
186+ }
187+
188+ #[ cfg( test) ]
189+ {
190+ write!( f, " {}" , self . args) ?;
191+
192+ let mut open_bracket_written = false ;
193+ if let Some ( peer_id) = self . peer_id {
194+ write!( f, " [" ) ?;
195+ open_bracket_written = true ;
196+ let mut peer_formatter = SubstringFormatter :: new( 8 , f) ;
197+ write!( peer_formatter, "p:{}" , peer_id) ?;
198+ }
199+
200+ if let Some ( payment_hash) = self . payment_hash {
201+ if !open_bracket_written {
202+ write!( f, " [" ) ?;
203+ open_bracket_written = true ;
204+ } else {
205+ write!( f, " " ) ?;
206+ }
207+
208+ let mut payment_formatter = SubstringFormatter :: new( 8 , f) ;
209+ write!( payment_formatter, "h:{}" , payment_hash) ?;
210+ }
211+
212+ if open_bracket_written {
213+ write!( f, "]" ) ?;
214+ }
215+
216+ Ok ( ( ) )
217+ }
161218 }
162219}
163220} }
@@ -166,9 +223,64 @@ impl_record!('a, );
166223#[ cfg( c_bindings) ]
167224impl_record ! ( , ' a) ;
168225
169- /// A trait encapsulating the operations required of a logger.
226+ // Writes only up to a certain number of unicode characters to the underlying formatter. This handles multi-byte Unicode
227+ // characters safely.
228+ struct SubstringFormatter < ' fmt : ' r , ' r > {
229+ remaining_chars : usize ,
230+ fmt : & ' r mut fmt:: Formatter < ' fmt > ,
231+ }
232+
233+ impl < ' fmt : ' r , ' r > SubstringFormatter < ' fmt , ' r > {
234+ fn new ( length : usize , formatter : & ' r mut fmt:: Formatter < ' fmt > ) -> Self {
235+ debug_assert ! ( length <= 100 ) ;
236+ SubstringFormatter { remaining_chars : length, fmt : formatter }
237+ }
238+
239+ // Pads the underlying formatter with spaces until the remaining character count.
240+ fn pad_remaining ( & mut self ) -> fmt:: Result {
241+ // Use a constant string to avoid allocations.
242+ const PAD100 : & str = " " ; // 100 spaces
243+
244+ self . fmt . write_str ( & PAD100 [ ..self . remaining_chars ] ) ?;
245+ self . remaining_chars = 0 ;
246+
247+ Ok ( ( ) )
248+ }
249+ }
250+
251+ impl < ' fmt : ' r , ' r > Write for SubstringFormatter < ' fmt , ' r > {
252+ fn write_str ( & mut self , s : & str ) -> fmt:: Result {
253+ let mut char_count = 0 ;
254+ let mut next_char_byte_pos = 0 ;
255+
256+ // Iterate over the unicode character boundaries in `s`. We take one more than the number of remaining
257+ // characters so we can find the byte boundary where we should stop writing.
258+ for ( pos, _) in s. char_indices ( ) . take ( self . remaining_chars + 1 ) {
259+ char_count += 1 ;
260+ next_char_byte_pos = pos;
261+ }
262+
263+ // Determine where to split the string.
264+ let at_cut_off_point = char_count == self . remaining_chars + 1 ;
265+ let split_pos = if at_cut_off_point {
266+ self . remaining_chars = 0 ;
267+ next_char_byte_pos
268+ } else {
269+ // Not enough characters in this chunk.
270+ self . remaining_chars -= char_count;
271+ s. len ( )
272+ } ;
273+
274+ // Write only the substring up to the split position into the formatter.
275+ self . fmt . write_str ( & s[ ..split_pos] )
276+ }
277+ }
278+
279+ /// A trait encapsulating the operations required of a logger. Keep in mind that log messages might not be entirely
280+ /// self-explanatory and may need accompanying context fields to be fully understood.
170281pub trait Logger {
171- /// Logs the [`Record`].
282+ /// Logs the [`Record`]. Since the record's [`Record::channel_id`] is not embedded in the message body, log
283+ /// implementations should print it alongside the message to keep entries clear.
172284 fn log ( & self , record : Record ) ;
173285}
174286
0 commit comments