mf_core/
tracing_init.rs

1//! 开发环境追踪初始化模块
2//!
3//! 此模块提供统一的追踪初始化接口,仅在开发环境启用。
4//! 生产环境下所有追踪代码会被编译器完全优化掉,实现零开销。
5
6// ============================================================================
7// tokio-console 支持(实时异步任务监控)
8// ============================================================================
9
10#[cfg(feature = "dev-console")]
11pub mod tokio_console {
12    //! tokio-console 实时监控模块
13    //!
14    //! 提供实时的异步任务监控和调试功能,无需手动添加 instrument 注解。
15    //!
16    //! # 使用方法
17    //!
18    //! 1. 启用 feature:
19    //! ```bash
20    //! cargo run --features dev-console
21    //! ```
22    //!
23    //! 2. 在代码中初始化:
24    //! ```rust,ignore
25    //! #[cfg(feature = "dev-console")]
26    //! mf_core::tracing_init::tokio_console::init()?;
27    //! ```
28    //!
29    //! 3. 启动 tokio-console 客户端:
30    //! ```bash
31    //! tokio-console
32    //! ```
33    //!
34    //! # 注意事项
35    //!
36    //! - tokio-console 会监听 `127.0.0.1:6669` 端口
37    //! - 不要在生产环境启用此 feature,会有性能开销
38    //! - 与其他 tracing 初始化函数互斥,只能选择一个
39
40    /// 初始化 tokio-console 订阅者
41    ///
42    /// 这会启动一个后台服务器,监听 `127.0.0.1:6669`,
43    /// 供 tokio-console 客户端连接。
44    ///
45    /// # 返回值
46    /// * `Ok(())` - 初始化成功
47    /// * `Err(anyhow::Error)` - 初始化失败
48    ///
49    /// # 示例
50    ///
51    /// ```rust,ignore
52    /// #[cfg(feature = "dev-console")]
53    /// {
54    ///     use mf_core::tracing_init::tokio_console;
55    ///     tokio_console::init()?;
56    ///     tracing::info!("tokio-console 已启动,请运行 'tokio-console' 连接");
57    /// }
58    /// ```
59    pub fn init() -> anyhow::Result<()> {
60        console_subscriber::init();
61        tracing::info!("🔍 tokio-console 已启动");
62        tracing::info!("📡 监听地址: 127.0.0.1:6669");
63        tracing::info!("💡 运行 'tokio-console' 命令连接到监控界面");
64        tracing::info!("📚 文档: https://docs.rs/tokio-console");
65        Ok(())
66    }
67
68    /// 使用自定义配置初始化 tokio-console
69    ///
70    /// # 参数
71    /// * `server_addr` - 服务器监听地址,例如 "127.0.0.1:6669"
72    ///
73    /// # 示例
74    ///
75    /// ```rust,ignore
76    /// #[cfg(feature = "dev-console")]
77    /// {
78    ///     use mf_core::tracing_init::tokio_console;
79    ///     tokio_console::init_with_config("0.0.0.0:6669")?;
80    /// }
81    /// ```
82    pub fn init_with_config(server_addr: &str) -> anyhow::Result<()> {
83        let builder = console_subscriber::ConsoleLayer::builder()
84            .server_addr(server_addr.parse()?);
85
86        builder.init();
87
88        tracing::info!("🔍 tokio-console 已启动(自定义配置)");
89        tracing::info!("📡 监听地址: {}", server_addr);
90        tracing::info!("💡 运行 'tokio-console' 命令连接到监控界面");
91        Ok(())
92    }
93}
94
95// ============================================================================
96// 开发环境追踪(Chrome Tracing、Perfetto 等)
97// ============================================================================
98
99#[cfg(feature = "dev-tracing")]
100pub mod dev_tracing {
101    use tracing_subscriber::{
102        fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter,
103    };
104    use std::path::PathBuf;
105    use std::sync::atomic::{AtomicU64, Ordering};
106
107    /// 全局追踪 ID 计数器
108    static TRACE_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
109
110    /// 生成唯一的追踪 ID
111    ///
112    /// 用于标识特定的方法调用,便于过滤和追踪完整的调用链路
113    pub fn generate_trace_id() -> u64 {
114        TRACE_ID_COUNTER.fetch_add(1, Ordering::SeqCst)
115    }
116
117    /// 检查是否应该追踪特定的方法
118    ///
119    /// 通过环境变量 `TRACE_METHODS` 控制,多个方法用逗号分隔
120    ///
121    /// # 示例
122    ///
123    /// ```bash
124    /// TRACE_METHODS=dispatch,command cargo dev
125    /// ```
126    pub fn should_trace(method_name: &str) -> bool {
127        if let Ok(trace_methods) = std::env::var("TRACE_METHODS") {
128            if trace_methods == "*" {
129                return true; // 追踪所有方法
130            }
131            trace_methods.split(',').any(|m| m.trim() == method_name)
132        } else {
133            true // 默认追踪所有(向后兼容)
134        }
135    }
136
137    /// 检查是否应该追踪特定的 tr_id
138    ///
139    /// 通过环境变量 `TRACE_TR_ID` 控制
140    ///
141    /// # 示例
142    ///
143    /// ```bash
144    /// TRACE_TR_ID=daaca572-1234-5678-9abc-def012345678 cargo dev
145    /// ```
146    pub fn should_trace_tr_id(tr_id: &str) -> bool {
147        if let Ok(target_tr_id) = std::env::var("TRACE_TR_ID") {
148            tr_id.starts_with(&target_tr_id)
149        } else {
150            true // 默认追踪所有
151        }
152    }
153
154    /// 追踪输出格式
155    #[derive(Debug, Clone)]
156    pub enum TraceFormat {
157        /// 控制台输出(带颜色,适合开发调试)
158        Console,
159        /// JSON 格式(适合日志分析)
160        Json,
161        /// Chrome Tracing 格式(适合性能可视化,推荐)
162        #[cfg(feature = "dev-tracing-chrome")]
163        Chrome,
164        /// Perfetto 格式(适合性能可视化)
165        #[cfg(feature = "dev-tracing-perfetto")]
166        Perfetto,
167    }
168
169    /// Chrome Tracing Guard(需要保持到程序结束)
170    #[cfg(feature = "dev-tracing-chrome")]
171    pub struct ChromeTracingGuard {
172        _guard: tracing_chrome::FlushGuard,
173    }
174
175    #[cfg(feature = "dev-tracing-chrome")]
176    impl Drop for ChromeTracingGuard {
177        fn drop(&mut self) {
178            tracing::info!("🔄 正在刷新 Chrome Tracing 数据...");
179        }
180    }
181
182    /// 追踪配置
183    #[derive(Debug, Clone)]
184    pub struct TraceConfig {
185        /// 输出格式
186        pub format: TraceFormat,
187        /// 输出路径(None 表示输出到 stdout)
188        pub output_path: Option<PathBuf>,
189        /// 最大日志级别
190        pub max_level: tracing::Level,
191        /// 是否显示目标模块
192        pub with_target: bool,
193        /// 是否显示线程 ID
194        pub with_thread_ids: bool,
195        /// 是否显示文件名和行号
196        pub with_file_line: bool,
197    }
198
199    impl Default for TraceConfig {
200        fn default() -> Self {
201            Self {
202                format: TraceFormat::Console,
203                output_path: None,
204                max_level: tracing::Level::DEBUG,
205                with_target: true,
206                with_thread_ids: true,
207                with_file_line: true,
208            }
209        }
210    }
211
212    impl TraceConfig {
213        /// 创建控制台输出配置
214        pub fn console() -> Self {
215            Self { format: TraceFormat::Console, ..Default::default() }
216        }
217
218        /// 创建 JSON 输出配置
219        pub fn json(output_path: impl Into<PathBuf>) -> Self {
220            Self {
221                format: TraceFormat::Json,
222                output_path: Some(output_path.into()),
223                ..Default::default()
224            }
225        }
226
227        /// 创建 Chrome Tracing 输出配置
228        #[cfg(feature = "dev-tracing-chrome")]
229        pub fn chrome(output_path: impl Into<PathBuf>) -> Self {
230            Self {
231                format: TraceFormat::Chrome,
232                output_path: Some(output_path.into()),
233                ..Default::default()
234            }
235        }
236
237        /// 创建 Perfetto 输出配置
238        #[cfg(feature = "dev-tracing-perfetto")]
239        pub fn perfetto(output_path: impl Into<PathBuf>) -> Self {
240            Self {
241                format: TraceFormat::Perfetto,
242                output_path: Some(output_path.into()),
243                ..Default::default()
244            }
245        }
246
247        /// 设置最大日志级别
248        pub fn with_max_level(
249            mut self,
250            level: tracing::Level,
251        ) -> Self {
252            self.max_level = level;
253            self
254        }
255    }
256
257    /// 追踪初始化结果
258    pub enum TracingGuard {
259        /// 无需 guard(Console/JSON/Perfetto)
260        None,
261        /// Chrome Tracing guard(需要保持到程序结束)
262        #[cfg(feature = "dev-tracing-chrome")]
263        Chrome(ChromeTracingGuard),
264    }
265
266    /// 初始化全局追踪
267    ///
268    /// # 示例
269    ///
270    /// ```no_run
271    /// use mf_core::tracing_init::dev_tracing::{init_tracing, TraceConfig};
272    ///
273    /// // 控制台输出
274    /// let _guard = init_tracing(TraceConfig::console()).unwrap();
275    ///
276    /// // JSON 文件输出
277    /// let _guard = init_tracing(TraceConfig::json("./logs/trace.json")).unwrap();
278    ///
279    /// // Chrome Tracing(需要保持 guard 到程序结束)
280    /// let _guard = init_tracing(TraceConfig::chrome("./logs/trace.json")).unwrap();
281    /// // ... 程序运行 ...
282    /// // guard 在这里 drop,确保数据被刷新
283    /// ```
284    pub fn init_tracing(config: TraceConfig) -> anyhow::Result<TracingGuard> {
285        match config.format {
286            TraceFormat::Console => {
287                init_console_tracing(config)?;
288                Ok(TracingGuard::None)
289            }
290            TraceFormat::Json => {
291                init_json_tracing(config)?;
292                Ok(TracingGuard::None)
293            }
294            #[cfg(feature = "dev-tracing-chrome")]
295            TraceFormat::Chrome => init_chrome_tracing(config),
296            #[cfg(feature = "dev-tracing-perfetto")]
297            TraceFormat::Perfetto => {
298                init_perfetto_tracing(config)?;
299                Ok(TracingGuard::None)
300            }
301        }
302    }
303
304    /// 初始化控制台追踪
305    fn init_console_tracing(config: TraceConfig) -> anyhow::Result<()> {
306        use tracing_subscriber::fmt::time::ChronoLocal;
307
308        let env_filter = EnvFilter::try_from_default_env()
309            .unwrap_or_else(|_| EnvFilter::new(config.max_level.as_str()));
310
311        let fmt_layer = fmt::layer()
312            .with_target(config.with_target)
313            .with_thread_ids(config.with_thread_ids)
314            .with_file(config.with_file_line)
315            .with_line_number(config.with_file_line)
316            .with_ansi(true)
317            .with_timer(ChronoLocal::new("%H:%M:%S%.3f".to_string()))
318            .with_span_events(fmt::format::FmtSpan::CLOSE) // 显示 span 关闭时的耗时
319            .pretty();
320
321        tracing_subscriber::registry().with(env_filter).with(fmt_layer).init();
322
323        tracing::info!("🔍 开发追踪已启用(控制台模式)");
324        tracing::info!("📊 日志级别: {}", config.max_level);
325        tracing::info!("⏱️  显示 span 执行时间");
326        Ok(())
327    }
328
329    /// 初始化 JSON 追踪
330    fn init_json_tracing(config: TraceConfig) -> anyhow::Result<()> {
331        let path = config
332            .output_path
333            .unwrap_or_else(|| PathBuf::from("./logs/trace.json"));
334
335        // 确保目录存在
336        if let Some(parent) = path.parent() {
337            std::fs::create_dir_all(parent)?;
338        }
339
340        let file = std::fs::File::create(&path)?;
341
342        let env_filter = EnvFilter::new(config.max_level.as_str());
343
344        let fmt_layer = fmt::layer()
345            .json()
346            .with_writer(file)
347            .with_target(config.with_target)
348            .with_thread_ids(config.with_thread_ids)
349            .with_file(config.with_file_line)
350            .with_line_number(config.with_file_line);
351
352        tracing_subscriber::registry().with(env_filter).with(fmt_layer).init();
353
354        tracing::info!("🔍 开发追踪已启用(JSON 模式)");
355        tracing::info!("📁 输出文件: {}", path.display());
356        tracing::info!("📊 日志级别: {}", config.max_level);
357        Ok(())
358    }
359
360    /// 初始化 Chrome Tracing 追踪
361    #[cfg(feature = "dev-tracing-chrome")]
362    fn init_chrome_tracing(config: TraceConfig) -> anyhow::Result<TracingGuard> {
363        let path = config
364            .output_path
365            .unwrap_or_else(|| PathBuf::from("./logs/trace.json"));
366
367        // 确保目录存在
368        if let Some(parent) = path.parent() {
369            std::fs::create_dir_all(parent)?;
370        }
371
372        // 创建 Chrome Tracing layer(传递路径而不是文件对象)
373        let (chrome_layer, guard) = tracing_chrome::ChromeLayerBuilder::new()
374            .file(&path)
375            .include_args(true)  // 包含 span 参数
376            .build();
377
378        // 同时输出到控制台(简化版)
379        let env_filter = EnvFilter::try_from_default_env()
380            .unwrap_or_else(|_| EnvFilter::new(config.max_level.as_str()));
381
382        let fmt_layer = fmt::layer()
383            .with_target(config.with_target)
384            .with_thread_ids(config.with_thread_ids)
385            .with_ansi(true)
386            .compact();
387
388        tracing_subscriber::registry()
389            .with(env_filter)
390            .with(fmt_layer)
391            .with(chrome_layer)
392            .init();
393
394        tracing::info!("🔍 开发追踪已启用(Chrome Tracing 模式)");
395        tracing::info!("📁 输出文件: {}", path.display());
396        tracing::info!("📊 日志级别: {}", config.max_level);
397        tracing::info!("🌐 查看方式: 在 Chrome 浏览器中访问 chrome://tracing 并加载文件");
398        tracing::info!("📦 包含信息: Span 时序、参数、线程 ID、进程 ID");
399        tracing::info!("⚠️  重要: 请保持返回的 guard 直到程序结束,以确保数据被正确写入");
400
401        // 返回 guard,调用者需要保持它直到程序结束
402        Ok(TracingGuard::Chrome(ChromeTracingGuard { _guard: guard }))
403    }
404
405    /// 初始化 Perfetto 追踪
406    #[cfg(feature = "dev-tracing-perfetto")]
407    fn init_perfetto_tracing(config: TraceConfig) -> anyhow::Result<()> {
408        use std::fs::File;
409
410        let path = config
411            .output_path
412            .unwrap_or_else(|| PathBuf::from("./logs/trace.perfetto"));
413
414        // 确保目录存在
415        if let Some(parent) = path.parent() {
416            std::fs::create_dir_all(parent)?;
417        }
418
419        // 创建文件用于 Perfetto 输出
420        let file = File::create(&path)?;
421        let perfetto_layer = tracing_perfetto::PerfettoLayer::new(file)
422            .with_debug_annotations(true);
423
424        // 同时输出到控制台(带时间戳)
425        let env_filter = EnvFilter::try_from_default_env()
426            .unwrap_or_else(|_| EnvFilter::new(config.max_level.as_str()));
427
428        let fmt_layer = fmt::layer()
429            .with_target(config.with_target)
430            .with_thread_ids(config.with_thread_ids)
431            .with_ansi(true)
432            .compact();
433
434        tracing_subscriber::registry()
435            .with(env_filter)
436            .with(fmt_layer)
437            .with(perfetto_layer)
438            .init();
439
440        // 记录系统信息到追踪
441        log_system_info();
442
443        tracing::info!("🔍 开发追踪已启用(Perfetto 模式)");
444        tracing::info!("📁 输出文件: {}", path.display());
445        tracing::info!("📊 使用 https://ui.perfetto.dev/ 查看追踪数据");
446        tracing::info!("📊 日志级别: {}", config.max_level);
447        tracing::info!("💡 Perfetto 包含: span 时序、线程信息、系统资源");
448
449        Ok(())
450    }
451
452    /// 记录系统信息到追踪(用于 Perfetto 分析)
453    #[cfg(feature = "dev-tracing-perfetto")]
454    fn log_system_info() {
455        use std::thread;
456
457        let process_id = std::process::id();
458        let thread_id = thread::current().id();
459        let thread_name =
460            thread::current().name().unwrap_or("main").to_string();
461
462        tracing::info!(
463            process_id = process_id,
464            thread_id = ?thread_id,
465            thread_name = %thread_name,
466            "系统信息"
467        );
468
469        // 记录 CPU 核心数
470        let cpu_count = num_cpus::get();
471        tracing::info!(cpu_count = cpu_count, "CPU 信息");
472    }
473
474    /// 快速初始化(使用默认配置)
475    pub fn init_default() -> anyhow::Result<TracingGuard> {
476        init_tracing(TraceConfig::default())
477    }
478}
479
480// 生产环境:空实现,零开销
481#[cfg(not(feature = "dev-tracing"))]
482pub mod dev_tracing {
483    use std::path::PathBuf;
484
485    /// 空 Guard(生产环境)
486    pub struct TracingGuard;
487
488    #[derive(Debug, Clone)]
489    pub enum TraceFormat {
490        Console,
491        Json,
492    }
493
494    #[derive(Debug, Clone)]
495    pub struct TraceConfig {
496        pub format: TraceFormat,
497        pub output_path: Option<PathBuf>,
498        pub max_level: (),
499        pub with_target: bool,
500        pub with_thread_ids: bool,
501        pub with_file_line: bool,
502    }
503
504    impl Default for TraceConfig {
505        fn default() -> Self {
506            Self {
507                format: TraceFormat::Console,
508                output_path: None,
509                max_level: (),
510                with_target: false,
511                with_thread_ids: false,
512                with_file_line: false,
513            }
514        }
515    }
516
517    impl TraceConfig {
518        pub fn console() -> Self {
519            Self::default()
520        }
521        pub fn json(_path: impl Into<PathBuf>) -> Self {
522            Self::default()
523        }
524        pub fn with_max_level(
525            self,
526            _level: (),
527        ) -> Self {
528            self
529        }
530    }
531
532    /// 生产环境:什么都不做
533    pub fn init_tracing(_config: TraceConfig) -> anyhow::Result<TracingGuard> {
534        Ok(TracingGuard)
535    }
536
537    pub fn init_default() -> anyhow::Result<TracingGuard> {
538        Ok(TracingGuard)
539    }
540}
541
542
543// ============================================================================
544// 便捷宏定义
545// ============================================================================
546
547/// 创建一个带唯一追踪 ID 的 span
548///
549/// 用于追踪特定方法调用的完整执行链路
550///
551/// # 示例
552///
553/// ```rust
554/// use moduforge_core::traced_span;
555///
556/// pub async fn my_method(&self) -> Result<()> {
557///     let _span = traced_span!("my_method");
558///     // 这个方法内的所有子调用都会继承 trace_id
559///     self.do_something().await?;
560///     Ok(())
561/// }
562/// ```
563///
564/// 然后可以通过 grep 过滤特定的 trace_id:
565///
566/// ```bash
567/// cargo dev 2>&1 | grep "trace_id=42"
568/// ```
569#[cfg(feature = "dev-tracing")]
570#[macro_export]
571macro_rules! traced_span {
572    ($name:expr) => {{
573        let trace_id = $crate::tracing_init::dev_tracing::generate_trace_id();
574        tracing::info_span!($name, trace_id = trace_id)
575    }};
576    ($name:expr, $($field:tt)*) => {{
577        let trace_id = $crate::tracing_init::dev_tracing::generate_trace_id();
578        tracing::info_span!($name, trace_id = trace_id, $($field)*)
579    }};
580}
581
582#[cfg(not(feature = "dev-tracing"))]
583#[macro_export]
584macro_rules! traced_span {
585    ($name:expr) => {{}};
586    ($name:expr, $($field:tt)*) => {{}};
587}
588
589/// 条件追踪宏 - 只在环境变量指定时才创建 span
590///
591/// 通过 `TRACE_METHODS` 环境变量控制要追踪的方法
592///
593/// # 示例
594///
595/// ```rust
596/// use moduforge_core::trace_if_enabled;
597///
598/// pub async fn dispatch(&mut self, tr: Transaction) -> Result<()> {
599///     let _span = trace_if_enabled!("dispatch", tr_id = %tr.id);
600///     // 只有在 TRACE_METHODS=dispatch 时才会追踪
601///     Ok(())
602/// }
603/// ```
604///
605/// 使用方式:
606///
607/// ```bash
608/// # 只追踪 dispatch 方法
609/// TRACE_METHODS=dispatch cargo dev
610///
611/// # 追踪多个方法
612/// TRACE_METHODS=dispatch,command,apply_inner cargo dev
613///
614/// # 追踪所有方法
615/// TRACE_METHODS=* cargo dev
616/// ```
617#[cfg(feature = "dev-tracing")]
618#[macro_export]
619macro_rules! trace_if_enabled {
620    ($method:expr) => {{
621        if $crate::tracing_init::dev_tracing::should_trace($method) {
622            Some(tracing::info_span!($method).entered())
623        } else {
624            None
625        }
626    }};
627    ($method:expr, $($field:tt)*) => {{
628        if $crate::tracing_init::dev_tracing::should_trace($method) {
629            Some(tracing::info_span!($method, $($field)*).entered())
630        } else {
631            None
632        }
633    }};
634}
635
636#[cfg(not(feature = "dev-tracing"))]
637#[macro_export]
638macro_rules! trace_if_enabled {
639    ($method:expr) => { None };
640    ($method:expr, $($field:tt)*) => { None };
641}