pinocchio_log/
lib.rs

1//! Lightweight log utility for Solana programs.
2//!
3//! This crate provides a `Logger` struct that can be used to efficiently log messages
4//! in a Solana program. The `Logger` struct is a wrapper around a fixed-size buffer,
5//! where types that implement the `Log` trait can be appended to the buffer.
6//!
7//! The `Logger` struct is generic over the size of the buffer, and the buffer size
8//! should be chosen based on the expected size of the log messages. When the buffer is
9//! full, the log message will be truncated. This is represented by the `@` character
10//! at the end of the log message.
11//!
12//! # Example
13//!
14//! Creating a `Logger` with a buffer size of `100` bytes, and appending a string and an
15//! `u64` value:
16//!
17//! ```
18//! use pinocchio_log::logger::Logger;
19//!
20//! let mut logger = Logger::<100>::default();
21//! logger.append("balance=");
22//! logger.append(1_000_000_000);
23//! logger.log();
24//!
25//! // Clear the logger buffer.
26//! logger.clear();
27//!
28//! logger.append(&["Hello ", "world!"]);
29//! logger.log();
30//! ```
31//!
32//! It also support adding precision to numeric types:
33//!
34//! ```
35//! use pinocchio_log::logger::{Argument, Logger};
36//!
37//! let mut logger = Logger::<100>::default();
38//!
39//! let lamports = 1_000_000_000u64;
40//!
41//! logger.append("balance (SOL)=");
42//! logger.append_with_args(lamports, &[Argument::Precision(9)]);
43//! logger.log();
44//! ```
45
46#![no_std]
47
48pub mod logger;
49
50#[cfg(feature = "macro")]
51pub use pinocchio_log_macro::*;
52
53#[cfg(test)]
54mod tests {
55    use crate::logger::{Argument, Logger};
56
57    /// Helper macro to generate test cases for numeric types.
58    ///
59    /// The test cases are generated for the given type and buffer size. The
60    /// assert compares that the logger buffer length is less than or equal to
61    /// the maximum length.
62    macro_rules! generate_numeric_test_case {
63        ( $value:expr, $max_len:expr, $($size:expr),+ $(,)? ) => {
64            $(
65                let mut logger = Logger::<$size>::default();
66                logger.append($value);
67                assert!((*logger).len() <= $max_len);
68            )*
69        };
70    }
71
72    /// Helper macro to generate test cases for `str` type.
73    ///
74    /// The test cases are generated for the given value and buffer size. The
75    /// assert compares that the logger buffer length is equal to the minimum
76    /// between the buffer size and the `str` length.
77    macro_rules! generate_str_test_case {
78        ( $str:expr, $($size:expr),+ $(,)? ) => {
79            $(
80                let mut logger = Logger::<$size>::default();
81                logger.append(core::str::from_utf8($str).unwrap());
82                assert_eq!((*logger).len(), core::cmp::min($str.len(), $size));
83            )*
84        };
85    }
86
87    #[test]
88    fn test_logger() {
89        let mut logger = Logger::<100>::default();
90        logger.append("Hello ");
91        logger.append("world!");
92
93        assert!(&*logger == "Hello world!".as_bytes());
94
95        logger.clear();
96
97        logger.append("balance=");
98        logger.append(1_000_000_000);
99
100        assert!(&*logger == "balance=1000000000".as_bytes());
101    }
102
103    #[test]
104    fn test_logger_truncated() {
105        let mut logger = Logger::<8>::default();
106        logger.append("Hello ");
107        logger.append("world!");
108
109        assert!(&*logger == "Hello w@".as_bytes());
110
111        let mut logger = Logger::<12>::default();
112
113        logger.append("balance=");
114        logger.append(1_000_000_000);
115
116        assert!(&*logger == "balance=100@".as_bytes());
117    }
118
119    #[test]
120    fn test_logger_slice() {
121        let mut logger = Logger::<20>::default();
122        logger.append(&["Hello ", "world!"]);
123
124        assert!(&*logger == "[\"Hello \", \"world!\"]".as_bytes());
125
126        let mut logger = Logger::<20>::default();
127        logger.append(&[123, 456]);
128
129        assert!(&*logger == "[123, 456]".as_bytes());
130    }
131
132    #[test]
133    fn test_logger_truncated_slice() {
134        let mut logger = Logger::<5>::default();
135        logger.append(&["Hello ", "world!"]);
136
137        assert!(&*logger == "[\"He@".as_bytes());
138
139        let mut logger = Logger::<4>::default();
140        logger.append(&[123, 456]);
141
142        assert!(&*logger == "[12@".as_bytes());
143    }
144
145    #[test]
146    fn test_logger_signed() {
147        let mut logger = Logger::<2>::default();
148        logger.append(-2);
149
150        assert!(&*logger == "-2".as_bytes());
151
152        let mut logger = Logger::<5>::default();
153        logger.append(-200_000_000);
154
155        assert!(&*logger == "-200@".as_bytes());
156    }
157
158    #[test]
159    fn test_logger_with_precision() {
160        let mut logger = Logger::<10>::default();
161
162        logger.append_with_args(200_000_000u64, &[Argument::Precision(2)]);
163        assert!(&*logger == "2000000.00".as_bytes());
164
165        logger.clear();
166
167        logger.append_with_args(2_000_000_000u64, &[Argument::Precision(2)]);
168        assert!(&*logger == "20000000.@".as_bytes());
169
170        logger.clear();
171
172        logger.append_with_args(2_000_000_000u64, &[Argument::Precision(5)]);
173        assert!(&*logger == "20000.000@".as_bytes());
174
175        logger.clear();
176
177        logger.append_with_args(2_000_000_000u64, &[Argument::Precision(10)]);
178        assert!(&*logger == "0.2000000@".as_bytes());
179
180        logger.clear();
181
182        logger.append_with_args(2u64, &[Argument::Precision(6)]);
183        assert!(&*logger == "0.000002".as_bytes());
184
185        logger.clear();
186
187        logger.append_with_args(2u64, &[Argument::Precision(9)]);
188        assert!(&*logger == "0.0000000@".as_bytes());
189
190        logger.clear();
191
192        logger.append_with_args(-2000000i32, &[Argument::Precision(6)]);
193        assert!(&*logger == "-2.000000".as_bytes());
194
195        logger.clear();
196
197        logger.append_with_args(-2i64, &[Argument::Precision(9)]);
198        assert!(&*logger == "-0.000000@".as_bytes());
199
200        logger.clear();
201
202        // This should have no effect since it is a string.
203        logger.append_with_args("0123456789", &[Argument::Precision(2)]);
204        assert!(&*logger == "0123456789".as_bytes());
205
206        logger.clear();
207
208        logger.append_with_args(2u8, &[Argument::Precision(8)]);
209        assert!(&*logger == "0.00000002".as_bytes());
210
211        logger.clear();
212
213        logger.append_with_args(2u8, &[Argument::Precision(u8::MAX)]);
214        assert!(&*logger == "0.0000000@".as_bytes());
215
216        let mut logger = Logger::<20>::default();
217
218        logger.append_with_args(2u8, &[Argument::Precision(u8::MAX)]);
219        assert!(&*logger == "0.00000000000000000@".as_bytes());
220
221        logger.clear();
222
223        logger.append_with_args(20_000u16, &[Argument::Precision(10)]);
224        assert!(&*logger == "0.0000020000".as_bytes());
225
226        let mut logger = Logger::<3>::default();
227
228        logger.append_with_args(2u64, &[Argument::Precision(u8::MAX)]);
229        assert!(&*logger == "0.@".as_bytes());
230
231        logger.clear();
232
233        logger.append_with_args(2u64, &[Argument::Precision(1)]);
234        assert!(&*logger == "0.2".as_bytes());
235
236        logger.clear();
237
238        logger.append_with_args(-2i64, &[Argument::Precision(1)]);
239        assert!(&*logger == "-0@".as_bytes());
240
241        let mut logger = Logger::<1>::default();
242
243        logger.append_with_args(-2i64, &[Argument::Precision(1)]);
244        assert!(&*logger == "@".as_bytes());
245
246        let mut logger = Logger::<2>::default();
247
248        logger.append_with_args(-2i64, &[Argument::Precision(1)]);
249        assert!(&*logger == "-@".as_bytes());
250
251        let mut logger = Logger::<20>::default();
252
253        logger.append_with_args(u64::MAX, &[Argument::Precision(u8::MAX)]);
254        assert!(&*logger == "0.00000000000000000@".as_bytes());
255
256        // 255 precision + leading 0 + decimal point
257        let mut logger = Logger::<257>::default();
258        logger.append_with_args(u64::MAX, &[Argument::Precision(u8::MAX)]);
259        assert!(logger.starts_with("0.00000000000000".as_bytes()));
260        assert!(logger.ends_with("18446744073709551615".as_bytes()));
261
262        logger.clear();
263
264        logger.append_with_args(u32::MAX, &[Argument::Precision(u8::MAX)]);
265        assert!(logger.starts_with("0.00000000000000".as_bytes()));
266        assert!(logger.ends_with("4294967295".as_bytes()));
267
268        logger.clear();
269
270        logger.append_with_args(u16::MAX, &[Argument::Precision(u8::MAX)]);
271        assert!(logger.starts_with("0.00000000000000".as_bytes()));
272        assert!(logger.ends_with("65535".as_bytes()));
273
274        logger.clear();
275
276        logger.append_with_args(u8::MAX, &[Argument::Precision(u8::MAX)]);
277        assert!(logger.starts_with("0.00000000000000".as_bytes()));
278        assert!(logger.ends_with("255".as_bytes()));
279
280        // 255 precision + sign + leading 0 + decimal point
281        let mut logger = Logger::<258>::default();
282        logger.append_with_args(i64::MIN, &[Argument::Precision(u8::MAX)]);
283        assert!(logger.starts_with("-0.00000000000000".as_bytes()));
284        assert!(logger.ends_with("9223372036854775808".as_bytes()));
285
286        logger.clear();
287
288        logger.append_with_args(i32::MIN, &[Argument::Precision(u8::MAX)]);
289        assert!(logger.starts_with("-0.00000000000000".as_bytes()));
290        assert!(logger.ends_with("2147483648".as_bytes()));
291
292        logger.clear();
293
294        logger.append_with_args(i16::MIN, &[Argument::Precision(u8::MAX)]);
295        assert!(logger.starts_with("-0.00000000000000".as_bytes()));
296        assert!(logger.ends_with("32768".as_bytes()));
297
298        logger.clear();
299
300        logger.append_with_args(i8::MIN, &[Argument::Precision(u8::MAX)]);
301        assert!(logger.starts_with("-0.00000000000000".as_bytes()));
302        assert!(logger.ends_with("128".as_bytes()));
303    }
304
305    #[test]
306    fn test_logger_with_truncate() {
307        let mut logger = Logger::<10>::default();
308
309        logger.append_with_args("0123456789", &[Argument::TruncateEnd(10)]);
310        assert!(&*logger == "0123456789".as_bytes());
311
312        logger.clear();
313
314        logger.append_with_args("0123456789", &[Argument::TruncateStart(10)]);
315        assert!(&*logger == "0123456789".as_bytes());
316
317        logger.clear();
318
319        logger.append_with_args("0123456789", &[Argument::TruncateEnd(9)]);
320        assert!(&*logger == "012345...".as_bytes());
321
322        logger.clear();
323
324        logger.append_with_args("0123456789", &[Argument::TruncateStart(9)]);
325        assert!(&*logger == "...456789".as_bytes());
326
327        let mut logger = Logger::<3>::default();
328
329        logger.append_with_args("0123456789", &[Argument::TruncateEnd(9)]);
330        assert!(&*logger == "..@".as_bytes());
331
332        logger.clear();
333
334        logger.append_with_args("0123456789", &[Argument::TruncateStart(9)]);
335        assert!(&*logger == "..@".as_bytes());
336
337        let mut logger = Logger::<1>::default();
338
339        logger.append_with_args("test", &[Argument::TruncateStart(0)]);
340        assert!(&*logger == "".as_bytes());
341
342        logger.clear();
343
344        logger.append_with_args("test", &[Argument::TruncateStart(1)]);
345        assert!(&*logger == "@".as_bytes());
346
347        let mut logger = Logger::<2>::default();
348
349        logger.append_with_args("test", &[Argument::TruncateStart(2)]);
350        assert!(&*logger == ".@".as_bytes());
351
352        let mut logger = Logger::<3>::default();
353
354        logger.append_with_args("test", &[Argument::TruncateStart(3)]);
355        assert!(&*logger == "..@".as_bytes());
356
357        let mut logger = Logger::<1>::default();
358
359        logger.append_with_args("test", &[Argument::TruncateEnd(0)]);
360        assert!(&*logger == "".as_bytes());
361
362        logger.clear();
363
364        logger.append_with_args("test", &[Argument::TruncateEnd(1)]);
365        assert!(&*logger == "@".as_bytes());
366
367        let mut logger = Logger::<2>::default();
368
369        logger.append_with_args("test", &[Argument::TruncateEnd(2)]);
370        assert!(&*logger == ".@".as_bytes());
371
372        let mut logger = Logger::<3>::default();
373
374        logger.append_with_args("test", &[Argument::TruncateEnd(3)]);
375        assert!(&*logger == "..@".as_bytes());
376    }
377
378    #[test]
379    fn test_logger_with_usize() {
380        let mut logger = Logger::<20>::default();
381
382        logger.append(usize::MIN);
383        assert!(&*logger == "0".as_bytes());
384
385        logger.clear();
386
387        logger.append(usize::MAX);
388
389        #[cfg(target_pointer_width = "32")]
390        {
391            assert!(&*logger == "4294967295".as_bytes());
392            assert_eq!(logger.len(), 10);
393        }
394        #[cfg(target_pointer_width = "64")]
395        {
396            assert!(&*logger == "18446744073709551615".as_bytes());
397            assert_eq!(logger.len(), 20);
398        }
399    }
400
401    #[test]
402    fn test_logger_with_isize() {
403        let mut logger = Logger::<20>::default();
404
405        logger.append(isize::MIN);
406
407        #[cfg(target_pointer_width = "32")]
408        {
409            assert!(&*logger == "-2147483648".as_bytes());
410            assert_eq!(logger.len(), 11);
411        }
412        #[cfg(target_pointer_width = "64")]
413        {
414            assert!(&*logger == "-9223372036854775808".as_bytes());
415            assert_eq!(logger.len(), 20);
416        }
417
418        logger.clear();
419
420        logger.append(isize::MAX);
421
422        #[cfg(target_pointer_width = "32")]
423        {
424            assert!(&*logger == "2147483647".as_bytes());
425            assert_eq!(logger.len(), 10);
426        }
427        #[cfg(target_pointer_width = "64")]
428        {
429            assert!(&*logger == "9223372036854775807".as_bytes());
430            assert_eq!(logger.len(), 19);
431        }
432    }
433
434    #[test]
435    fn test_logger_buffer_size_unsigned() {
436        // Test case for an unsigned numeric type.
437        macro_rules! unsigned_test_case {
438            ( $( ($ty:ident, $max_len:literal) ),+ $(,)? ) => {
439                    $(
440                        generate_numeric_test_case!($ty::MAX, $max_len, 1,
441                        2,
442                        3,
443                        4,
444                        5,
445                        6,
446                        7,
447                        8,
448                        9,
449                        10,
450                        11,
451                        12,
452                        13,
453                        14,
454                        15,
455                        16,
456                        17,
457                        18,
458                        19,
459                        20,
460                        50,
461                        100,
462                        1000);
463                )*
464            };
465        }
466
467        unsigned_test_case!(
468            (u8, 3),
469            (u16, 5),
470            (u32, 10),
471            (u64, 20),
472            (u128, 39),
473            (usize, 20)
474        );
475    }
476
477    #[test]
478    fn test_logger_buffer_size_signed() {
479        // Test case for a signed numeric type.
480        macro_rules! signed_test_case {
481            ( $( ($ty:ident, $max_len:literal) ),+ $(,)? ) => {
482                    $(
483                        generate_numeric_test_case!($ty::MIN, ($max_len + 1), 1,
484                            2,
485                            3,
486                            4,
487                            5,
488                            6,
489                            7,
490                            8,
491                            9,
492                            10,
493                            11,
494                            12,
495                            13,
496                            14,
497                            15,
498                            16,
499                            17,
500                            18,
501                            19,
502                            20,
503                            50,
504                            100,
505                            1000);
506                    )*
507            };
508        }
509
510        signed_test_case!(
511            (i8, 3),
512            (i16, 5),
513            (i32, 10),
514            (i64, 20),
515            (i128, 39),
516            (isize, 20)
517        );
518    }
519
520    #[test]
521    fn test_logger_buffer_size_str() {
522        // Test case for a str type.
523        macro_rules! str_test_case {
524            ( $( $size:expr ),+ $(,)? ) => {
525                    $(
526                        generate_str_test_case!(&[b'x'; $size], 1,
527                            2,
528                            3,
529                            4,
530                            5,
531                            6,
532                            7,
533                            8,
534                            9,
535                            10,
536                            11,
537                            12,
538                            13,
539                            14,
540                            15,
541                            16,
542                            17,
543                            18,
544                            19,
545                            20,
546                            50,
547                            100,
548                            1000);
549                    )*
550            };
551        }
552
553        str_test_case!(1, 5, 10, 50, 100, 1000, 10000);
554    }
555
556    #[test]
557    fn test_logger_bool() {
558        let mut logger = Logger::<5>::default();
559        logger.append(true);
560
561        assert!(&*logger == "true".as_bytes());
562
563        let mut logger = Logger::<5>::default();
564        logger.append(false);
565
566        assert!(&*logger == "false".as_bytes());
567
568        let mut logger = Logger::<3>::default();
569        logger.append(true);
570
571        assert!(&*logger == "tr@".as_bytes());
572
573        let mut logger = Logger::<4>::default();
574        logger.append(false);
575
576        assert!(&*logger == "fal@".as_bytes());
577    }
578}