From 1dae26064601d465bf015b163782acb3324a8131 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 26 Jun 2026 18:43:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=81=E8=A3=85=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E8=AE=BE=E6=96=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 85 +++++++++++++++++++++++ Cargo.toml | 6 +- crates/oxide_log/Cargo.toml | 12 ++++ crates/oxide_log/src/filter.rs | 99 +++++++++++++++++++++++++++ crates/oxide_log/src/init.rs | 109 ++++++++++++++++++++++++++++++ crates/oxide_log/src/level.rs | 22 ++++++ crates/oxide_log/src/lib.rs | 10 +++ crates/oxide_log/src/macros.rs | 6 ++ crates/oxide_log/src/subsystem.rs | 57 ++++++++++++++++ 9 files changed, 404 insertions(+), 2 deletions(-) create mode 100644 crates/oxide_log/Cargo.toml create mode 100644 crates/oxide_log/src/filter.rs create mode 100644 crates/oxide_log/src/init.rs create mode 100644 crates/oxide_log/src/level.rs create mode 100644 crates/oxide_log/src/lib.rs create mode 100644 crates/oxide_log/src/macros.rs create mode 100644 crates/oxide_log/src/subsystem.rs diff --git a/Cargo.lock b/Cargo.lock index cdcae941..d8bc9e1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,6 +334,15 @@ dependencies = [ "itertools", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -379,6 +388,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "dragonbox_ecma" version = "0.1.12" @@ -772,6 +787,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-integer" version = "0.1.46" @@ -1106,6 +1127,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "oxide_log" +version = "0.1.0" +dependencies = [ + "tracing", + "tracing-appender", + "tracing-subscriber", +] + [[package]] name = "oxide_parser" version = "0.1.0" @@ -1274,6 +1304,12 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1653,6 +1689,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "2.0.117" @@ -1717,6 +1759,36 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -1753,6 +1825,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" diff --git a/Cargo.toml b/Cargo.toml index 1d5af2c4..350b97b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/oxide_parser", "crates/oxide_bytecode", "crates/oxide_code_cache", + "crates/oxide_log", "crates/oxide_compiler", "crates/oxide_types", "crates/oxide_vm", @@ -48,8 +49,9 @@ lru = "0.18" smallvec = { version = "1", features = ["union"] } chrono = { version = "0.4", default-features = false, features = ["clock"] } unicode-normalization = "0.1" -tracing = "0.1.44" -tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } +tracing = { version = "0.1.44", features = ["max_level_trace", "release_max_level_info"] } +tracing-subscriber = { version = "0.3.23", features = ["env-filter", "registry"] } +tracing-appender = "0.2.5" [workspace.lints.clippy] all = { level = "deny", priority = -1 } diff --git a/crates/oxide_log/Cargo.toml b/crates/oxide_log/Cargo.toml new file mode 100644 index 00000000..0f0194b3 --- /dev/null +++ b/crates/oxide_log/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "oxide_log" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "OxideJS - Structured logging subsystem (tracing-based)" + +[dependencies] +tracing = { workspace = true, features = ["max_level_trace", "release_max_level_info"] } +tracing-subscriber = { workspace = true, features = ["env-filter", "registry"] } +tracing-appender = "0.2.5" diff --git a/crates/oxide_log/src/filter.rs b/crates/oxide_log/src/filter.rs new file mode 100644 index 00000000..98e1660e --- /dev/null +++ b/crates/oxide_log/src/filter.rs @@ -0,0 +1,99 @@ +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::Arc; + +use tracing::Subscriber; +use tracing_subscriber::layer::Filter; + +use crate::level::Level; +use crate::subsystem::{SubsystemId, SUBSYSTEM_COUNT}; + +#[derive(Clone)] +pub struct SubsystemFilter { + levels: Arc<[AtomicU8; SUBSYSTEM_COUNT]>, +} + +impl SubsystemFilter { + pub fn new() -> Self { + Self { + levels: Arc::new([ + AtomicU8::new(0), + AtomicU8::new(0), + AtomicU8::new(0), + AtomicU8::new(0), + ]), + } + } + + pub fn set_level(&self, id: SubsystemId, level: Level) { + self.levels[id as usize].store(level as u8, Ordering::Relaxed); + } + + pub fn get_level(&self, id: SubsystemId) -> Level { + let raw = self.levels[id as usize].load(Ordering::Relaxed); + match raw { + 1 => Level::Error, + 2 => Level::Warn, + 3 => Level::Info, + 4 => Level::Debug, + 5 => Level::Trace, + _ => Level::Off, + } + } +} + +impl SubsystemFilter { + fn is_enabled_for(&self, target: &str, tracing_level: &tracing::Level) -> bool { + let sid = subsystem_for_target(target); + let max = self.get_level(sid); + match max { + Level::Off => false, + Level::Error => *tracing_level <= tracing::Level::ERROR, + Level::Warn => *tracing_level <= tracing::Level::WARN, + Level::Info => *tracing_level <= tracing::Level::INFO, + Level::Debug => *tracing_level <= tracing::Level::DEBUG, + Level::Trace => true, + } + } +} + +impl Default for SubsystemFilter { + fn default() -> Self { + Self::new() + } +} + +impl Filter for SubsystemFilter { + fn enabled( + &self, + meta: &tracing::Metadata<'_>, + _cx: &tracing_subscriber::layer::Context<'_, S>, + ) -> bool { + self.is_enabled_for(meta.target(), meta.level()) + } +} + +fn subsystem_for_target(target: &str) -> SubsystemId { + if let Some(rest) = target.strip_prefix("oxide::") { + match rest { + "vm" => SubsystemId::Vm, + "ic" => SubsystemId::Ic, + "kernel" => SubsystemId::Kernel, + "builtins" => SubsystemId::Builtins, + _ => { + if rest.starts_with("vm::") { + SubsystemId::Vm + } else if rest.starts_with("ic::") { + SubsystemId::Ic + } else if rest.starts_with("kernel::") { + SubsystemId::Kernel + } else if rest.starts_with("builtins::") { + SubsystemId::Builtins + } else { + SubsystemId::Kernel + } + } + } + } else { + SubsystemId::Kernel + } +} diff --git a/crates/oxide_log/src/init.rs b/crates/oxide_log/src/init.rs new file mode 100644 index 00000000..dd125f0c --- /dev/null +++ b/crates/oxide_log/src/init.rs @@ -0,0 +1,109 @@ +use std::io; +use std::path::PathBuf; +use std::sync::{Once, OnceLock}; + +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::EnvFilter; +use tracing_subscriber::Layer; + +use crate::filter::SubsystemFilter; +use crate::level::Level; +use crate::subsystem::{self, SubsystemId, SUBSYSTEM_COUNT}; + +#[derive(Debug, Clone)] +pub enum Output { + Stderr, + Stdout, + File(PathBuf), +} + +pub struct LogConfig { + pub output: Output, + pub levels: [Level; SUBSYSTEM_COUNT], +} + +impl Default for LogConfig { + fn default() -> Self { + Self { + output: Output::Stderr, + levels: [Level::Off; SUBSYSTEM_COUNT], + } + } +} + +static INIT: Once = Once::new(); +static SUBSYSTEM: OnceLock = OnceLock::new(); +static FILE_GUARD: OnceLock = OnceLock::new(); + +pub fn get_subsystem() -> &'static SubsystemFilter { + SUBSYSTEM.get().expect("oxide_log::init() must be called before logging") +} + +pub fn set_level(id: SubsystemId, level: Level) { + get_subsystem().set_level(id, level); +} + +pub fn init(config: &LogConfig) { + INIT.call_once(|| { + let filter = SubsystemFilter::new(); + for (i, level) in config.levels.iter().enumerate() { + let id = match i { + 0 => SubsystemId::Vm, + 1 => SubsystemId::Ic, + 2 => SubsystemId::Kernel, + 3 => SubsystemId::Builtins, + _ => continue, + }; + filter.set_level(id, *level); + } + SUBSYSTEM.set(filter.clone()).ok(); + + let env_filter = EnvFilter::try_from_env("OXIDE_LOG").unwrap_or_else(|_| EnvFilter::new("oxide=off")); + + subsystem::apply_env_levels(); + + let stderr_layer = tracing_subscriber::fmt::Layer::default() + .with_writer(io::stderr) + .with_ansi(false) + .without_time() + .compact(); + + match &config.output { + Output::Stderr => { + let subscriber = tracing_subscriber::Registry::default() + .with(env_filter) + .with(stderr_layer.with_filter(filter)); + subscriber.init(); + } + Output::Stdout => { + let stdout_layer = tracing_subscriber::fmt::Layer::default() + .with_writer(io::stdout) + .with_ansi(false) + .without_time() + .compact(); + let subscriber = tracing_subscriber::Registry::default() + .with(env_filter) + .with(stdout_layer.with_filter(filter)); + subscriber.init(); + } + Output::File(dir) => { + let file_appender = tracing_appender::rolling::daily(dir, "oxide.log"); + let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); + FILE_GUARD.set(guard).ok(); + + let file_layer = tracing_subscriber::fmt::Layer::default() + .with_writer(non_blocking) + .with_ansi(false) + .without_time() + .compact(); + + let subscriber = tracing_subscriber::Registry::default() + .with(env_filter) + .with(stderr_layer.with_filter(filter.clone())) + .with(file_layer.with_filter(filter)); + subscriber.init(); + } + } + }); +} diff --git a/crates/oxide_log/src/level.rs b/crates/oxide_log/src/level.rs new file mode 100644 index 00000000..09e2bc18 --- /dev/null +++ b/crates/oxide_log/src/level.rs @@ -0,0 +1,22 @@ +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Level { + Off = 0, + Error = 1, + Warn = 2, + Info = 3, + Debug = 4, + Trace = 5, +} + +impl Level { + pub fn as_tracing_level(self) -> tracing::Level { + match self { + Level::Off | Level::Error => tracing::Level::ERROR, + Level::Warn => tracing::Level::WARN, + Level::Info => tracing::Level::INFO, + Level::Debug => tracing::Level::DEBUG, + Level::Trace => tracing::Level::TRACE, + } + } +} diff --git a/crates/oxide_log/src/lib.rs b/crates/oxide_log/src/lib.rs new file mode 100644 index 00000000..fd09ff52 --- /dev/null +++ b/crates/oxide_log/src/lib.rs @@ -0,0 +1,10 @@ +pub mod filter; +pub mod init; +pub mod level; +pub mod macros; +pub mod subsystem; + +pub use filter::SubsystemFilter; +pub use init::{init, set_level, LogConfig, Output}; +pub use level::Level; +pub use subsystem::{SubsystemId, SUBSYSTEM_COUNT}; diff --git a/crates/oxide_log/src/macros.rs b/crates/oxide_log/src/macros.rs new file mode 100644 index 00000000..197b5cef --- /dev/null +++ b/crates/oxide_log/src/macros.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! __log_event { + ($target:expr, $level:expr, $($arg:tt)*) => { + tracing::event!(target: $target, $level, $($arg)*) + }; +} diff --git a/crates/oxide_log/src/subsystem.rs b/crates/oxide_log/src/subsystem.rs new file mode 100644 index 00000000..a15f69b4 --- /dev/null +++ b/crates/oxide_log/src/subsystem.rs @@ -0,0 +1,57 @@ +use crate::level::Level; + +#[repr(usize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SubsystemId { + Vm = 0, + Ic = 1, + Kernel = 2, + Builtins = 3, +} + +pub const SUBSYSTEM_COUNT: usize = 4; + +pub fn parse_level(s: &str) -> Option { + if s.eq_ignore_ascii_case("off") { + Some(Level::Off) + } else if s.eq_ignore_ascii_case("error") { + Some(Level::Error) + } else if s.eq_ignore_ascii_case("warn") { + Some(Level::Warn) + } else if s.eq_ignore_ascii_case("info") { + Some(Level::Info) + } else if s.eq_ignore_ascii_case("debug") { + Some(Level::Debug) + } else if s.eq_ignore_ascii_case("trace") { + Some(Level::Trace) + } else { + None + } +} + +pub fn apply_env_levels() { + let Ok(spec) = std::env::var("OXIDE_LOG") else { + return; + }; + for directive in spec.split(',') { + let Some((target, level_str)) = directive.split_once('=') else { + continue; + }; + let Some(level) = parse_level(level_str.trim()) else { + continue; + }; + match target.trim() { + "oxide" => { + crate::set_level(SubsystemId::Vm, level); + crate::set_level(SubsystemId::Ic, level); + crate::set_level(SubsystemId::Kernel, level); + crate::set_level(SubsystemId::Builtins, level); + } + "oxide::vm" => crate::set_level(SubsystemId::Vm, level), + "oxide::ic" => crate::set_level(SubsystemId::Ic, level), + "oxide::kernel" => crate::set_level(SubsystemId::Kernel, level), + "oxide::builtins" => crate::set_level(SubsystemId::Builtins, level), + _ => {} + } + } +}