atlas_program_log/
lib.rs

1//! Lightweight log utility for Atlas programs.
2//!
3//! This crate provides a `Logger` struct that can be used to efficiently log messages
4//! in a Atlas 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 atlas_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 atlas_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 (ATLAS)=");
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 atlas_program_log_macro::*;
56
57// Enabling the "std" feature when `target_os = "atlas"` or
58// `target_arch = "bpf"` has no effect.
59#[cfg(all(not(any(target_os = "atlas", 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!((u8, 3), (u16, 5), (u32, 10), (u64, 20), (usize, 20));
477        #[cfg(not(target_arch = "bpf"))]
478        unsigned_test_case!((u128, 39),);
479    }
480
481    #[test]
482    fn test_logger_buffer_size_signed() {
483        // Test case for a signed numeric type.
484        macro_rules! signed_test_case {
485            ( $( ($ty:ident, $max_len:literal) ),+ $(,)? ) => {
486                    $(
487                        generate_numeric_test_case!($ty::MIN, ($max_len + 1), 1,
488                            2,
489                            3,
490                            4,
491                            5,
492                            6,
493                            7,
494                            8,
495                            9,
496                            10,
497                            11,
498                            12,
499                            13,
500                            14,
501                            15,
502                            16,
503                            17,
504                            18,
505                            19,
506                            20,
507                            50,
508                            100,
509                            1000);
510                    )*
511            };
512        }
513
514        signed_test_case!((i8, 3), (i16, 5), (i32, 10), (i64, 20), (isize, 20));
515        #[cfg(not(target_arch = "bpf"))]
516        signed_test_case!((i128, 39),);
517    }
518
519    #[test]
520    fn test_logger_buffer_size_str() {
521        // Test case for a str type.
522        macro_rules! str_test_case {
523            ( $( $size:expr ),+ $(,)? ) => {
524                    $(
525                        generate_str_test_case!(&[b'x'; $size], 1,
526                            2,
527                            3,
528                            4,
529                            5,
530                            6,
531                            7,
532                            8,
533                            9,
534                            10,
535                            11,
536                            12,
537                            13,
538                            14,
539                            15,
540                            16,
541                            17,
542                            18,
543                            19,
544                            20,
545                            50,
546                            100,
547                            1000);
548                    )*
549            };
550        }
551
552        str_test_case!(1, 5, 10, 50, 100, 1000, 10000);
553    }
554
555    #[test]
556    fn test_logger_bool() {
557        let mut logger = Logger::<5>::default();
558        logger.append(true);
559
560        assert!(&*logger == "true".as_bytes());
561
562        let mut logger = Logger::<5>::default();
563        logger.append(false);
564
565        assert!(&*logger == "false".as_bytes());
566
567        let mut logger = Logger::<3>::default();
568        logger.append(true);
569
570        assert!(&*logger == "tr@".as_bytes());
571
572        let mut logger = Logger::<4>::default();
573        logger.append(false);
574
575        assert!(&*logger == "fal@".as_bytes());
576    }
577}