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(
363        config: TraceConfig
364    ) -> anyhow::Result<TracingGuard> {
365        let path = config
366            .output_path
367            .unwrap_or_else(|| PathBuf::from("./logs/trace.json"));
368
369        // 确保目录存在
370        if let Some(parent) = path.parent() {
371            std::fs::create_dir_all(parent)?;
372        }
373
374        // 创建 Chrome Tracing layer(传递路径而不是文件对象)
375        let (chrome_layer, guard) = tracing_chrome::ChromeLayerBuilder::new()
376            .file(&path)
377            .include_args(true) // 包含 span 参数
378            .build();
379
380        // 同时输出到控制台(简化版)
381        let env_filter = EnvFilter::try_from_default_env()
382            .unwrap_or_else(|_| EnvFilter::new(config.max_level.as_str()));
383
384        let fmt_layer = fmt::layer()
385            .with_target(config.with_target)
386            .with_thread_ids(config.with_thread_ids)
387            .with_ansi(true)
388            .compact();
389
390        tracing_subscriber::registry()
391            .with(env_filter)
392            .with(fmt_layer)
393            .with(chrome_layer)
394            .init();
395
396        tracing::info!("🔍 开发追踪已启用(Chrome Tracing 模式)");
397        tracing::info!("📁 输出文件: {}", path.display());
398        tracing::info!("📊 日志级别: {}", config.max_level);
399        tracing::info!(
400            "🌐 查看方式: 在 Chrome 浏览器中访问 chrome://tracing 并加载文件"
401        );
402        tracing::info!("📦 包含信息: Span 时序、参数、线程 ID、进程 ID");
403        tracing::info!(
404            "⚠️  重要: 请保持返回的 guard 直到程序结束,以确保数据被正确写入"
405        );
406
407        // 返回 guard,调用者需要保持它直到程序结束
408        Ok(TracingGuard::Chrome(ChromeTracingGuard { _guard: guard }))
409    }
410
411    /// 初始化 Perfetto 追踪
412    #[cfg(feature = "dev-tracing-perfetto")]
413    fn init_perfetto_tracing(config: TraceConfig) -> anyhow::Result<()> {
414        use std::fs::File;
415
416        let path = config
417            .output_path
418            .unwrap_or_else(|| PathBuf::from("./logs/trace.perfetto"));
419
420        // 确保目录存在
421        if let Some(parent) = path.parent() {
422            std::fs::create_dir_all(parent)?;
423        }
424
425        // 创建文件用于 Perfetto 输出
426        let file = File::create(&path)?;
427        let perfetto_layer = tracing_perfetto::PerfettoLayer::new(file)
428            .with_debug_annotations(true);
429
430        // 同时输出到控制台(带时间戳)
431        let env_filter = EnvFilter::try_from_default_env()
432            .unwrap_or_else(|_| EnvFilter::new(config.max_level.as_str()));
433
434        let fmt_layer = fmt::layer()
435            .with_target(config.with_target)
436            .with_thread_ids(config.with_thread_ids)
437            .with_ansi(true)
438            .compact();
439
440        tracing_subscriber::registry()
441            .with(env_filter)
442            .with(fmt_layer)
443            .with(perfetto_layer)
444            .init();
445
446        // 记录系统信息到追踪
447        log_system_info();
448
449        tracing::info!("🔍 开发追踪已启用(Perfetto 模式)");
450        tracing::info!("📁 输出文件: {}", path.display());
451        tracing::info!("📊 使用 https://ui.perfetto.dev/ 查看追踪数据");
452        tracing::info!("📊 日志级别: {}", config.max_level);
453        tracing::info!("💡 Perfetto 包含: span 时序、线程信息、系统资源");
454
455        Ok(())
456    }
457
458    /// 记录系统信息到追踪(用于 Perfetto 分析)
459    #[cfg(feature = "dev-tracing-perfetto")]
460    fn log_system_info() {
461        use std::thread;
462
463        let process_id = std::process::id();
464        let thread_id = thread::current().id();
465        let thread_name =
466            thread::current().name().unwrap_or("main").to_string();
467
468        tracing::info!(
469            process_id = process_id,
470            thread_id = ?thread_id,
471            thread_name = %thread_name,
472            "系统信息"
473        );
474
475        // 记录 CPU 核心数
476        let cpu_count = num_cpus::get();
477        tracing::info!(cpu_count = cpu_count, "CPU 信息");
478    }
479
480    /// 快速初始化(使用默认配置)
481    pub fn init_default() -> anyhow::Result<TracingGuard> {
482        init_tracing(TraceConfig::default())
483    }
484}
485
486// 生产环境:空实现,零开销
487#[cfg(not(feature = "dev-tracing"))]
488pub mod dev_tracing {
489    use std::path::PathBuf;
490
491    /// 空 Guard(生产环境)
492    pub struct TracingGuard;
493
494    #[derive(Debug, Clone)]
495    pub enum TraceFormat {
496        Console,
497        Json,
498    }
499
500    #[derive(Debug, Clone)]
501    pub struct TraceConfig {
502        pub format: TraceFormat,
503        pub output_path: Option<PathBuf>,
504        pub max_level: (),
505        pub with_target: bool,
506        pub with_thread_ids: bool,
507        pub with_file_line: bool,
508    }
509
510    impl Default for TraceConfig {
511        fn default() -> Self {
512            Self {
513                format: TraceFormat::Console,
514                output_path: None,
515                max_level: (),
516                with_target: false,
517                with_thread_ids: false,
518                with_file_line: false,
519            }
520        }
521    }
522
523    impl TraceConfig {
524        pub fn console() -> Self {
525            Self::default()
526        }
527        pub fn json(_path: impl Into<PathBuf>) -> Self {
528            Self::default()
529        }
530        pub fn with_max_level(
531            self,
532            _level: (),
533        ) -> Self {
534            self
535        }
536    }
537
538    /// 生产环境:什么都不做
539    pub fn init_tracing(_config: TraceConfig) -> anyhow::Result<TracingGuard> {
540        Ok(TracingGuard)
541    }
542
543    pub fn init_default() -> anyhow::Result<TracingGuard> {
544        Ok(TracingGuard)
545    }
546}
547
548// ============================================================================
549// 便捷宏定义
550// ============================================================================
551
552/// 创建一个带唯一追踪 ID 的 span
553///
554/// 用于追踪特定方法调用的完整执行链路
555///
556/// # 示例
557///
558/// ```rust
559/// use moduforge_core::traced_span;
560///
561/// pub async fn my_method(&self) -> Result<()> {
562///     let _span = traced_span!("my_method");
563///     // 这个方法内的所有子调用都会继承 trace_id
564///     self.do_something().await?;
565///     Ok(())
566/// }
567/// ```
568///
569/// 然后可以通过 grep 过滤特定的 trace_id:
570///
571/// ```bash
572/// cargo dev 2>&1 | grep "trace_id=42"
573/// ```
574#[cfg(feature = "dev-tracing")]
575#[macro_export]
576macro_rules! traced_span {
577    ($name:expr) => {{
578        let trace_id = $crate::tracing_init::dev_tracing::generate_trace_id();
579        tracing::info_span!($name, trace_id = trace_id)
580    }};
581    ($name:expr, $($field:tt)*) => {{
582        let trace_id = $crate::tracing_init::dev_tracing::generate_trace_id();
583        tracing::info_span!($name, trace_id = trace_id, $($field)*)
584    }};
585}
586
587#[cfg(not(feature = "dev-tracing"))]
588#[macro_export]
589macro_rules! traced_span {
590    ($name:expr) => {{}};
591    ($name:expr, $($field:tt)*) => {{}};
592}
593
594/// 条件追踪宏 - 只在环境变量指定时才创建 span
595///
596/// 通过 `TRACE_METHODS` 环境变量控制要追踪的方法
597///
598/// # 示例
599///
600/// ```rust
601/// use moduforge_core::trace_if_enabled;
602///
603/// pub async fn dispatch(&mut self, tr: Transaction) -> Result<()> {
604///     let _span = trace_if_enabled!("dispatch", tr_id = %tr.id);
605///     // 只有在 TRACE_METHODS=dispatch 时才会追踪
606///     Ok(())
607/// }
608/// ```
609///
610/// 使用方式:
611///
612/// ```bash
613/// # 只追踪 dispatch 方法
614/// TRACE_METHODS=dispatch cargo dev
615///
616/// # 追踪多个方法
617/// TRACE_METHODS=dispatch,command,apply_inner cargo dev
618///
619/// # 追踪所有方法
620/// TRACE_METHODS=* cargo dev
621/// ```
622#[cfg(feature = "dev-tracing")]
623#[macro_export]
624macro_rules! trace_if_enabled {
625    ($method:expr) => {{
626        if $crate::tracing_init::dev_tracing::should_trace($method) {
627            Some(tracing::info_span!($method).entered())
628        } else {
629            None
630        }
631    }};
632    ($method:expr, $($field:tt)*) => {{
633        if $crate::tracing_init::dev_tracing::should_trace($method) {
634            Some(tracing::info_span!($method, $($field)*).entered())
635        } else {
636            None
637        }
638    }};
639}
640
641#[cfg(not(feature = "dev-tracing"))]
642#[macro_export]
643macro_rules! trace_if_enabled {
644    ($method:expr) => {
645        None
646    };
647    ($method:expr, $($field:tt)*) => {
648        None
649    };
650}