Skip to main content

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