Skip to main content

zerolaunch_plugin_sdk_rust/
logging.rs

1//! 插件日志系统。
2//!
3//! 提供双写能力:
4//! - 主干:`tracing` 事件 → stderr → 宿主收集到 `<log-dir>/<plugin-id>.log`(所有级别,崩溃安全)
5//! - 旁路:WARN/ERROR 事件 → 非阻塞通道 → 后台任务 → `host/log` RPC(尽力而为转发到宿主)
6//!
7//! 插件开发者只需使用标准 `tracing::info!()` / `tracing::warn!()` / `tracing::error!()` 宏,
8//! 无需调用 `host().log(...).await`。
9
10use std::io;
11use tokio::sync::mpsc;
12
13/// 一条待转发到宿主的日志条目。
14pub struct LogEntry {
15    pub level: String,
16    pub message: String,
17}
18
19/// 将 `tracing` 格式化输出写入 log 通道的 writer。
20///
21/// 每个实例绑定一个固定的日志级别("warn" 或 "error"),
22/// 通过不同的 `tracing_subscriber::fmt::Layer` 实例分级别过滤。
23#[derive(Clone)]
24struct HostLogWriter {
25    tx: mpsc::UnboundedSender<LogEntry>,
26    level: &'static str,
27}
28
29impl HostLogWriter {
30    fn new(tx: mpsc::UnboundedSender<LogEntry>, level: &'static str) -> Self {
31        Self { tx, level }
32    }
33}
34
35impl io::Write for HostLogWriter {
36    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
37        let message = String::from_utf8_lossy(buf).trim_end().to_string();
38        if !message.is_empty() {
39            // 通道满了就丢弃,不阻塞 tracing 调用者
40            let _ = self.tx.send(LogEntry {
41                level: self.level.to_string(),
42                message,
43            });
44        }
45        Ok(buf.len())
46    }
47
48    fn flush(&mut self) -> io::Result<()> {
49        Ok(())
50    }
51}
52
53impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for HostLogWriter {
54    type Writer = HostLogWriter;
55
56    fn make_writer(&'a self) -> Self::Writer {
57        self.clone()
58    }
59}
60
61/// 初始化插件日志系统。
62///
63/// # 返回值
64///
65/// 返回 `mpsc::UnboundedReceiver<LogEntry>`,调用方需要 spawn 一个后台任务消费该通道,
66/// 将 WARN/ERROR 日志转发到宿主。
67///
68/// # Panics
69///
70/// 如果全局 subscriber 已被设置(例如在测试中),`try_init()` 会静默忽略。
71pub fn init_logging() -> mpsc::UnboundedReceiver<LogEntry> {
72    use tracing_subscriber::filter::{filter_fn, LevelFilter};
73    use tracing_subscriber::prelude::*;
74    use tracing_subscriber::Registry;
75
76    let (log_tx, log_rx) = mpsc::unbounded_channel::<LogEntry>();
77
78    // 如果已设置(如嵌套测试),不重复初始化
79    let _ = Registry::default()
80        .with(
81            tracing_subscriber::fmt::layer()
82                .with_writer(io::stderr)
83                .with_filter(LevelFilter::INFO),
84        )
85        .with(
86            tracing_subscriber::fmt::layer()
87                .with_writer(HostLogWriter::new(log_tx.clone(), "warn"))
88                .with_filter(filter_fn(|metadata| {
89                    *metadata.level() == tracing::Level::WARN
90                })),
91        )
92        .with(
93            tracing_subscriber::fmt::layer()
94                .with_writer(HostLogWriter::new(log_tx.clone(), "error"))
95                .with_filter(filter_fn(|metadata| {
96                    *metadata.level() == tracing::Level::ERROR
97                })),
98        )
99        .try_init();
100
101    log_rx
102}