use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use tauri::{AppHandle, Emitter, Runtime};
use tracing_subscriber::Layer;
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub struct LogMessage(Vec<String>);
impl std::ops::Deref for LogMessage {
type Target = Vec<String>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for LogMessage {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl std::fmt::Display for LogMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.join(", "))
}
}
#[derive(Debug, Serialize, Clone)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub struct RecordPayload {
pub message: String,
pub level: LogLevel,
}
pub struct WebviewLayer<R: Runtime> {
app_handle: AppHandle<R>,
}
impl<R: Runtime> WebviewLayer<R> {
pub fn new(app_handle: AppHandle<R>) -> Self {
Self { app_handle }
}
}
impl<S, R: Runtime> Layer<S> for WebviewLayer<R>
where
S: tracing::Subscriber,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let mut visitor = MessageVisitor::default();
event.record(&mut visitor);
let level: LogLevel = (*event.metadata().level()).into();
let payload = RecordPayload {
message: visitor.message,
level,
};
let _ = self.app_handle.emit("tracing://log", payload);
}
}
#[derive(Default)]
struct MessageVisitor {
message: String,
}
impl tracing::field::Visit for MessageVisitor {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" || self.message.is_empty() {
self.message = format!("{:?}", value);
}
}
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
if field.name() == "message" || self.message.is_empty() {
self.message = value.to_string();
}
}
}
#[derive(Debug, Clone, Deserialize_repr, Serialize_repr, Default)]
#[repr(u16)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub enum LogLevel {
Trace = 1,
Debug,
#[default]
Info,
Warn,
Error,
}
impl From<LogLevel> for tracing::Level {
fn from(log_level: LogLevel) -> Self {
match log_level {
LogLevel::Trace => tracing::Level::TRACE,
LogLevel::Debug => tracing::Level::DEBUG,
LogLevel::Info => tracing::Level::INFO,
LogLevel::Warn => tracing::Level::WARN,
LogLevel::Error => tracing::Level::ERROR,
}
}
}
impl From<tracing::Level> for LogLevel {
fn from(log_level: tracing::Level) -> Self {
match log_level {
tracing::Level::TRACE => LogLevel::Trace,
tracing::Level::DEBUG => LogLevel::Debug,
tracing::Level::INFO => LogLevel::Info,
tracing::Level::WARN => LogLevel::Warn,
tracing::Level::ERROR => LogLevel::Error,
}
}
}