File size: 7,142 Bytes
a21c316
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
//! Log Module Bridge - Captures tracing logs and emits them to the frontend via Tauri Events.
//! Uses a global ring buffer that can be attached to Tauri after app initialization.

use parking_lot::RwLock;
use serde::Serialize;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, OnceLock};
use tauri::Emitter;
use tracing::field::{Field, Visit};
use tracing::{Event, Level, Subscriber};
use tracing_subscriber::layer::Context;
use tracing_subscriber::Layer;

/// Maximum logs to keep in buffer
const MAX_BUFFER_SIZE: usize = 5000;

/// Global flag to enable/disable log bridging
static LOG_BRIDGE_ENABLED: AtomicBool = AtomicBool::new(false);

/// Atomic counter for unique log IDs
static LOG_ID_COUNTER: AtomicU64 = AtomicU64::new(0);

/// Global app handle for emitting events (set once during setup)
static APP_HANDLE: OnceLock<tauri::AppHandle> = OnceLock::new();

/// Global log buffer for storing logs before UI connects
static LOG_BUFFER: OnceLock<Arc<RwLock<VecDeque<LogEntry>>>> = OnceLock::new();

fn get_log_buffer() -> &'static Arc<RwLock<VecDeque<LogEntry>>> {
    LOG_BUFFER.get_or_init(|| Arc::new(RwLock::new(VecDeque::with_capacity(MAX_BUFFER_SIZE))))
}

/// Log entry sent to frontend
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LogEntry {
    pub id: u64,
    pub timestamp: i64,
    pub level: String,
    pub target: String,
    pub message: String,
    pub fields: std::collections::HashMap<String, String>,
}

/// Initialize the log bridge with app handle (call from setup)
pub fn init_log_bridge(app_handle: tauri::AppHandle) {
    let _ = APP_HANDLE.set(app_handle);
    tracing::debug!("[LogBridge] Initialized with app handle");
}

/// Enable log bridging and emit buffered logs
pub fn enable_log_bridge() {
    LOG_BRIDGE_ENABLED.store(true, Ordering::SeqCst);

    // Emit all buffered logs to frontend
    if let Some(handle) = APP_HANDLE.get() {
        let buffer = get_log_buffer().read();
        for entry in buffer.iter() {
            let _ = handle.emit("log-event", entry.clone());
        }
    }

    tracing::info!("[LogBridge] Debug console enabled");
}

/// Disable log bridging
pub fn disable_log_bridge() {
    LOG_BRIDGE_ENABLED.store(false, Ordering::SeqCst);
    tracing::info!("[LogBridge] Debug console disabled");
}

/// Check if log bridging is enabled
pub fn is_log_bridge_enabled() -> bool {
    LOG_BRIDGE_ENABLED.load(Ordering::SeqCst)
}

/// Get all buffered logs
pub fn get_buffered_logs() -> Vec<LogEntry> {
    get_log_buffer().read().iter().cloned().collect()
}

/// Clear log buffer
pub fn clear_log_buffer() {
    get_log_buffer().write().clear();
}

/// Emit accounts://refreshed event to notify the frontend of account state changes
/// This is used by background tasks (e.g. warmup 403 handling) that cannot access AppHandle directly.
pub fn emit_accounts_refreshed() {
    if let Some(handle) = APP_HANDLE.get() {
        let _ = handle.emit("accounts://refreshed", ());
        tracing::debug!("[LogBridge] Emitted accounts://refreshed event to frontend");
    }
}

/// Visitor to extract fields from tracing events
struct FieldVisitor {
    message: Option<String>,
    fields: std::collections::HashMap<String, String>,
}

impl FieldVisitor {
    fn new() -> Self {
        Self {
            message: None,
            fields: std::collections::HashMap::new(),
        }
    }
}

impl Visit for FieldVisitor {
    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
        let value_str = format!("{:?}", value);
        if field.name() == "message" {
            self.message = Some(value_str.trim_matches('"').to_string());
        } else {
            self.fields.insert(field.name().to_string(), value_str);
        }
    }

    fn record_str(&mut self, field: &Field, value: &str) {
        if field.name() == "message" {
            self.message = Some(value.to_string());
        } else {
            self.fields
                .insert(field.name().to_string(), value.to_string());
        }
    }

    fn record_i64(&mut self, field: &Field, value: i64) {
        self.fields
            .insert(field.name().to_string(), value.to_string());
    }

    fn record_u64(&mut self, field: &Field, value: u64) {
        self.fields
            .insert(field.name().to_string(), value.to_string());
    }

    fn record_bool(&mut self, field: &Field, value: bool) {
        self.fields
            .insert(field.name().to_string(), value.to_string());
    }
}

/// Tracing Layer that bridges logs to buffer and optionally to Tauri frontend
pub struct TauriLogBridgeLayer;

impl TauriLogBridgeLayer {
    pub fn new() -> Self {
        Self
    }
}

impl Default for TauriLogBridgeLayer {
    fn default() -> Self {
        Self::new()
    }
}

impl<S> Layer<S> for TauriLogBridgeLayer
where
    S: Subscriber,
{
    fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
        // [FIX] 如果调试控制台未启用,直接跳过所有处理,避免性能损耗
        if !LOG_BRIDGE_ENABLED.load(Ordering::Relaxed) {
            return;
        }

        // Extract metadata
        let metadata = event.metadata();
        let level = match *metadata.level() {
            Level::ERROR => "ERROR",
            Level::WARN => "WARN",
            Level::INFO => "INFO",
            Level::DEBUG => "DEBUG",
            Level::TRACE => "TRACE",
        };

        // Visit fields
        let mut visitor = FieldVisitor::new();
        event.record(&mut visitor);

        // Build message
        let message = visitor.message.unwrap_or_default();

        // Skip empty messages and internal noise
        if message.is_empty() && visitor.fields.is_empty() {
            return;
        }

        // Create log entry
        let entry = LogEntry {
            id: LOG_ID_COUNTER.fetch_add(1, Ordering::SeqCst),
            timestamp: chrono::Utc::now().timestamp_millis(),
            level: level.to_string(),
            target: metadata.target().to_string(),
            message,
            fields: visitor.fields,
        };

        // Add to buffer
        {
            let mut buffer = get_log_buffer().write();
            if buffer.len() >= MAX_BUFFER_SIZE {
                buffer.pop_front();
            }
            buffer.push_back(entry.clone());
        }

        // Emit to frontend
        if let Some(handle) = APP_HANDLE.get() {
            let _ = handle.emit("log-event", entry);
        }
    }
}

// ============================================================================
// Tauri Commands
// ============================================================================

#[tauri::command]
pub fn enable_debug_console() {
    enable_log_bridge();
}

#[tauri::command]
pub fn disable_debug_console() {
    disable_log_bridge();
}

#[tauri::command]
pub fn is_debug_console_enabled() -> bool {
    is_log_bridge_enabled()
}

#[tauri::command]
pub fn get_debug_console_logs() -> Vec<LogEntry> {
    get_buffered_logs()
}

#[tauri::command]
pub fn clear_debug_console_logs() {
    clear_log_buffer();
}