@@ -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,29 @@ 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 peer_formatter = SubstringFormatter :: new( 8 , f) ;
166+ if let Some ( peer_id) = self . peer_id {
167+ write!( peer_formatter, "p:{}" , peer_id) ?;
168+ }
169+ peer_formatter. pad_remaining( ) ?;
170+
171+ let mut channel_formatter = SubstringFormatter :: new( 10 , f) ;
172+ if let Some ( channel_id) = self . channel_id {
173+ write!( channel_formatter, " ch:{}" , channel_id) ?;
174+ }
175+ channel_formatter. pad_remaining( ) ?;
176+
177+ let mut payment_formatter = SubstringFormatter :: new( 9 , f) ;
178+ if let Some ( payment_hash) = self . payment_hash {
179+ write!( payment_formatter, " h:{}" , payment_hash) ?;
180+ }
181+ payment_formatter. pad_remaining( ) ?;
182+
183+ write!( f, " {}" , self . args)
161184 }
162185}
163186} }
@@ -166,9 +189,67 @@ impl_record!('a, );
166189#[ cfg( c_bindings) ]
167190impl_record ! ( , ' a) ;
168191
169- /// A trait encapsulating the operations required of a logger.
192+ // Writes only up to a certain number of unicode characters to the underlying formatter. This handles multi-byte Unicode
193+ // characters safely.
194+ struct SubstringFormatter < ' fmt : ' r , ' r > {
195+ remaining_chars : usize ,
196+ fmt : & ' r mut fmt:: Formatter < ' fmt > ,
197+ }
198+
199+ impl < ' fmt : ' r , ' r > SubstringFormatter < ' fmt , ' r > {
200+ fn new ( length : usize , formatter : & ' r mut fmt:: Formatter < ' fmt > ) -> Self {
201+ debug_assert ! ( length <= 100 ) ;
202+ SubstringFormatter { remaining_chars : length, fmt : formatter }
203+ }
204+
205+ // Pads the underlying formatter with spaces until the remaining character count.
206+ fn pad_remaining ( & mut self ) -> fmt:: Result {
207+ // Use a constant string to avoid allocations.
208+ const PAD100 : & str = " " ; // 100 spaces
209+
210+ self . fmt . write_str ( & PAD100 [ ..self . remaining_chars ] ) ?;
211+ self . remaining_chars = 0 ;
212+
213+ Ok ( ( ) )
214+ }
215+ }
216+
217+ impl < ' fmt : ' r , ' r > Write for SubstringFormatter < ' fmt , ' r > {
218+ fn write_str ( & mut self , s : & str ) -> fmt:: Result {
219+ // Track how many characters we've counted so far.
220+ let mut char_count = 0 ;
221+ // Track the byte position of the next character boundary.
222+ let mut next_char_pos = 0 ;
223+
224+ // Iterate over the unicode character boundaries in `s`. We take one more than the number of remaining
225+ // characters so we can find the byte boundary where we should stop writing.
226+ for ( pos, _) in s. char_indices ( ) . take ( self . remaining_chars + 1 ) {
227+ char_count += 1 ;
228+ next_char_pos = pos;
229+ }
230+
231+ // Determine where to split the string.
232+ let at_cut_off_point = char_count == self . remaining_chars + 1 ;
233+ let split_pos = if at_cut_off_point {
234+ self . remaining_chars = 0 ;
235+ // Stop before the (self.remaining + 1)-th character.
236+ next_char_pos
237+ } else {
238+ // Not enough characters in this chunk, reduce remaining count and consume the entire string.
239+ self . remaining_chars -= char_count;
240+ s. len ( )
241+ } ;
242+
243+ // Write only the substring up to the split position into the formatter.
244+ self . fmt . write_str ( & s[ ..split_pos] )
245+ }
246+ }
247+
248+ /// A trait encapsulating the operations required of a logger. Keep in mind that log messages might not be entirely
249+ /// self-explanatory and may need accompanying context fields to be fully understood.
170250pub trait Logger {
171- /// Logs the [`Record`].
251+ /// Logs the [`Record`]. Since the record's [`Record::channel_id`] is not embedded in the message body, log
252+ /// implementations should print it alongside the message to keep entries clear.
172253 fn log ( & self , record : Record ) ;
173254}
174255
0 commit comments