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