Skip to main content

horizon_lattice_core/
logging.rs

1//! Logging and debugging facilities for Horizon Lattice.
2//!
3//! This module provides:
4//! - Integration with the `tracing` crate for structured logging
5//! - Debug visualization for object trees
6//! - Performance tracing hooks for profiling
7//!
8//! # Tracing Integration
9//!
10//! Horizon Lattice uses the `tracing` crate for instrumentation. To see logs,
11//! you need to install a tracing subscriber in your application:
12//!
13//! ```ignore
14//! use tracing_subscriber;
15//!
16//! fn main() {
17//!     // Initialize tracing (you can customize this)
18//!     tracing_subscriber::fmt::init();
19//!
20//!     // Your application code...
21//! }
22//! ```
23//!
24//! # Debug Visualization
25//!
26//! Use [`ObjectTreeDebug`] to get detailed views of the object hierarchy:
27//!
28//! ```ignore
29//! use horizon_lattice_core::logging::ObjectTreeDebug;
30//!
31//! let debug = ObjectTreeDebug::new();
32//! println!("{}", debug.format_tree());
33//! ```
34
35use std::fmt::{self, Write as FmtWrite};
36
37use crate::object::{ObjectId, ObjectResult, global_registry};
38
39/// Span names used throughout Horizon Lattice for tracing.
40///
41/// These constants can be used to filter traces for specific subsystems.
42pub mod span_names {
43    /// Event loop processing span.
44    pub const EVENT_LOOP: &str = "horizon_lattice::event_loop";
45    /// Timer processing span.
46    pub const TIMER: &str = "horizon_lattice::timer";
47    /// Signal emission span.
48    pub const SIGNAL: &str = "horizon_lattice::signal";
49    /// Property change span.
50    pub const PROPERTY: &str = "horizon_lattice::property";
51    /// Object lifecycle span.
52    pub const OBJECT: &str = "horizon_lattice::object";
53    /// Task queue processing span.
54    pub const TASK: &str = "horizon_lattice::task";
55}
56
57/// Target names for log filtering.
58///
59/// Use these with `tracing` directives to filter logs by subsystem.
60pub mod targets {
61    /// Core framework target.
62    pub const CORE: &str = "horizon_lattice_core";
63    /// Event loop target.
64    pub const EVENT_LOOP: &str = "horizon_lattice_core::event_loop";
65    /// Timer system target.
66    pub const TIMER: &str = "horizon_lattice_core::timer";
67    /// Signal/slot system target.
68    pub const SIGNAL: &str = "horizon_lattice_core::signal";
69    /// Property system target.
70    pub const PROPERTY: &str = "horizon_lattice_core::property";
71    /// Object model target.
72    pub const OBJECT: &str = "horizon_lattice_core::object";
73}
74
75/// Style options for object tree visualization.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
77pub enum TreeStyle {
78    /// ASCII characters for tree branches.
79    Ascii,
80    /// Unicode box-drawing characters.
81    #[default]
82    Unicode,
83    /// Compact single-line representation.
84    Compact,
85}
86
87/// Configuration for object tree debug output.
88#[derive(Debug, Clone)]
89pub struct TreeFormatOptions {
90    /// The style of tree visualization.
91    pub style: TreeStyle,
92    /// Whether to show object IDs.
93    pub show_ids: bool,
94    /// Whether to show type names.
95    pub show_types: bool,
96    /// Whether to show dynamic property names.
97    pub show_properties: bool,
98    /// Maximum depth to traverse (None for unlimited).
99    pub max_depth: Option<usize>,
100    /// Indent size for each level.
101    pub indent_size: usize,
102}
103
104impl Default for TreeFormatOptions {
105    fn default() -> Self {
106        Self {
107            style: TreeStyle::default(),
108            show_ids: true,
109            show_types: true,
110            show_properties: false,
111            max_depth: None,
112            indent_size: 2,
113        }
114    }
115}
116
117impl TreeFormatOptions {
118    /// Create options for detailed debugging output.
119    pub fn detailed() -> Self {
120        Self {
121            show_properties: true,
122            ..Default::default()
123        }
124    }
125
126    /// Create options for minimal output.
127    pub fn minimal() -> Self {
128        Self {
129            show_ids: false,
130            show_types: false,
131            show_properties: false,
132            ..Default::default()
133        }
134    }
135}
136
137/// Debug utility for visualizing object trees.
138///
139/// This provides various methods for inspecting and displaying the
140/// object hierarchy in a human-readable format.
141#[derive(Debug, Clone)]
142pub struct ObjectTreeDebug {
143    options: TreeFormatOptions,
144}
145
146impl ObjectTreeDebug {
147    /// Create a new debug visualizer with default options.
148    pub fn new() -> Self {
149        Self {
150            options: TreeFormatOptions::default(),
151        }
152    }
153
154    /// Create a debug visualizer with custom options.
155    pub fn with_options(options: TreeFormatOptions) -> Self {
156        Self { options }
157    }
158
159    /// Format the entire object tree starting from all root objects.
160    pub fn format_all(&self) -> ObjectResult<String> {
161        let registry = global_registry()?;
162        let roots = registry.root_objects();
163
164        let mut output = String::new();
165        writeln!(
166            output,
167            "Object Tree ({} total objects):",
168            registry.object_count()
169        )
170        .expect("write to String");
171
172        if roots.is_empty() {
173            writeln!(output, "  (empty)").expect("write to String");
174        } else {
175            for root_id in roots {
176                self.format_subtree_into(root_id, 0, true, &mut output)?;
177            }
178        }
179
180        Ok(output)
181    }
182
183    /// Format a subtree starting from a specific object.
184    pub fn format_subtree(&self, root: ObjectId) -> ObjectResult<String> {
185        let mut output = String::new();
186        self.format_subtree_into(root, 0, true, &mut output)?;
187        Ok(output)
188    }
189
190    /// Format a subtree into an existing string buffer.
191    fn format_subtree_into(
192        &self,
193        id: ObjectId,
194        depth: usize,
195        is_last: bool,
196        output: &mut String,
197    ) -> ObjectResult<()> {
198        // Check max depth
199        if let Some(max) = self.options.max_depth
200            && depth > max
201        {
202            return Ok(());
203        }
204
205        let registry = global_registry()?;
206
207        // Handle objects that may have been removed (e.g., in concurrent test scenarios)
208        let name = match registry.object_name(id) {
209            Ok(name) => name,
210            Err(crate::object::ObjectError::InvalidObjectId) => return Ok(()),
211            Err(e) => return Err(e),
212        };
213        let type_name = match registry.type_name(id) {
214            Ok(name) => name,
215            Err(crate::object::ObjectError::InvalidObjectId) => return Ok(()),
216            Err(e) => return Err(e),
217        };
218        let children = match registry.children(id) {
219            Ok(children) => children,
220            Err(crate::object::ObjectError::InvalidObjectId) => return Ok(()),
221            Err(e) => return Err(e),
222        };
223
224        // Build the prefix based on style and depth
225        let prefix = self.build_prefix(depth, is_last);
226        output.push_str(&prefix);
227
228        // Object name
229        let display_name = if name.is_empty() { "(unnamed)" } else { &name };
230        output.push_str(display_name);
231
232        // Optional ID
233        if self.options.show_ids {
234            write!(output, " [{:?}]", id).expect("write to String");
235        }
236
237        // Optional type
238        if self.options.show_types {
239            // Extract just the type name without the full path for readability
240            let short_type = type_name.rsplit("::").next().unwrap_or(type_name);
241            write!(output, " ({})", short_type).expect("write to String");
242        }
243
244        output.push('\n');
245
246        // Optional properties
247        if self.options.show_properties {
248            let prop_names: Vec<String> = registry.with_read(|r| {
249                r.dynamic_property_names(id)
250                    .map(|names| names.into_iter().map(|s| s.to_string()).collect())
251            })?;
252            if !prop_names.is_empty() {
253                let prop_prefix = self.build_property_prefix(depth, is_last);
254                for prop_name in prop_names {
255                    writeln!(output, "{}  .{}", prop_prefix, prop_name).expect("write to String");
256                }
257            }
258        }
259
260        // Recursively format children
261        let child_count = children.len();
262        for (i, child_id) in children.into_iter().enumerate() {
263            let child_is_last = i == child_count - 1;
264            self.format_subtree_into(child_id, depth + 1, child_is_last, output)?;
265        }
266
267        Ok(())
268    }
269
270    /// Build the prefix string for a tree node.
271    fn build_prefix(&self, depth: usize, is_last: bool) -> String {
272        if depth == 0 {
273            return String::new();
274        }
275
276        let (branch, corner, space) = match self.options.style {
277            TreeStyle::Ascii => ("|", "+--", "   "),
278            TreeStyle::Unicode => (
279                "\u{2502}",
280                "\u{251c}\u{2500}\u{2500}",
281                "\u{2514}\u{2500}\u{2500}",
282            ),
283            TreeStyle::Compact => ("", "- ", "- "),
284        };
285
286        let mut prefix = String::new();
287
288        // Add indentation for parent levels
289        for _ in 0..(depth - 1) {
290            prefix.push_str(branch);
291            for _ in 0..self.options.indent_size {
292                prefix.push(' ');
293            }
294        }
295
296        // Add the connector for this level
297        if is_last {
298            prefix.push_str(if self.options.style == TreeStyle::Unicode {
299                "\u{2514}\u{2500}\u{2500} "
300            } else {
301                space
302            });
303        } else {
304            prefix.push_str(corner);
305            prefix.push(' ');
306        }
307
308        prefix
309    }
310
311    /// Build the prefix for property lines.
312    fn build_property_prefix(&self, depth: usize, _is_last: bool) -> String {
313        let (branch, _) = match self.options.style {
314            TreeStyle::Ascii => ("|", "   "),
315            TreeStyle::Unicode => ("\u{2502}", "   "),
316            TreeStyle::Compact => ("", "  "),
317        };
318
319        let mut prefix = String::new();
320        for _ in 0..depth {
321            prefix.push_str(branch);
322            for _ in 0..self.options.indent_size {
323                prefix.push(' ');
324            }
325        }
326        prefix
327    }
328}
329
330impl Default for ObjectTreeDebug {
331    fn default() -> Self {
332        Self::new()
333    }
334}
335
336impl fmt::Display for ObjectTreeDebug {
337    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338        match self.format_all() {
339            Ok(output) => write!(f, "{}", output),
340            Err(e) => write!(f, "Error formatting object tree: {}", e),
341        }
342    }
343}
344
345/// A guard that emits a tracing span when dropped.
346///
347/// This is useful for tracking the duration of operations.
348#[derive(Debug)]
349pub struct PerfSpan {
350    #[allow(dead_code)]
351    span: tracing::span::EnteredSpan,
352}
353
354impl PerfSpan {
355    /// Create a new performance span.
356    ///
357    /// The span will be active until the guard is dropped.
358    pub fn new(name: &'static str) -> Self {
359        let span = tracing::info_span!(target: "horizon_lattice::perf", "perf", operation = name);
360        Self {
361            span: span.entered(),
362        }
363    }
364}
365
366/// Macros for common tracing patterns.
367///
368/// These are re-exported for convenience but are just wrappers around
369/// the `tracing` crate macros with consistent target naming.
370#[macro_export]
371macro_rules! lattice_trace {
372    ($($arg:tt)*) => {
373        tracing::trace!(target: "horizon_lattice_core", $($arg)*)
374    };
375}
376
377/// Log a debug-level message to the Horizon Lattice target.
378///
379/// This macro wraps `tracing::debug!` with the `horizon_lattice_core` target.
380#[macro_export]
381macro_rules! lattice_debug {
382    ($($arg:tt)*) => {
383        tracing::debug!(target: "horizon_lattice_core", $($arg)*)
384    };
385}
386
387/// Log an info-level message to the Horizon Lattice target.
388///
389/// This macro wraps `tracing::info!` with the `horizon_lattice_core` target.
390#[macro_export]
391macro_rules! lattice_info {
392    ($($arg:tt)*) => {
393        tracing::info!(target: "horizon_lattice_core", $($arg)*)
394    };
395}
396
397/// Log a warning-level message to the Horizon Lattice target.
398///
399/// This macro wraps `tracing::warn!` with the `horizon_lattice_core` target.
400#[macro_export]
401macro_rules! lattice_warn {
402    ($($arg:tt)*) => {
403        tracing::warn!(target: "horizon_lattice_core", $($arg)*)
404    };
405}
406
407/// Log an error-level message to the Horizon Lattice target.
408///
409/// This macro wraps `tracing::error!` with the `horizon_lattice_core` target.
410#[macro_export]
411macro_rules! lattice_error {
412    ($($arg:tt)*) => {
413        tracing::error!(target: "horizon_lattice_core", $($arg)*)
414    };
415}
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420    use crate::object::{Object, ObjectBase, init_global_registry};
421
422    struct TestWidget {
423        base: ObjectBase,
424    }
425
426    impl TestWidget {
427        fn new(name: &str) -> Self {
428            let widget = Self {
429                base: ObjectBase::new::<Self>(),
430            };
431            widget.base.set_name(name);
432            widget
433        }
434    }
435
436    impl Object for TestWidget {
437        fn object_id(&self) -> ObjectId {
438            self.base.id()
439        }
440    }
441
442    fn setup() {
443        init_global_registry();
444    }
445
446    #[test]
447    fn test_tree_format_empty() {
448        setup();
449        let debug = ObjectTreeDebug::new();
450        let output = debug.format_all().unwrap();
451        assert!(output.contains("Object Tree"));
452    }
453
454    #[test]
455    fn test_tree_format_single() {
456        setup();
457        let widget = TestWidget::new("root");
458
459        let debug = ObjectTreeDebug::new();
460        let output = debug.format_subtree(widget.object_id()).unwrap();
461
462        assert!(output.contains("root"));
463        assert!(output.contains("TestWidget"));
464    }
465
466    #[test]
467    fn test_tree_format_hierarchy() {
468        setup();
469        let root = TestWidget::new("window");
470        let child1 = TestWidget::new("button1");
471        let child2 = TestWidget::new("button2");
472
473        child1.base.set_parent(Some(root.object_id())).unwrap();
474        child2.base.set_parent(Some(root.object_id())).unwrap();
475
476        let debug = ObjectTreeDebug::new();
477        let output = debug.format_subtree(root.object_id()).unwrap();
478
479        assert!(output.contains("window"));
480        assert!(output.contains("button1"));
481        assert!(output.contains("button2"));
482    }
483
484    #[test]
485    fn test_tree_format_minimal() {
486        setup();
487        let widget = TestWidget::new("test");
488
489        let debug = ObjectTreeDebug::with_options(TreeFormatOptions::minimal());
490        let output = debug.format_subtree(widget.object_id()).unwrap();
491
492        assert!(output.contains("test"));
493        assert!(!output.contains("TestWidget"));
494        assert!(!output.contains("["));
495    }
496
497    #[test]
498    fn test_perf_span() {
499        setup();
500        // Just ensure it compiles and doesn't panic
501        let _span = PerfSpan::new("test_operation");
502    }
503}