@@ -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,64 @@ 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+ let mut char_count = 0 ;
220+ let mut next_char_byte_pos = 0 ;
221+
222+ // Iterate over the unicode character boundaries in `s`. We take one more than the number of remaining
223+ // characters so we can find the byte boundary where we should stop writing.
224+ for ( pos, _) in s. char_indices ( ) . take ( self . remaining_chars + 1 ) {
225+ char_count += 1 ;
226+ next_char_byte_pos = pos;
227+ }
228+
229+ // Determine where to split the string.
230+ let at_cut_off_point = char_count == self . remaining_chars + 1 ;
231+ let split_pos = if at_cut_off_point {
232+ self . remaining_chars = 0 ;
233+ next_char_byte_pos
234+ } else {
235+ // Not enough characters in this chunk.
236+ self . remaining_chars -= char_count;
237+ s. len ( )
238+ } ;
239+
240+ // Write only the substring up to the split position into the formatter.
241+ self . fmt . write_str ( & s[ ..split_pos] )
242+ }
243+ }
244+
245+ /// A trait encapsulating the operations required of a logger. Keep in mind that log messages might not be entirely
246+ /// self-explanatory and may need accompanying context fields to be fully understood.
170247pub trait Logger {
171- /// Logs the [`Record`].
248+ /// Logs the [`Record`]. Since the record's [`Record::channel_id`] is not embedded in the message body, log
249+ /// implementations should print it alongside the message to keep entries clear.
172250 fn log ( & self , record : Record ) ;
173251}
174252
0 commit comments