quicklog/
lib.rs

1//! An asynchronous single-threaded logger where formatting and I/O are deferred at callsite.
2//!
3//! # Overview
4//!
5//! `Quicklog` is provides a framework for logging where it allows for deferred
6//! deferred formatting and deferred I/O of logging, which should in turn provide
7//! more performant logging with low callsite latency.
8//!
9//! ## Deferred Formatting
10//!
11//! #### Why?
12//!
13//! Formatting a `struct` into a `String` requires the overhead of serialization.
14//! Deferring the serialization of a struct can be avoided by cloning / copying
15//! the struct at a point in time, and saving that onto a queue.
16//!
17//! Later at the flush site, this struct is serialized into a string when I/O is
18//! going to be performed.
19//!
20//! ## Deferred I/O
21//!
22//! #### Why?
23//!
24//! Deferring the I/O of formatting would allow for low callsite latency and allow
25//! a user to implement their own flush site, possibly on a separate thread
26//!
27//! # Usage
28//!
29//! `init!()` macro needs to be called to initialize the logger before we can
30//! start logging, probably near the entry point of your application.
31//!
32//! ## Example Usage
33//!
34//! ```
35//! # use std::thread;
36//! # use quicklog::{info, init, flush};
37//! fn main() {
38//!     init!();
39//!
40//!     // log some stuff
41//!     info!("hello world! {}", "some argument");
42//!
43//!     // flush on separate thread
44//!     thread::spawn(|| {
45//!         loop {
46//!             flush!();
47//!         }
48//!     });
49//! }
50//! ```
51//!
52//! # Macros
53//!
54//! #### Shorthand Macros
55//!
56//! Quicklog allows a number of macros with 5 different levels of verbosity. These
57//! wrap around [`log!`] with the corresponding levels.
58//!
59//! * [`trace!`]
60//! * [`debug!`]
61//! * [`info!`]
62//! * [`warn!`]
63//! * [`error!`]
64//!
65//! Internally, these shorthands call [`try_log!`] with their respective levels.
66//!
67//! ## Setup Macros
68//!
69//! Quicklog allows a user specified [`Clock`] or [`Flush`] to be implemented by
70//! the user. This can be passed in through these macros, as long as the
71//! underlying struct implements the correct traits
72//!
73//! * [`with_clock!`]: Specify the Clock Quicklog uses
74//! * [`with_flush!`]: Specify the Flusher Quicklog uses
75//! * [`with_flush_into_file`]: Specify path to flush log lines into
76//!
77//! ## Macro prefix for partial serialization
78//!
79//! To speed things up, if you are logging a large struct, there could be some small things
80//! you might not want to log. This functionality can be done through implementing the
81//! [`Serialize`] trait, where you can implement how to copy which parts of the struct.
82//!
83//! This could additionally be helpful if you already have the struct inside a buffer in byte
84//! form, as you could simply pass the buffer directly into the decode fn, eliminiating any
85//! need to copy.
86//!
87//! ```ignore
88//! struct SomeStruct {
89//!     num: i64
90//! }
91//!
92//! impl Serialize for SomeStruct {
93//!    fn encode(&self, write_buf: &'static mut [u8]) -> Store { /* some impl */ }
94//!    fn buffer_size_required(&self) -> usize { /* some impl */ }
95//! }
96//!
97//! fn main() {
98//!     let s = SomeStruct { num: 1_000_000 };
99//!     info!("some struct: {}", ^s);
100//! }
101//! ```
102//!
103//! ## Macro prefix for eager evaluation
104//!
105//! There are two prefixes you can use for variables, `%` and `?`. This works the same
106//! way as `tracing`, where `%` eagerly evaluates an object that implements `Display`
107//! and `?` eagerly evaluates an object that implements `Debug`.
108//!
109//! ```
110//! # use quicklog::{init, info};
111//! # fn main() {
112//! # let impl_debug = "";
113//! # let impl_display = "";
114//! # init!();
115//! info!("eager display {}; eager debug {}", %impl_display, ?impl_debug);
116//! // logically expands into:
117//! // info!(
118//! //      "eager display {}; eager debug {}",
119//! //      format!("{}",   impl_display),
120//! //      format!("{:?}", impl_debug)
121//! // );
122//! # }
123//! ```
124//!
125//! ## Structured fields
126//!
127//! Structured fields in log lines can be specified using `field_name = field_value`
128//! syntax. `field_name` can be a literal or a bunch of idents. This can also
129//! be used in combination with `%` and `?` prefix on args to eagerly evaluate
130//! expressions into format strings.
131//!
132//! ```
133//! # use quicklog::{init, info};
134//! # fn main() {
135//! # init!();
136//! # let value = 10;
137//! info!("hello world {} {} {}", question.tricky = true, question.answer = ?value, question.val = &value);
138//! // output: "hello world question.tricky=true question.answer=10 question.val=10"
139//! # }
140//! ```
141//!
142//! # Environment variables
143//!
144//! There are two environment variables you can set:
145//!
146//! 1. `QUICKLOG_MAX_LOGGER_CAPACITY`
147//!     - sets the size of the spsc ring buffer used for logging
148//! 2. `QUICKLOG_MAX_SERIALIZE_BUFFER_CAPACITY`
149//!     - sets the size of the byte buffer used for static serialization
150//!     - this can be increased when you run into issues out of memory in debug
151//!     when conducting load testing
152//!
153//! # Components
154//!
155//! ## quicklog-clock
156//!
157//! [`Clock`] is the trait for a clock that can be used with [`Quicklog`]. Clocks can
158//! be swapped out at runtime with a different implementation.
159//!
160//! This swap should be done at the init stage of your application to ensure
161//! that timings are consistent.
162//!
163//! ### Example
164//!
165//! ```ignore
166//! struct SomeClock;
167//!
168//! impl Clock for SomeClock { /* impl methods */ }
169//!
170//! fn main() {
171//!     init!();
172//!
173//!     with_clock!(SomeClock::new());
174//!
175//!     // logger now uses SomeClock for timestamping
176//!     info!("Hello, world!");
177//!     flush!();
178//! }
179//! ```
180//!
181//! ## quicklog-flush
182//!
183//! [`Flush`] is the trait that defines how the log messages would be flushed.
184//! These logs can be printed through using the pre-defined [`StdoutFlusher`] or
185//! saved to a file through the pre-defined [`FileFlusher`] to a specified
186//! location through the string passed in.
187//!
188//! ### Example
189//!
190//! ```
191//! # use quicklog::{init, flush, with_flush};
192//! # use quicklog_flush::stdout_flusher::StdoutFlusher;
193//! fn main() {
194//!     init!();
195//!
196//!     with_flush!(StdoutFlusher);
197//!
198//!     // uses the StdoutFlusher passed in for flushing
199//!     flush!();
200//! }
201//! ```
202//!
203//! [`Serialize`]: serialize::Serialize
204//! [`StdoutFlusher`]: quicklog_flush::stdout_flusher::StdoutFlusher
205//! [`FileFlusher`]: quicklog_flush::file_flusher::FileFlusher
206
207use heapless::spsc::Queue;
208use once_cell::unsync::{Lazy, OnceCell};
209use quanta::Instant;
210use serialize::buffer::{Buffer, BUFFER};
211use std::fmt::Display;
212
213use quicklog_clock::{quanta::QuantaClock, Clock};
214use quicklog_flush::{file_flusher::FileFlusher, Flush};
215
216/// contains logging levels and filters
217pub mod level;
218/// contains macros
219pub mod macros;
220/// contains trait for serialization and pre-generated impl for common types and buffer
221pub mod serialize;
222
223include!("constants.rs");
224/// `constants.rs` is generated from `build.rs`, should not be modified manually
225pub mod constants;
226
227/// Internal API
228///
229/// Intermediate log item being stored into logging queue
230#[doc(hidden)]
231pub type Intermediate = (Instant, Box<dyn Display>);
232
233/// Logger initialized to Quicklog
234#[doc(hidden)]
235static mut LOGGER: Lazy<Quicklog> = Lazy::new(Quicklog::default);
236
237/// Producer side of queue
238pub type Sender = heapless::spsc::Producer<'static, Intermediate, MAX_LOGGER_CAPACITY>;
239/// Result from pushing onto queue
240pub type SendResult = Result<(), Intermediate>;
241/// Consumer side of queue
242pub type Receiver = heapless::spsc::Consumer<'static, Intermediate, MAX_LOGGER_CAPACITY>;
243/// Result from trying to pop from logging queue
244pub type RecvResult = Result<(), FlushError>;
245
246/// Sender handles sending into the mpsc queue
247static mut SENDER: OnceCell<Sender> = OnceCell::new();
248/// Receiver handles flushing from mpsc queue
249static mut RECEIVER: OnceCell<Receiver> = OnceCell::new();
250
251/// Log is the base trait that Quicklog will implement.
252/// Flushing and formatting is deferred while logging.
253pub trait Log {
254    /// Dequeues a single line from logging queue and passes it to Flusher
255    fn flush_one(&mut self) -> RecvResult;
256    /// Enqueues a single line onto logging queue
257    fn log(&self, display: Box<dyn Display>) -> SendResult;
258}
259
260/// Errors that can be presented when flushing
261#[derive(Debug)]
262pub enum FlushError {
263    /// Queue is empty
264    Empty,
265}
266
267///  ha**Internal API**
268///
269/// Returns a mut reference to the globally static logger [`LOGGER`]
270#[doc(hidden)]
271pub fn logger() -> &'static mut Quicklog {
272    unsafe { &mut LOGGER }
273}
274
275/// Quicklog implements the Log trait, to provide logging
276pub struct Quicklog {
277    flusher: Box<dyn Flush>,
278    clock: Box<dyn Clock>,
279}
280
281impl Quicklog {
282    /// Sets which flusher to be used, used in [`with_flush!`]
283    #[doc(hidden)]
284    pub fn use_flush(&mut self, flush: Box<dyn Flush>) {
285        self.flusher = flush
286    }
287
288    /// Sets which flusher to be used, used in [`with_clock!`]
289    #[doc(hidden)]
290    pub fn use_clock(&mut self, clock: Box<dyn Clock>) {
291        self.clock = clock
292    }
293
294    /// Initializes channel inside of quicklog, can be called
295    /// through [`init!`] macro
296    pub fn init() {
297        Quicklog::init_channel();
298        Quicklog::init_buffer();
299    }
300
301    /// Initializes buffer for static serialization
302    fn init_buffer() {
303        unsafe {
304            BUFFER.set(Buffer::new()).ok();
305        }
306    }
307
308    /// Initializes channel for main logging queue
309    fn init_channel() {
310        static mut QUEUE: Queue<Intermediate, MAX_LOGGER_CAPACITY> = Queue::new();
311        let (sender, receiver): (Sender, Receiver) = unsafe { QUEUE.split() };
312        unsafe {
313            SENDER.set(sender).ok();
314            RECEIVER.set(receiver).ok();
315        }
316    }
317}
318
319impl Default for Quicklog {
320    fn default() -> Self {
321        Quicklog {
322            flusher: Box::new(FileFlusher::new("logs/quicklog.log")),
323            clock: Box::new(QuantaClock::new()),
324        }
325    }
326}
327
328impl Log for Quicklog {
329    fn log(&self, display: Box<dyn Display>) -> SendResult {
330        match unsafe {
331            SENDER
332                .get_mut()
333                .expect("Sender is not initialized, `Quicklog::init()` needs to be called at the entry point of your application")
334                .enqueue((self.clock.get_instant(), display))
335        } {
336            Ok(_) => Ok(()),
337            Err(err) => Err(err),
338        }
339    }
340
341    fn flush_one(&mut self) -> RecvResult {
342        match unsafe {
343            RECEIVER
344                    .get_mut()
345                    .expect("RECEIVER is not initialized, `Quicklog::init()` needs to be called at the entry point of your application")
346                    .dequeue()
347        } {
348            Some((time_logged, disp)) => {
349                let log_line = format!(
350                    "[{:?}]{}\n",
351                    self.clock
352                        .compute_system_time_from_instant(time_logged)
353                        .expect("Unable to get time from instant"),
354                    disp
355                );
356                self.flusher.flush_one(log_line);
357                Ok(())
358            }
359            None => Err(FlushError::Empty),
360        }
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use std::{str::from_utf8, sync::Mutex};
367
368    use quicklog_flush::Flush;
369
370    use crate::{
371        debug, error, flush, info,
372        serialize::{Serialize, Store},
373        trace, warn,
374    };
375
376    struct VecFlusher {
377        pub vec: &'static mut Vec<String>,
378    }
379
380    impl VecFlusher {
381        pub fn new(vec: &'static mut Vec<String>) -> VecFlusher {
382            VecFlusher { vec }
383        }
384    }
385
386    impl Flush for VecFlusher {
387        fn flush_one(&mut self, display: String) {
388            self.vec.push(display);
389        }
390    }
391
392    #[derive(Clone, Debug)]
393    struct Something {
394        some_str: &'static str,
395    }
396
397    fn message_from_log_line(log_line: &str) -> String {
398        log_line
399            .split('\t')
400            .last()
401            .map(|s| s.chars().take(s.len() - 1).collect::<String>())
402            .unwrap()
403    }
404
405    fn message_and_level_from_log_line(log_line: &str) -> String {
406        let timestamp_end_idx = log_line.find(']').unwrap() + 1;
407        log_line
408            .chars()
409            .skip(timestamp_end_idx)
410            .take(log_line.len() - timestamp_end_idx - 1)
411            .collect::<String>()
412    }
413
414    /// tests need to be single threaded, this mutex ensures
415    /// tests are only executed in single threaded mode
416    /// and [`Quicklog::init!`] is only called once.
417    static TEST_LOCK: Mutex<usize> = Mutex::new(0);
418
419    macro_rules! setup {
420        () => {
421            // acquire lock within scope of each test
422            let mut guard = TEST_LOCK.lock().unwrap();
423            if *guard == 0 {
424                crate::init!();
425                *guard += 1;
426            }
427            static mut VEC: Vec<String> = Vec::new();
428            let vec_flusher = unsafe { VecFlusher::new(&mut VEC) };
429            crate::logger().use_flush(Box::new(vec_flusher));
430        };
431    }
432
433    fn from_log_lines<F: Fn(&str) -> String>(lines: &[String], f: F) -> Vec<String> {
434        lines.iter().map(|s| f(s.as_str())).collect::<Vec<_>>()
435    }
436
437    #[doc(hidden)]
438    macro_rules! helper_assert {
439        (@ $f:expr, $format_string:expr, $check_f:expr) => {
440            $f;
441            flush!();
442            assert_eq!(
443                unsafe { from_log_lines(&VEC, $check_f) },
444                vec![$format_string]
445            );
446            unsafe {
447                let _ = &VEC.clear();
448            }
449        };
450    }
451
452    macro_rules! assert_message_equal {
453        ($f:expr, $format_string:expr) => { helper_assert!(@ $f, $format_string, message_from_log_line) };
454    }
455
456    macro_rules! assert_message_with_level_equal {
457        ($f:expr, $format_string:expr) => { helper_assert!(@ $f, $format_string, message_and_level_from_log_line) };
458    }
459
460    #[derive(Clone, Debug)]
461    struct NestedSomething {
462        thing: Something,
463    }
464
465    impl std::fmt::Display for Something {
466        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
467            write!(f, "Something display: {}", self.some_str)
468        }
469    }
470
471    #[test]
472    fn has_all_levels() {
473        setup!();
474
475        assert_message_with_level_equal!(
476            trace!("Hello world {}", "Another"),
477            format!("[TRACE]\tHello world {}", "Another")
478        );
479        assert_message_with_level_equal!(
480            debug!("Hello world {}", "Another"),
481            format!("[DEBUG]\tHello world {}", "Another")
482        );
483        assert_message_with_level_equal!(
484            info!("Hello world {}", "Another"),
485            format!("[INFO]\tHello world {}", "Another")
486        );
487        assert_message_with_level_equal!(
488            warn!("Hello world {}", "Another"),
489            format!("[WARN]\tHello world {}", "Another")
490        );
491        assert_message_with_level_equal!(
492            error!("Hello world {}", "Another"),
493            format!("[ERROR]\tHello world {}", "Another")
494        );
495    }
496
497    #[test]
498    fn works_in_closure() {
499        setup!();
500
501        let s1 = Something {
502            some_str: "Hello world 1",
503        };
504        let s2 = Something {
505            some_str: "Hello world 2",
506        };
507
508        let f = || {
509            assert_message_equal!(
510                info!("Hello world {} {:?}", s1, s2),
511                format!("Hello world {} {:?}", s1, s2)
512            );
513        };
514
515        f();
516    }
517
518    #[test]
519    fn works_with_attributes() {
520        setup!();
521
522        let s1 = Something {
523            some_str: "Hello world 1",
524        };
525        let s2 = Something {
526            some_str: "Hello world 2",
527        };
528        let nested = NestedSomething {
529            thing: Something {
530                some_str: "hello nested",
531            },
532        };
533
534        assert_message_equal!(
535            info!("log one attr {}", nested.thing.some_str),
536            format!("log one attr {}", nested.thing.some_str)
537        );
538        assert_message_equal!(
539            info!("hello world {} {:?}", s1.some_str, s2.some_str),
540            format!("hello world {} {:?}", s1.some_str, s2.some_str)
541        );
542    }
543
544    #[test]
545    fn works_with_box_ref() {
546        setup!();
547
548        let s1 = Box::new(Something {
549            some_str: "Hello world 1",
550        });
551        let s2 = Box::new(Something {
552            some_str: "Hello world 2",
553        });
554
555        assert_message_equal!(
556            info!("log single box ref {}", s1.as_ref()),
557            format!("log single box ref {}", s1.as_ref())
558        );
559        assert_message_equal!(
560            info!("log multi box ref {} {:?}", s1.as_ref(), s2.as_ref()),
561            format!("log multi box ref {} {:?}", s1.as_ref(), s2.as_ref())
562        );
563    }
564
565    #[test]
566    fn works_with_move() {
567        setup!();
568
569        let s1 = Something {
570            some_str: "Hello world 1",
571        };
572        let s2 = Something {
573            some_str: "Hello world 2",
574        };
575        let s3 = Something {
576            some_str: "Hello world 3",
577        };
578
579        assert_message_equal!(
580            info!("log multi move {} {:?}", s1, s2),
581            format!("log multi move {} {:?}", s1, s2)
582        );
583        assert_message_equal!(
584            info!("log single move {}", s3),
585            format!("log single move {}", s3)
586        );
587    }
588
589    #[test]
590    fn works_with_references() {
591        setup!();
592
593        let s1 = Something {
594            some_str: "Hello world 1",
595        };
596        let s2 = Something {
597            some_str: "Hello world 2",
598        };
599
600        assert_message_equal!(
601            info!("log single ref: {}", &s1),
602            format!("log single ref: {}", &s1)
603        );
604        assert_message_equal!(
605            info!("log multi ref: {} {:?}", &s1, &s2),
606            format!("log multi ref: {} {:?}", &s1, &s2)
607        );
608    }
609
610    fn log_multi_ref_helper(thing: &Something, thing2: &Something) {
611        info!("log multi ref {} {:?}", thing, thing2);
612    }
613
614    fn log_ref_helper(thing: &Something) {
615        info!("log single ref: {}", thing)
616    }
617
618    #[test]
619    fn works_with_ref_lifetime_inside_fn() {
620        setup!();
621
622        let s1 = Something {
623            some_str: "Hello world 1",
624        };
625        let s2 = Something {
626            some_str: "Hello world 2",
627        };
628
629        assert_message_equal!(log_ref_helper(&s1), format!("log single ref: {}", &s1));
630        assert_message_equal!(
631            log_multi_ref_helper(&s2, &s1),
632            format!("log multi ref {} {:?}", &s2, &s1)
633        );
634    }
635
636    #[derive(Clone)]
637    struct A {
638        price: u64,
639        symbol: &'static str,
640        exch_id: u64,
641    }
642
643    impl A {
644        fn get_price(&self) -> u64 {
645            self.price
646        }
647
648        fn get_exch_id(&self) -> u64 {
649            self.exch_id
650        }
651
652        fn get_symbol(&self) -> &'static str {
653            self.symbol
654        }
655    }
656
657    #[test]
658    fn works_with_fn_return_val() {
659        setup!();
660
661        let a = A {
662            price: 1_521_523,
663            symbol: "SomeSymbol",
664            exch_id: 642_153_768,
665        };
666
667        assert_message_equal!(
668            info!(
669                "A: price: {} symbol: {} exch_id: {}",
670                a.get_price(),
671                ?a.get_symbol(),
672                %a.get_exch_id()
673            ),
674            format!(
675                "A: price: {} symbol: \"{}\" exch_id: {:?}",
676                a.get_price(),
677                a.get_symbol(),
678                a.get_exch_id()
679            )
680        );
681        assert_message_equal!(
682            info!("single call {}", a.get_price()),
683            format!("single call {}", a.get_price())
684        );
685    }
686
687    fn log_ref_and_move(s1: Something, s2r: &Something) {
688        info!("Hello world {} {:?}", s1, s2r);
689    }
690
691    #[test]
692    fn works_with_ref_and_move() {
693        setup!();
694
695        let s1 = Something {
696            some_str: "Hello world 1",
697        };
698        let s1_clone = s1.clone();
699        let s2 = Something {
700            some_str: "Hello world 2",
701        };
702
703        assert_message_equal!(
704            log_ref_and_move(s1, &s2),
705            format!("Hello world {} {:?}", s1_clone, &s2)
706        );
707        let s3 = Something {
708            some_str: "Hello world 3",
709        };
710        let s4 = Something {
711            some_str: "Hello world 4",
712        };
713
714        assert_message_equal!(
715            info!("ref: {:?}, move: {}", &s2, s3),
716            format!("ref: {:?}, move: {}", &s2, s3)
717        );
718        assert_message_equal!(info!("single ref: {}", &s2), format!("single ref: {}", &s2));
719        assert_message_equal!(info!("single move: {}", s4), format!("single move: {}", s4));
720    }
721
722    #[test]
723    fn works_with_eager_debug_display_hints() {
724        setup!();
725
726        let s1 = Something {
727            some_str: "Hello world 1",
728        };
729        let s2 = Something {
730            some_str: "Hello world 2",
731        };
732        let some_str = "hello world";
733
734        assert_message_equal!(
735            info!("display {}; eager debug {}; eager display {}, eager display inner field {}", some_str, ?s2, %s1, %s1.some_str),
736            format!(
737                "display {}; eager debug {:?}; eager display {}, eager display inner field {}",
738                some_str, s2, s1, s1.some_str
739            )
740        );
741        assert_message_equal!(
742            info!("single eager display: {}", %s2),
743            format!("single eager display: {}", s2)
744        );
745    }
746
747    #[test]
748    fn works_with_fields() {
749        setup!();
750
751        let s1 = Something {
752            some_str: "Hello world 1",
753        };
754        let s2 = Something {
755            some_str: "Hello world 1",
756        };
757        let s3 = Something {
758            some_str: "Hello world 3",
759        };
760        let s3_clone = s3.clone();
761
762        assert_message_equal!(
763            info!("pass by ref {}", some_struct.field1.innerfield.inner = &s1),
764            format!("pass by ref some_struct.field1.innerfield.inner={}", &s1)
765        );
766        assert_message_equal!(
767            info!("pass by move {}", some.inner.field = s3),
768            format!("pass by move some.inner.field={}", s3_clone)
769        );
770        assert_message_equal!(
771            info!(
772                "non-nested field: {}, nested field: {}, pure lit: {}",
773                borrow_s2_field = %s2,
774                some_inner_field.inner.field.inner.arg = "hello world",
775                "pure lit arg" = "another lit arg"
776            ),
777            format!("non-nested field: borrow_s2_field={}, nested field: some_inner_field.inner.field.inner.arg=hello world, pure lit: pure lit arg=another lit arg", &s2)
778        );
779        assert_message_equal!(
780            info!(
781                "pure lit: {}, reuse debug: {}, nested field: {}, able to reuse after pass by ref: {}",
782                "pure lit arg" = "another lit arg",
783                "able to reuse s1" = ?s1,
784                some_inner_field.some.field.included = "hello world",
785                able.to.reuse.s2.borrow = &s2
786            ),
787            format!("pure lit: pure lit arg=another lit arg, reuse debug: able to reuse s1={:?}, nested field: some_inner_field.some.field.included=hello world, able to reuse after pass by ref: able.to.reuse.s2.borrow={}", s1, &s2)
788        );
789    }
790
791    struct S {
792        symbol: String,
793    }
794
795    impl Serialize for S {
796        fn encode(&self, write_buf: &'static mut [u8]) -> Store {
797            fn decode(read_buf: &[u8]) -> String {
798                let x = from_utf8(read_buf).unwrap();
799                x.to_string()
800            }
801            write_buf.copy_from_slice(self.symbol.as_bytes());
802            Store::new(decode, write_buf)
803        }
804
805        fn buffer_size_required(&self) -> usize {
806            self.symbol.len()
807        }
808    }
809
810    #[derive(Debug, Clone, Copy)]
811    struct BigStruct {
812        vec: [i32; 100],
813        some: &'static str,
814    }
815
816    impl Serialize for BigStruct {
817        fn encode(&self, write_buf: &'static mut [u8]) -> Store {
818            fn decode(buf: &[u8]) -> String {
819                let (mut _head, mut tail) = buf.split_at(0);
820                let mut vec = vec![];
821                for _ in 0..100 {
822                    (_head, tail) = tail.split_at(4);
823                    vec.push(i32::from_le_bytes(_head.try_into().unwrap()));
824                }
825                let s = from_utf8(tail).unwrap();
826                format!("vec: {:?}, str: {}", vec, s)
827            }
828
829            let (mut _head, mut tail) = write_buf.split_at_mut(0);
830            for i in 0..100 {
831                (_head, tail) = tail.split_at_mut(4);
832                _head.copy_from_slice(&self.vec[i].to_le_bytes())
833            }
834
835            tail.copy_from_slice(self.some.as_bytes());
836
837            Store::new(decode, write_buf)
838        }
839
840        fn buffer_size_required(&self) -> usize {
841            std::mem::size_of::<i32>() * 100 + self.some.len()
842        }
843    }
844
845    #[test]
846    fn works_with_serialize() {
847        setup!();
848
849        let s = S {
850            symbol: String::from("Hello"),
851        };
852        let bs = BigStruct {
853            vec: [1; 100],
854            some: "The quick brown fox jumps over the lazy dog",
855        };
856
857        assert_message_equal!(info!("s: {} {}", ^s, ^s), "s: Hello Hello");
858        assert_message_equal!(
859            info!("bs: {}", ^bs),
860            format!(
861                "bs: vec: {:?}, str: {}",
862                vec![1; 100],
863                "The quick brown fox jumps over the lazy dog"
864            )
865        );
866    }
867}