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