1- // Logging setup for wsrx-desktop-gpui
2- use std:: fs;
1+ // UI Logger - Custom tracing subscriber that sends logs to the UI
2+ //
3+ // This layer captures tracing events and forwards them to a channel
4+ // that the UI can consume to display logs in real-time.
35
4- use anyhow:: Result ;
5- use directories:: ProjectDirs ;
6- use tracing_appender:: non_blocking;
7- use tracing_subscriber:: { EnvFilter , fmt, prelude:: * } ;
6+ use std:: sync:: Arc ;
87
9- pub fn setup ( ) -> Result < (
8+ use chrono:: Local ;
9+ use tokio:: sync:: mpsc;
10+ use tracing:: { Event , Level , Subscriber } ;
11+ use tracing_subscriber:: { Layer , layer:: Context } ;
12+
13+ use crate :: models:: LogEntry ;
14+
15+ /// A tracing layer that sends log entries to the UI via a channel
16+ pub struct UILogLayer {
17+ sender : Arc < mpsc:: UnboundedSender < LogEntry > > ,
18+ }
19+
20+ impl UILogLayer {
21+ /// Create a new UI log layer
22+ /// Returns the layer and a receiver for consuming log entries
23+ pub fn new ( ) -> ( Self , mpsc:: UnboundedReceiver < LogEntry > ) {
24+ let ( sender, receiver) = mpsc:: unbounded_channel ( ) ;
25+ let layer = Self {
26+ sender : Arc :: new ( sender) ,
27+ } ;
28+ ( layer, receiver)
29+ }
30+ }
31+
32+ impl < S > Layer < S > for UILogLayer
33+ where
34+ S : Subscriber ,
35+ {
36+ fn on_event ( & self , event : & Event < ' _ > , _ctx : Context < ' _ , S > ) {
37+ // Extract log level
38+ let level = match * event. metadata ( ) . level ( ) {
39+ Level :: TRACE => "TRACE" ,
40+ Level :: DEBUG => "DEBUG" ,
41+ Level :: INFO => "INFO" ,
42+ Level :: WARN => "WARN" ,
43+ Level :: ERROR => "ERROR" ,
44+ } ;
45+
46+ // Extract target (module path)
47+ let target = event. metadata ( ) . target ( ) ;
48+
49+ // Format timestamp
50+ let timestamp = Local :: now ( ) . format ( "%Y-%m-%d %H:%M:%S" ) . to_string ( ) ;
51+
52+ // Extract message from event
53+ // Note: This is a simplified approach. For production, you'd want to
54+ // use a visitor pattern to properly extract all fields.
55+ let mut message = String :: new ( ) ;
56+ event. record ( & mut MessageVisitor {
57+ message : & mut message,
58+ } ) ;
59+
60+ // Create log entry
61+ let log_entry = LogEntry {
62+ timestamp,
63+ level : level. to_string ( ) ,
64+ target : target. to_string ( ) ,
65+ message,
66+ } ;
67+
68+ // Send to UI (ignore errors if receiver is dropped)
69+ let _ = self . sender . send ( log_entry) ;
70+ }
71+ }
72+
73+ /// Visitor for extracting the message from a tracing event
74+ struct MessageVisitor < ' a > {
75+ message : & ' a mut String ,
76+ }
77+
78+ impl < ' a > tracing:: field:: Visit for MessageVisitor < ' a > {
79+ fn record_debug ( & mut self , field : & tracing:: field:: Field , value : & dyn std:: fmt:: Debug ) {
80+ if field. name ( ) == "message" {
81+ * self . message = format ! ( "{:?}" , value) ;
82+ // Remove quotes added by Debug formatting
83+ if self . message . starts_with ( '"' ) && self . message . ends_with ( '"' ) {
84+ * self . message = self . message [ 1 ..self . message . len ( ) - 1 ] . to_string ( ) ;
85+ }
86+ } else {
87+ // Append other fields to the message
88+ if !self . message . is_empty ( ) {
89+ self . message . push_str ( ", " ) ;
90+ }
91+ self . message
92+ . push_str ( & format ! ( "{}={:?}" , field. name( ) , value) ) ;
93+ }
94+ }
95+ }
96+
97+ /// Set up logging with UI layer
98+ /// Returns guards for file and console loggers, plus a receiver for UI logs
99+ pub fn setup_with_ui ( ) -> anyhow:: Result < (
10100 tracing_appender:: non_blocking:: WorkerGuard ,
11101 tracing_appender:: non_blocking:: WorkerGuard ,
102+ mpsc:: UnboundedReceiver < LogEntry > ,
12103) > {
104+ use std:: fs;
105+
106+ use directories:: ProjectDirs ;
107+ use tracing_appender:: non_blocking;
108+ use tracing_subscriber:: { EnvFilter , fmt, prelude:: * } ;
109+
13110 // Get platform-specific directories
14111 let proj_dirs = ProjectDirs :: from ( "org" , "xdsec" , "wsrx-desktop-gpui" )
15112 . ok_or_else ( || anyhow:: anyhow!( "Failed to get project directories" ) ) ?;
@@ -24,7 +121,10 @@ pub fn setup() -> Result<(
24121 let file_appender = tracing_appender:: rolling:: daily ( log_dir, "wsrx-desktop-gpui.log" ) ;
25122 let ( file_non_blocking, file_guard) = non_blocking ( file_appender) ;
26123
27- // Set up the subscriber with both console and file output
124+ // UI logger
125+ let ( ui_layer, ui_receiver) = UILogLayer :: new ( ) ;
126+
127+ // Set up the subscriber with console, file, and UI output
28128 tracing_subscriber:: registry ( )
29129 . with (
30130 fmt:: layer ( )
@@ -37,9 +137,10 @@ pub fn setup() -> Result<(
37137 . with_writer ( file_non_blocking)
38138 . with_filter ( EnvFilter :: from_default_env ( ) ) ,
39139 )
140+ . with ( ui_layer)
40141 . init ( ) ;
41142
42- tracing:: info!( "Logging initialized for wsrx-desktop-gpui " ) ;
143+ tracing:: info!( "Logger initialized. " ) ;
43144
44- Ok ( ( console_guard, file_guard) )
145+ Ok ( ( console_guard, file_guard, ui_receiver ) )
45146}
0 commit comments