tauri_plugin_tracing/
layer.rs

1//! WebviewLayer for forwarding log events to the frontend.
2
3use serde::{Deserialize, Serialize};
4use serde_repr::{Deserialize_repr, Serialize_repr};
5use tauri::{AppHandle, Emitter, Runtime};
6use tracing_subscriber::Layer;
7
8/// A log message consisting of one or more string parts.
9///
10/// This type wraps a `Vec<String>` to allow logging multiple values in a single call.
11/// When displayed, the parts are joined with ", ".
12#[derive(Debug, Deserialize, Serialize, Clone)]
13#[serde(rename_all = "camelCase")]
14#[cfg_attr(feature = "specta", derive(specta::Type))]
15pub struct LogMessage(Vec<String>);
16
17impl std::ops::Deref for LogMessage {
18    type Target = Vec<String>;
19
20    fn deref(&self) -> &Self::Target {
21        &self.0
22    }
23}
24
25impl std::ops::DerefMut for LogMessage {
26    fn deref_mut(&mut self) -> &mut Self::Target {
27        &mut self.0
28    }
29}
30
31impl std::fmt::Display for LogMessage {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f, "{}", self.join(", "))
34    }
35}
36
37/// Payload for a log record, used when emitting events to the webview.
38#[derive(Debug, Serialize, Clone)]
39#[cfg_attr(feature = "specta", derive(specta::Type))]
40pub struct RecordPayload {
41    /// The formatted log message.
42    pub message: String,
43    /// The severity level of the log.
44    pub level: LogLevel,
45}
46
47/// A tracing layer that emits log events to the webview via Tauri events.
48///
49/// This layer intercepts all log events and forwards them to the frontend
50/// via the `tracing://log` event, allowing JavaScript code to receive
51/// logs using `attachLogger()` or `attachConsole()`.
52///
53/// # Example
54///
55/// By default, the plugin does not set up a global subscriber. Use this layer
56/// when composing your own subscriber:
57///
58/// ```rust,no_run
59/// # use tauri_plugin_tracing::{Builder, WebviewLayer, LevelFilter};
60/// # use tracing_subscriber::{Registry, layer::SubscriberExt, util::SubscriberInitExt, fmt};
61/// let builder = Builder::new()
62///     .with_max_level(LevelFilter::DEBUG);
63/// let filter = builder.build_filter();
64///
65/// tauri::Builder::default()
66///     .plugin(builder.build())
67///     .setup(move |app| {
68///         Registry::default()
69///             .with(fmt::layer())
70///             .with(WebviewLayer::new(app.handle().clone()))
71///             .with(filter)
72///             .init();
73///         Ok(())
74///     });
75///     // .run(tauri::generate_context!())
76/// ```
77pub struct WebviewLayer<R: Runtime> {
78    app_handle: AppHandle<R>,
79}
80
81impl<R: Runtime> WebviewLayer<R> {
82    /// Creates a new WebviewLayer that forwards log events to the given app handle.
83    ///
84    /// Events are emitted via the `tracing://log` event channel.
85    pub fn new(app_handle: AppHandle<R>) -> Self {
86        Self { app_handle }
87    }
88}
89
90impl<S, R: Runtime> Layer<S> for WebviewLayer<R>
91where
92    S: tracing::Subscriber,
93{
94    fn on_event(
95        &self,
96        event: &tracing::Event<'_>,
97        _ctx: tracing_subscriber::layer::Context<'_, S>,
98    ) {
99        let mut visitor = MessageVisitor::default();
100        event.record(&mut visitor);
101
102        let level: LogLevel = (*event.metadata().level()).into();
103        let payload = RecordPayload {
104            message: visitor.message,
105            level,
106        };
107
108        let _ = self.app_handle.emit("tracing://log", payload);
109    }
110}
111
112#[derive(Default)]
113struct MessageVisitor {
114    message: String,
115}
116
117impl tracing::field::Visit for MessageVisitor {
118    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
119        if field.name() == "message" || self.message.is_empty() {
120            self.message = format!("{:?}", value);
121        }
122    }
123
124    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
125        if field.name() == "message" || self.message.is_empty() {
126            self.message = value.to_string();
127        }
128    }
129}
130
131/// An enum representing the available verbosity levels of the logger.
132///
133/// It is very similar to `log::Level`, but serializes to unsigned ints instead of strings.
134///
135/// # Examples
136///
137/// ```
138/// use tauri_plugin_tracing::LogLevel;
139///
140/// // Default is Info
141/// assert!(matches!(LogLevel::default(), LogLevel::Info));
142///
143/// // Convert to tracing::Level
144/// let level: tracing::Level = LogLevel::Debug.into();
145/// assert_eq!(level, tracing::Level::DEBUG);
146///
147/// // Convert from tracing::Level
148/// let log_level: LogLevel = tracing::Level::WARN.into();
149/// assert!(matches!(log_level, LogLevel::Warn));
150/// ```
151#[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Default)]
152#[repr(u16)]
153#[cfg_attr(feature = "specta", derive(specta::Type))]
154pub enum LogLevel {
155    /// The "trace" level.
156    ///
157    /// Designates very low priority, often extremely verbose, information.
158    Trace = 1,
159    /// The "debug" level.
160    ///
161    /// Designates lower priority information.
162    Debug,
163    /// The "info" level.
164    ///
165    /// Designates useful information.
166    #[default]
167    Info,
168    /// The "warn" level.
169    ///
170    /// Designates hazardous situations.
171    Warn,
172    /// The "error" level.
173    ///
174    /// Designates very serious errors.
175    Error,
176}
177
178impl From<LogLevel> for tracing::Level {
179    fn from(log_level: LogLevel) -> Self {
180        match log_level {
181            LogLevel::Trace => tracing::Level::TRACE,
182            LogLevel::Debug => tracing::Level::DEBUG,
183            LogLevel::Info => tracing::Level::INFO,
184            LogLevel::Warn => tracing::Level::WARN,
185            LogLevel::Error => tracing::Level::ERROR,
186        }
187    }
188}
189
190impl From<tracing::Level> for LogLevel {
191    fn from(log_level: tracing::Level) -> Self {
192        match log_level {
193            tracing::Level::TRACE => LogLevel::Trace,
194            tracing::Level::DEBUG => LogLevel::Debug,
195            tracing::Level::INFO => LogLevel::Info,
196            tracing::Level::WARN => LogLevel::Warn,
197            tracing::Level::ERROR => LogLevel::Error,
198        }
199    }
200}