Files
OxideJS-New/crates/oxide_log/src/init.rs
T

152 lines
5.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 日志初始化。
//!
//! `init` 一次性初始化全局 `tracing` subscriber,输出可定向到 stderr / stdout /
//! 按日滚动的文件;[`set_level`] 与 `get_subsystem` 提供运行期级别控制。
//! 重复调用 `init` 幂等(内部 `Once`),后续调用不生效。
//! 时间戳统一毫秒精度(`MillisTime`,`epoch秒.毫秒`),供 GC 周期关联与
//! 跨进程现场拼接。
use std::io;
use std::path::PathBuf;
use std::sync::{Once, OnceLock};
use std::time::{SystemTime, UNIX_EPOCH};
use tracing_subscriber::fmt::format::Writer;
use tracing_subscriber::fmt::time::FormatTime;
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};
/// 毫秒精度时间戳:tracing 缺省 `SystemTime` 为秒精度,GC 周期关联与跨进程
/// 现场拼接需要毫秒级;输出 `epoch秒.毫秒`。
struct MillisTime;
impl FormatTime for MillisTime {
fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
write!(w, "{}.{:03}", now.as_secs(), now.subsec_millis())
}
}
/// 日志输出目标。
#[derive(Debug, Clone)]
pub enum Output {
/// 写到标准错误(默认)。
Stderr,
/// 写到标准输出。
Stdout,
/// 按日滚动写入 `dir` 目录下的 `oxide.log`。
File(PathBuf),
}
/// 日志初始化配置。
///
/// `levels` 按 [`SubsystemId`] 的下标顺序给出各子系统初始级别;
/// 默认值为输出到 stderr 且全部子系统关闭。
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<SubsystemFilter> = OnceLock::new();
static FILE_GUARD: OnceLock<tracing_appender::non_blocking::WorkerGuard> = OnceLock::new();
/// 获取全局 [`SubsystemFilter`],供日志宏在检查级别时使用。
///
/// # Panics
///
/// 若在调用 [`init`] 之前调用会 panic——日志宏必须发生在初始化之后。
pub fn get_subsystem() -> &'static SubsystemFilter {
SUBSYSTEM.get().expect("oxide_log::init() must be called before logging")
}
/// 运行期调整指定子系统的日志级别。
///
/// 直接写全局过滤器,无需重新初始化 subscriber。
pub fn set_level(id: SubsystemId, level: Level) {
get_subsystem().set_level(id, level);
}
/// 初始化全局日志 subscriber。
///
/// 按 [`LogConfig::levels`] 设置各子系统初始级别,再读取 `OXIDE_LOG` 环境变量
/// 覆盖,最后按 `output` 组装 subscriber。内部以 `Once` 保证只执行一次。
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=trace"));
subsystem::apply_env_levels();
let stderr_layer = tracing_subscriber::fmt::Layer::default()
.with_writer(io::stderr)
.with_ansi(false)
.with_timer(MillisTime)
.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)
.with_timer(MillisTime)
.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)
.with_timer(MillisTime)
.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();
}
}
});
}