rosace_trace/subscribers/
console.rs1use std::sync::atomic::{AtomicUsize, Ordering};
2
3use crate::bus::TraceSubscriber;
4use crate::event::{Method, RosaceTrace};
5
6#[derive(Debug, Clone, PartialEq)]
10pub enum ConsoleFilter {
11 All,
13 State,
15 Network,
17 Performance,
19 Component(String),
21}
22
23pub struct ConsoleSubscriber {
34 filter: ConsoleFilter,
35 event_count: AtomicUsize,
36 include_high_frequency: bool,
41}
42
43impl ConsoleSubscriber {
44 pub fn new() -> Self {
46 Self {
47 filter: ConsoleFilter::All,
48 event_count: AtomicUsize::new(0),
49 include_high_frequency: false,
50 }
51 }
52
53 pub fn with_filter(filter: ConsoleFilter) -> Self {
55 Self {
56 filter,
57 event_count: AtomicUsize::new(0),
58 include_high_frequency: false,
59 }
60 }
61
62 pub fn high_frequency(mut self, on: bool) -> Self {
66 self.include_high_frequency = on;
67 self
68 }
69
70 pub fn event_count(&self) -> usize {
72 self.event_count.load(Ordering::Relaxed)
73 }
74
75 fn should_print(&self, event: &RosaceTrace) -> bool {
76 if event.is_high_frequency()
81 && !self.include_high_frequency
82 && !matches!(self.filter, ConsoleFilter::Performance)
83 {
84 return false;
85 }
86 match &self.filter {
87 ConsoleFilter::All => true,
88 ConsoleFilter::State => {
89 matches!(event, RosaceTrace::AtomRead { .. } | RosaceTrace::AtomWrite { .. })
90 }
91 ConsoleFilter::Network => matches!(
92 event,
93 RosaceTrace::RequestStart { .. } | RosaceTrace::RequestEnd { .. }
94 ),
95 ConsoleFilter::Performance => matches!(
96 event,
97 RosaceTrace::FrameStart { .. }
98 | RosaceTrace::FrameEnd { .. }
99 | RosaceTrace::LayoutStart { .. }
100 | RosaceTrace::LayoutEnd { .. }
101 ),
102 ConsoleFilter::Component(name) => match event {
103 RosaceTrace::ComponentMount { name: n, .. } => *n == name.as_str(),
104 RosaceTrace::ComponentUnmount { name: n, .. } => *n == name.as_str(),
105 RosaceTrace::ComponentRebuild { .. } => true,
107 _ => false,
108 },
109 }
110 }
111
112 pub fn format(event: &RosaceTrace) -> String {
113 match event {
114 RosaceTrace::ComponentMount { name, location, .. } => {
115 format!(
116 "[MOUNT] {:<20} {}:{}",
117 name, location.file, location.line
118 )
119 }
120 RosaceTrace::ComponentUnmount { name, .. } => {
121 format!("[UNMOUNT] {}", name)
122 }
123 RosaceTrace::ComponentRebuild { cause, duration, .. } => {
124 format!(
125 "[REBUILD] cause: {:?} {:.1}ms",
126 cause,
127 duration.as_secs_f64() * 1000.0
128 )
129 }
130 RosaceTrace::AtomRead { atom, component } => {
131 format!("[ATOM] read atom:{} by component:{}", atom.0, component.0)
132 }
133 RosaceTrace::AtomWrite { atom, location, .. } => {
134 format!(
135 "[ATOM] write atom:{} {}:{}",
136 atom.0, location.file, location.line
137 )
138 }
139 RosaceTrace::LayoutStart { component, constraints } => {
140 format!(
141 "[LAYOUT] start component:{} w:[{:.0}..{:?}] h:[{:.0}..{:?}]",
142 component.0,
143 constraints.min_width,
144 constraints.max_width,
145 constraints.min_height,
146 constraints.max_height
147 )
148 }
149 RosaceTrace::LayoutEnd { component, size, duration } => {
150 format!(
151 "[LAYOUT] end component:{} {:.0}x{:.0} {:.2}ms",
152 component.0,
153 size.width,
154 size.height,
155 duration.as_secs_f64() * 1000.0
156 )
157 }
158 RosaceTrace::FrameStart { frame, .. } => {
159 format!("[FRAME] #{:<6} start", frame)
160 }
161 RosaceTrace::FrameEnd { frame, duration, dropped } => {
162 let budget = if *dropped { "✗ DROPPED" } else { "✓" };
163 format!(
164 "[FRAME] #{:<6} {:.2}ms {}",
165 frame,
166 duration.as_secs_f64() * 1000.0,
167 budget
168 )
169 }
170 RosaceTrace::PaintRegion { rect } => {
171 format!(
172 "[PAINT] ({:.0},{:.0}) {:.0}x{:.0}",
173 rect.origin.x, rect.origin.y, rect.size.width, rect.size.height
174 )
175 }
176 RosaceTrace::RouteChange { from, to, transition } => {
177 let from_str = from.as_ref().map(|r| r.0.as_str()).unwrap_or("(none)");
178 format!(
179 "[ROUTE] {} → {} {}",
180 from_str, to.0, transition.0
181 )
182 }
183 RosaceTrace::RequestStart { url, method, .. } => {
184 let m = match method {
185 Method::Get => "GET",
186 Method::Post => "POST",
187 Method::Put => "PUT",
188 Method::Delete => "DELETE",
189 Method::Patch => "PATCH",
190 Method::Other(s) => s.as_str(),
191 };
192 format!("[REQUEST] {} {}", m, url)
193 }
194 RosaceTrace::RequestEnd { id, status, duration, cached, size } => {
195 let cache = if *cached { "cache:hit" } else { "cache:miss" };
196 format!(
197 "[REQUEST] id:{} {} {:.0}ms {} {}b",
198 id.0,
199 status,
200 duration.as_secs_f64() * 1000.0,
201 cache,
202 size
203 )
204 }
205 RosaceTrace::FfiCall { fn_name, duration } => {
206 format!(
207 "[FFI] {} {:.2}ms",
208 fn_name,
209 duration.as_secs_f64() * 1000.0
210 )
211 }
212 RosaceTrace::FfiError { fn_name, error } => {
213 format!("[FFI] {} ERROR: {}", fn_name, error)
214 }
215 RosaceTrace::GestureReceived { kind, handler } => {
216 format!("[GESTURE] {:?} handler:{}", kind, handler.0)
217 }
218 RosaceTrace::ShaderRegister { pipeline, wgsl_len } => {
219 format!("[SHADER] register pipeline:{} wgsl:{}b", pipeline, wgsl_len)
220 }
221 RosaceTrace::Log { level, target, message, .. } => {
222 format!("[{}] {} {}", level.label().trim(), target, message)
223 }
224 }
225 }
226}
227
228impl Default for ConsoleSubscriber {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233
234impl TraceSubscriber for ConsoleSubscriber {
235 fn on_trace(&self, event: &RosaceTrace) {
236 self.event_count.fetch_add(1, Ordering::Relaxed);
237 if self.should_print(event) {
238 eprintln!("{}", Self::format(event));
239 }
240 }
241}