Skip to main content

wash_runtime/plugin/
wasi_logging.rs

1//! Structured logging plugin for WebAssembly components.
2//!
3//! This plugin implements the `wasi:logging/logging@0.1.0-draft` interface,
4//! providing components with structured logging capabilities. It bridges
5//! component log messages to the host's tracing infrastructure.
6//!
7//! # Features
8//!
9//! - Structured logging with multiple log levels
10//! - Integration with Rust's `tracing` ecosystem
11//! - Contextual logging with component identification
12//! - Efficient log message routing
13//!
14//! # Log Levels
15//!
16//! The plugin supports the standard WASI logging levels:
17//! - Trace: Detailed diagnostic information
18//! - Debug: Debug-level messages
19//! - Info: General informational messages
20//! - Warn: Warning messages
21//! - Error: Error messages
22//!
23//! # Usage
24//!
25//! Components can use the WASI logging interface to emit structured log
26//! messages that will be processed by the host's logging infrastructure.
27
28use std::collections::HashSet;
29
30use anyhow::bail;
31
32const WASI_LOGGING_ID: &str = "wasi-logging";
33
34use crate::{
35    engine::{ctx::Ctx, workload::WorkloadComponent},
36    plugin::{HostPlugin, wasi_logging::bindings::wasi::logging::logging::Level},
37    wit::{WitInterface, WitWorld},
38};
39
40mod bindings {
41    wasmtime::component::bindgen!({
42        world: "logging",
43        trappable_imports: true,
44        async: true,
45    });
46}
47
48/// WASI logging plugin that provides structured logging capabilities.
49///
50/// This plugin bridges component log messages to the host's tracing infrastructure,
51/// allowing WebAssembly components to emit structured log messages that are
52/// processed and routed by the host's logging system.
53pub struct WasiLogging;
54
55impl bindings::wasi::logging::logging::Host for Ctx {
56    async fn log(&mut self, level: Level, context: String, message: String) -> anyhow::Result<()> {
57        match level {
58            Level::Critical => tracing::error!(id = &self.id, context, "{message}"),
59            Level::Error => tracing::error!(id = &self.id, context, "{message}"),
60            Level::Warn => tracing::warn!(id = &self.id, context, "{message}"),
61            Level::Info => tracing::info!(id = &self.id, context, "{message}"),
62            Level::Debug => tracing::debug!(id = &self.id, context, "{message}"),
63            Level::Trace => tracing::trace!(id = &self.id, context, "{message}"),
64        }
65        Ok(())
66    }
67}
68
69#[async_trait::async_trait]
70impl HostPlugin for WasiLogging {
71    fn id(&self) -> &'static str {
72        WASI_LOGGING_ID
73    }
74
75    fn world(&self) -> WitWorld {
76        WitWorld {
77            imports: HashSet::from([WitInterface::from("wasi:logging/logging@0.1.0-draft")]),
78            ..Default::default()
79        }
80    }
81
82    async fn on_component_bind(
83        &self,
84        workload_handle: &mut WorkloadComponent,
85        interfaces: std::collections::HashSet<crate::wit::WitInterface>,
86    ) -> anyhow::Result<()> {
87        // Ensure exactly one interface: "wasi:logging/logging"
88        let mut iter = interfaces.iter();
89        let Some(interface) = iter.next() else {
90            bail!("No interfaces provided; expected wasi:logging/logging");
91        };
92        if iter.next().is_some()
93            || interface.namespace != "wasi"
94            || interface.package != "logging"
95            || !interface.interfaces.contains("logging")
96        {
97            bail!(
98                "Expected exactly one interface: wasi:logging/logging, got: {:?}",
99                interfaces
100            );
101        }
102
103        // Add `wasi:logging/logging` to the workload's linker
104        bindings::wasi::logging::logging::add_to_linker(workload_handle.linker(), |ctx| ctx)?;
105
106        Ok(())
107    }
108}