@@ -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,69 @@ impl_record!('a, );
166189#[ cfg( c_bindings) ]
167190impl_record ! ( , ' a) ;
168191
169- /// A trait encapsulating the operations required of a logger.
192+ // Define a struct `SubstringFormatter` that wraps two fields:
193+ // 1. A counter (`usize`) indicating how many unicode characters remain to be written.
194+ // 2. A mutable reference to a `fmt::Formatter` used for writing output.
195+ struct SubstringFormatter < ' fmt : ' r , ' r > ( usize , & ' r mut fmt:: Formatter < ' fmt > ) ;
196+
197+ impl < ' fmt : ' r , ' r > SubstringFormatter < ' fmt , ' r > {
198+ fn new ( length : usize , formatter : & ' r mut fmt:: Formatter < ' fmt > ) -> Self {
199+ debug_assert ! ( length <= 100 ) ;
200+ SubstringFormatter ( length, formatter)
201+ }
202+
203+ // Pads the underlying formatter with spaces until the remaining character count (`self.0`)
204+ fn pad_remaining ( & mut self ) -> fmt:: Result {
205+ // Use a constant string to avoid allocations.
206+ const PAD100 : & str = " " ; // 100 spaces
207+
208+ self . 1 . write_str ( & PAD100 [ ..self . 0 ] ) ?;
209+ self . 0 = 0 ;
210+
211+ Ok ( ( ) )
212+ }
213+ }
214+
215+ impl < ' fmt : ' r , ' r > Write for SubstringFormatter < ' fmt , ' r > {
216+ // Write only up to a certain number of unicode characters (self.0) from the input string `s` to the underlying
217+ // formatter (`self.1`). This handles multi-byte Unicode characters safely.
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`. `char_indices()` yields (byte_index, char) pairs. We
225+ // take `self.0 + 1` characters so we can find the byte boundary where we should stop writing.
226+ for ( pos, _) in s. char_indices ( ) . take ( self . 0 + 1 ) {
227+ char_count += 1 ;
228+ next_char_pos = pos;
229+ }
230+
231+ // Determine where to split the string. If we’ve found at least `self.0 + 1` characters, we’ve reached the
232+ // cutoff point.
233+ let split_pos = if char_count == self . 0 + 1 {
234+ // Reset remaining character counter to zero.
235+ self . 0 = 0 ;
236+ // Stop before the (self.0 + 1)-th character.
237+ next_char_pos
238+ } else {
239+ // Not enough characters in this chunk — reduce remaining count
240+ // and consume the entire string.
241+ self . 0 -= char_count;
242+ s. len ( )
243+ } ;
244+
245+ // Write only the substring up to the split position into the formatter.
246+ self . 1 . write_str ( & s[ ..split_pos] )
247+ }
248+ }
249+
250+ /// A trait encapsulating the operations required of a logger. Keep in mind that log messages might not be entirely
251+ /// self-explanatory and may need accompanying context fields to be fully understood.
170252pub trait Logger {
171- /// Logs the [`Record`].
253+ /// Logs the [`Record`]. Since the record's [`Record::channel_id`] is not embedded in the message body, log
254+ /// implementations should print it alongside the message to keep entries clear.
172255 fn log ( & self , record : Record ) ;
173256}
174257
0 commit comments