fuchsia_inspect_contrib/log/
mod.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Provides convenience macros for writing to an inspect bounded list (bounded log)
6//!
7//! ## Example
8//!
9//! ```rust
10//! let inspector = Inspector::default();
11//! let list_node = inspector.root().create_child("list_node");
12//! let list_node = BoundedListNode::new(list_node, 10);
13//! inspect_log!(list_node, k1: "1".to_string(), meaning_of_life: 42u64, k3: 3i64, k4: 4f64);
14//! // Inspect now has:
15//! // root: {
16//! //   list_node: {
17//! //     "0": { "@time": <timestamp>, k1: "1", meaning_of_life: 42, k3: 3, k4: 4 }
18//! //   }
19//! // }
20//! ```
21
22use fuchsia_inspect::Node;
23use std::borrow::Cow;
24
25mod impls;
26mod wrappers;
27
28pub use wrappers::{InspectBytes, InspectList, InspectListClosure, InspectUintArray};
29
30/// Trait for writing to a node in bounded lists.
31pub trait WriteInspect {
32    /// Write a *single* value (property or child node) to |node| with the specified |key|.
33    /// If multiple properties need to be written, consider creating a single child
34    /// node with those properties.
35    fn write_inspect<'a>(&self, writer: &Node, key: impl Into<Cow<'a, str>>);
36}
37
38/// Macro to log a new entry to a bounded list node with the specified key-value pairs. Each value
39/// must be a type that implements `WriteInspect`. This macro automatically injects a timestamp
40/// to each entry.
41///
42/// Example:
43///
44/// ```
45/// let bounded_list_node = ...;
46/// inspect_log!(bounded_list_node, {});   // log only a timestamped entry
47/// inspect_log!(bounded_list_node, k1: "1", k2: 2i64, k3: "3");   // non-block form
48/// inspect_log!(bounded_list_node, {   // block form (only difference is syntactic)
49///     ba: "dum",
50///     tss: "tss",
51/// });
52/// inspect_log!(bounded_list_node, {   // logging nested data structure
53///     k1: {
54///        subkey1: "subval1",
55///        subkey2: 2,
56///     }
57/// });
58/// ```
59#[macro_export]
60macro_rules! inspect_log {
61    ($bounded_list_node:expr, $($args:tt)+) => {{
62        use $crate::{inspect_insert, nodes::{NodeTimeExt, BootTimeline}};
63        $bounded_list_node.add_entry(|node| {
64            NodeTimeExt::<BootTimeline>::record_time(node, "@time");
65            inspect_insert!(@internal_inspect_log node, $($args)+);
66        });
67    }};
68}
69
70/// Macro to insert items using a Node. Each value must be a type that implements
71/// `WriteInspect`.
72///
73/// Example:
74///
75/// ```
76/// let node = ...; // fuchsia_inspect::Node
77/// inspect_insert!(node, k1: "1", k2: 2i64, k3: "3");
78/// ```
79#[macro_export]
80macro_rules! inspect_insert {
81    (@internal $node_writer:expr,) => {};
82
83    // Insert tree
84    (@internal $node_writer:expr, var $key:ident: { $($sub:tt)+ }) => {{
85        let child_writer = $node_writer.create_child($key);
86        inspect_insert!(@internal child_writer, $($sub)+);
87        $node_writer.record(child_writer);
88    }};
89
90    (@internal $node_writer:expr, var $key:ident: { $($sub:tt)+ }, $($rest:tt)*) => {{
91        inspect_insert!(@internal $node_writer, var $key: { $($sub)+ });
92        inspect_insert!(@internal $node_writer, $($rest)*);
93    }};
94
95    // Insert properties and metrics
96    (@internal $node_writer:expr, var $key:ident: $val:expr) => {{
97        $val.write_inspect(&$node_writer, $key);
98    }};
99
100    (@internal $node_writer:expr, var $key:ident: $val:expr, $($rest:tt)*) => {{
101        inspect_insert!(@internal $node_writer, var $key: $val);
102        inspect_insert!(@internal $node_writer, $($rest)*);
103    }};
104
105    // Insert optional value
106    (@internal $node_writer:expr, var $key:ident?: $val:expr) => {{
107        match $val {
108            Some(val) => inspect_insert!(@internal $node_writer, var $key: val),
109            None => (),
110        }
111    }};
112
113    (@internal $node_writer:expr, var $key:ident?: $val:expr, $($rest:tt)*) => {{
114        inspect_insert!(@internal $node_writer, var $key?: $val);
115        inspect_insert!(@internal $node_writer, $($rest)*);
116    }};
117
118    // Key identifier format
119    (@internal $node_writer:expr, $key:ident: $($rest:tt)+) => {{
120        let key = stringify!($key);
121        inspect_insert!(@internal $node_writer, var key: $($rest)+);
122    }};
123
124    (@internal $node_writer:expr, $key:ident?: $($rest:tt)+) => {{
125        let key = stringify!($key);
126        inspect_insert!(@internal $node_writer, var key?: $($rest)+);
127    }};
128
129    (@internal $node_writer:expr, $key:expr => $($rest:tt)+) => {{
130        let key: std::borrow::Cow<'_, _> = $key.into();
131        inspect_insert!(@internal $node_writer, var key: $($rest)+);
132    }};
133
134    // Entry point: from inspect_log! (mainly to allow empty event)
135    (@internal_inspect_log $node_writer:expr, { $($args:tt)* }) => {{
136        // User may specify an empty event, so `WriteInspect` may not always
137        // be used.
138        #[allow(unused_imports)]
139        use $crate::log::WriteInspect;
140        inspect_insert!(@internal $node_writer, $($args)*);
141    }};
142
143    (@internal_inspect_log $node_writer:expr, $($args:tt)+) => {{
144        use $crate::log::WriteInspect;
145        inspect_insert!(@internal $node_writer, $($args)+);
146    }};
147
148    // Entry point: block syntax
149    ($node_writer:expr, { $($args:tt)+ }) => {{
150        use $crate::log::WriteInspect;
151        inspect_insert!(@internal $node_writer, $($args)+);
152    }};
153
154    // Entry point: non-block syntax
155    ($node_writer:expr, $($args:tt)+) => {{
156        use $crate::log::WriteInspect;
157        inspect_insert!(@internal $node_writer, $($args)+);
158    }};
159}
160
161/// Convenience macro to construct a closure that implements WriteInspect, so it can be
162/// used in `inspect_log!` and `inspect_insert!`.
163///
164/// Note that this macro constructs a *move* closure underneath, unlike `inspect_log!` and
165/// `inspect_insert!` where variables are only borrowed.
166///
167/// Example 1:
168///
169/// ```
170/// let bounded_list_node = ...;
171/// let obj = make_inspect_loggable!(k1: "1", k2: 2i64, k3: "3");
172/// inspect_log!(bounded_list_node, some_key: obj);
173/// ```
174///
175/// Example 2
176///
177/// ```
178/// let bounded_list_node = ...;
179/// let point = Some((10, 50));
180/// inspect_log!(bounded_list_node, point?: point.map(|(x, y)| make_inspect_loggable!({
181///     x: x,
182///     y: y,
183/// })))
184/// ```
185#[macro_export]
186macro_rules! make_inspect_loggable {
187    ($($args:tt)+) => {{
188        use $crate::inspect_insert;
189        use fuchsia_inspect::Node;
190        use std::borrow::Cow;
191        struct WriteInspectClosure<F>(F);
192        impl<F> WriteInspect for WriteInspectClosure<F>
193            where
194                F: Fn(&Node, String) {
195            fn write_inspect<'b>(&self, writer: &Node, key: impl Into<Cow<'b, str>>) {
196                self.0(writer, key.into().to_string());
197            }
198        }
199        let f = WriteInspectClosure(move |writer: &Node, key| {
200            let child = writer.create_child(key);
201            inspect_insert!(child, $($args)+);
202            writer.record(child);
203        });
204        f
205    }};
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use crate::nodes::BoundedListNode;
212    use diagnostics_assertions::{assert_data_tree, AnyProperty};
213    use fuchsia_inspect::{DiagnosticsHierarchyGetter, Inspector};
214    use fuchsia_sync::Mutex;
215    use test_util::assert_lt;
216
217    #[fuchsia::test]
218    fn test_inspect_log_basic() {
219        let (inspector, mut node) = inspector_and_list_node();
220
221        // Logging string and full-size numeric type
222        inspect_log!(node, k1: "1".to_string(), meaning_of_life: 42u64, k3: 3i64, k4: 4f64);
223
224        // Logging smaller numeric types (which should be converted to bigger types)
225        inspect_log!(node, small_uint: 1u8, small_int: 2i8, float: 3f32);
226
227        // Logging reference types + using bracket format
228        inspect_log!(node, {
229            s: "str",
230            uint: &13u8,
231        });
232
233        // Logging empty event
234        inspect_log!(node, {});
235
236        let hierarchy = inspector.get_diagnostics_hierarchy();
237        assert_data_tree!(hierarchy, root: {
238            list_node: {
239                "0": { "@time": AnyProperty, k1: "1", meaning_of_life: 42u64, k3: 3i64, k4: 4f64 },
240                "1": { "@time": AnyProperty, small_uint: 1u64, small_int: 2i64, float: 3f64 },
241                "2": { "@time": AnyProperty, s: "str", uint: 13u64 },
242                "3": { "@time": AnyProperty },
243            }
244        });
245
246        let get_time = |index| {
247            hierarchy
248                .get_property_by_path(&["list_node", index, "@time"])
249                .and_then(|p| p.int())
250                .unwrap()
251        };
252        assert_lt!(get_time("0"), get_time("1"));
253        assert_lt!(get_time("1"), get_time("2"));
254        assert_lt!(get_time("2"), get_time("3"));
255    }
256
257    #[fuchsia::test]
258    fn test_inspect_log_nested() {
259        let (inspector, mut node) = inspector_and_list_node();
260        inspect_log!(node, {
261            k1: {
262                sub1: "subval1",
263                sub2: {
264                    subsub1: "subsubval1",
265                },
266                sub3: 3u64,
267            },
268            k2: if true { 10u64 } else { 20 }
269        });
270
271        assert_data_tree!(inspector, root: {
272            list_node: {
273                "0": {
274                    "@time": AnyProperty,
275                    k1: {
276                        sub1: "subval1",
277                        sub2: {
278                            subsub1: "subsubval1",
279                        },
280                        sub3: 3u64,
281                    },
282                    k2: 10u64
283                }
284            }
285        });
286    }
287
288    #[fuchsia::test]
289    fn test_inspect_log_var_key_syntax() {
290        let (inspector, mut node) = inspector_and_list_node();
291        let key = "@@@";
292        inspect_log!(node, var key: "!!!");
293
294        assert_data_tree!(inspector, root: {
295            list_node: {
296                "0": {
297                    "@time": AnyProperty,
298                    "@@@": "!!!"
299                }
300            }
301        });
302    }
303
304    #[fuchsia::test]
305    fn test_inspect_log_parsing() {
306        // if this test compiles, it's considered as succeeded
307        let (_inspector, mut node) = inspector_and_list_node();
308
309        // Non-block version, no trailing comma
310        inspect_log!(node, k1: "v1", k2: "v2");
311
312        // Non-block version, trailing comma
313        inspect_log!(node, k1: "v1", k2: "v2",);
314
315        // Block version, no trailing comma
316        inspect_log!(node, {
317            k1: "v1",
318            k2: "v2"
319        });
320
321        // Block version, trailing comma
322        inspect_log!(node, {
323            k1: "v1",
324            k2: "v2",
325        });
326    }
327
328    #[fuchsia::test]
329    fn test_inspect_log_allows_mutex_guard_temporary() {
330        // if this test compiles, it's considered as succeeded
331        let (_inspector, node) = inspector_and_list_node();
332        let node = Mutex::new(node);
333        inspect_log!(node.lock(), k1: "v1");
334    }
335
336    #[fuchsia::test]
337    fn test_inspect_log_macro_does_not_move_value() {
338        // if this test compiles, it's considered as succeeded
339        let (_inspector, mut node) = inspector_and_list_node();
340        let s = String::from("s");
341        inspect_log!(node, s: s);
342
343        // Should not cause compiler error since value is not moved
344        println!("{}", s);
345    }
346
347    #[fuchsia::test]
348    fn test_log_option() {
349        let (inspector, mut node) = inspector_and_list_node();
350
351        inspect_log!(node, some?: Some("a"));
352
353        inspect_log!(node, none?: None as Option<String>);
354
355        assert_data_tree!(inspector, root: {
356            list_node: {
357                "0": { "@time": AnyProperty, some: "a" },
358                "1": { "@time": AnyProperty },
359            }
360        });
361    }
362
363    #[fuchsia::test]
364    fn test_log_inspect_bytes() {
365        let (inspector, mut node) = inspector_and_list_node();
366        let bytes = [11u8, 22, 33];
367
368        inspect_log!(node, bytes: InspectBytes(&bytes));
369        inspect_log!(node, bytes: InspectBytes(&bytes[..]));
370        inspect_log!(node, bytes: InspectBytes(bytes));
371
372        assert_data_tree!(inspector, root: {
373            list_node: {
374                "0": { "@time": AnyProperty, bytes: vec![11u8, 22, 33] },
375                "1": { "@time": AnyProperty, bytes: vec![11u8, 22, 33] },
376                "2": { "@time": AnyProperty, bytes: vec![11u8, 22, 33] },
377            }
378        });
379    }
380
381    #[fuchsia::test]
382    fn test_log_inspect_list() {
383        let (inspector, mut node) = inspector_and_list_node();
384        let list = [11u8, 22, 33];
385
386        inspect_log!(node, list: InspectList(&list));
387
388        assert_data_tree!(inspector, root: {
389            list_node: {
390                "0": {
391                    "@time": AnyProperty,
392                    list: {
393                        "0": 11u64,
394                        "1": 22u64,
395                        "2": 33u64,
396                    }
397                }
398            }
399        });
400    }
401
402    #[fuchsia::test]
403    fn test_log_inspect_list_closure() {
404        let (inspector, mut node) = inspector_and_list_node();
405        let list = [13u32, 17, 29];
406        let list_mapped = InspectListClosure(&list, |node_writer, key, item| {
407            inspect_insert!(node_writer, var key: item * 2);
408        });
409
410        inspect_log!(node, list: list_mapped);
411
412        assert_data_tree!(inspector, root: {
413            list_node: {
414                "0": {
415                    "@time": AnyProperty,
416                    list: {
417                        "0": 26u64,
418                        "1": 34u64,
419                        "2": 58u64,
420                    }
421                }
422            }
423        });
424    }
425
426    #[fuchsia::test]
427    fn test_log_inspect_uint_array() {
428        let (inspector, mut node) = inspector_and_list_node();
429        let list = [1u32, 2, 3, 4, 5, 6];
430
431        inspect_log!(node, list: InspectUintArray::new(&list));
432
433        assert_data_tree!(inspector, root: {
434            list_node: {
435                "0": {
436                    "@time": AnyProperty,
437                    list: vec![1u64, 2, 3, 4, 5, 6],
438                }
439            }
440        });
441    }
442
443    #[fuchsia::test]
444    fn test_inspect_insert_parsing() {
445        // if this test compiles, it's considered as succeeded
446        let (_inspector, mut node) = inspector_and_list_node();
447        let node_writer = node.add_entry(|node_writer| {
448            // Non-block version, no trailing comma
449            inspect_insert!(node_writer, k1: "v1".to_string(), k2: if true { 10u64 } else { 20 });
450        });
451
452        // Non-block version, trailing comma
453        inspect_insert!(node_writer, k1: 1i64, k2: 2f64,);
454
455        // Block version, no trailing comma
456        inspect_insert!(node_writer, {
457            k1: 1u8,
458            k2: 2i8
459        });
460
461        // Block version, trailing comma
462        inspect_insert!(node_writer, {
463            k1: &1u64,
464            k2?: Some("v2"),
465        });
466    }
467
468    #[fuchsia::test]
469    fn test_make_inspect_loggable() {
470        let (inspector, mut node) = inspector_and_list_node();
471
472        let obj = make_inspect_loggable!(k1: "1", k2: 2i64, k3: "3");
473        inspect_log!(node, some_key: obj);
474
475        let point = Some((10i64, 50i64));
476        inspect_log!(node, point?: point.map(|(x, y)| make_inspect_loggable!({
477            x: x,
478            y: y,
479        })));
480
481        assert_data_tree!(inspector, root: {
482            list_node: {
483                "0": {
484                    "@time": AnyProperty,
485                    some_key: { k1: "1", k2: 2i64, k3: "3" },
486                },
487                "1": {
488                    "@time": AnyProperty,
489                    point: { x: 10i64, y: 50i64 },
490                },
491            }
492        });
493    }
494
495    #[fuchsia::test]
496    fn test_log_inspect_string_reference() {
497        let (inspector, mut node) = inspector_and_list_node();
498
499        inspect_log!(node, "foo" => "foo_1");
500        inspect_log!(node, "foo" => "foo_2");
501        inspect_log!(node, "foo" => "foo_3");
502        inspect_log!(node, "foo" => "foo_4");
503
504        assert_data_tree!(inspector, root: {
505            list_node: {
506                "0": { "@time": AnyProperty, foo: "foo_1" },
507                "1": { "@time": AnyProperty, foo: "foo_2" },
508                "2": { "@time": AnyProperty, foo: "foo_3" },
509                "3": { "@time": AnyProperty, foo: "foo_4" },
510            }
511        });
512    }
513
514    fn inspector_and_list_node() -> (Inspector, BoundedListNode) {
515        let inspector = Inspector::default();
516        let list_node = inspector.root().create_child("list_node");
517        let list_node = BoundedListNode::new(list_node, 10);
518        (inspector, list_node)
519    }
520}