ua-client 1.2.0

Native OPC UA browser/inspector GUI built on async-opcua and egui
Documentation
use std::fmt::Write as _;

use tokio::sync::mpsc::UnboundedSender;
use tracing::field::{Field, Visit};
use tracing::{Event, Level, Subscriber};
use tracing_subscriber::layer::{Context, SubscriberExt};
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Layer};

use crate::messages::UiUpdate;
use crate::types::{LogLevel, LogLine};

pub fn init_tracing(tx: UnboundedSender<UiUpdate>) {
    let filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("info,opcua=info,ua_client=debug"));
    tracing_subscriber::registry()
        .with(filter)
        .with(ChannelLogLayer::new(tx))
        .init();
}

pub struct ChannelLogLayer {
    tx: UnboundedSender<UiUpdate>,
}

impl ChannelLogLayer {
    pub fn new(tx: UnboundedSender<UiUpdate>) -> Self {
        Self { tx }
    }
}

impl<S: Subscriber> Layer<S> for ChannelLogLayer {
    fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
        let metadata = event.metadata();
        let level = to_level(*metadata.level());
        let mut visitor = MessageVisitor::default();
        event.record(&mut visitor);
        let line = LogLine {
            level,
            target: metadata.target().to_string(),
            message: visitor.message,
        };
        let _ = self.tx.send(UiUpdate::Log(line));
    }
}

fn to_level(level: Level) -> LogLevel {
    match level {
        Level::ERROR => LogLevel::Error,
        Level::WARN => LogLevel::Warn,
        Level::INFO => LogLevel::Info,
        Level::DEBUG => LogLevel::Debug,
        Level::TRACE => LogLevel::Trace,
    }
}

#[derive(Default)]
struct MessageVisitor {
    message: String,
}

impl Visit for MessageVisitor {
    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
        if field.name() == "message" {
            let _ = write!(&mut self.message, "{value:?}");
        } else {
            if !self.message.is_empty() {
                self.message.push(' ');
            }
            let _ = write!(&mut self.message, "{}={value:?}", field.name());
        }
    }

    fn record_str(&mut self, field: &Field, value: &str) {
        if field.name() == "message" {
            self.message.push_str(value);
        } else {
            if !self.message.is_empty() {
                self.message.push(' ');
            }
            let _ = write!(&mut self.message, "{}={value}", field.name());
        }
    }
}