solana_program_log/
lib.rs

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