cyfs_debug/panic/
manager.rs

1use super::panic::*;
2use crate::bug_report::*;
3use cyfs_base::*;
4use cyfs_util::*;
5
6use backtrace::Backtrace;
7use chrono::offset::Local;
8use chrono::DateTime;
9use std::panic;
10use std::path::PathBuf;
11use std::sync::Arc;
12
13// 触发了panic
14pub type FnOnPanic = dyn EventListenerAsyncRoutine<CyfsPanicInfo, ()>;
15type OnPanicEventManager = SyncEventManagerSync<CyfsPanicInfo, ()>;
16
17pub trait BugReportHandler: Send + Sync {
18    fn notify(
19        &self,
20        product_name: &str,
21        service_name: &str,
22        panic_info: &CyfsPanicInfo,
23    ) -> BuckyResult<()>;
24}
25
26struct PanicManagerImpl {
27    product_name: String,
28    service_name: String,
29
30    log_to_file: bool,
31    log_dir: PathBuf,
32
33    exit_on_panic: bool,
34
35    on_panic: OnPanicEventManager,
36
37    // reporters
38    reporter: Option<Box<dyn BugReportHandler>>,
39}
40
41impl PanicManagerImpl {
42    pub fn new(builder: PanicBuilder) -> Self {
43        let reporter = if !builder.disable_bug_report {
44            let mut reporter = BugReportManager::new(builder.bug_reporter);
45            if reporter.is_empty() {
46                reporter.load_from_config();
47            }
48
49            Some(Box::new(reporter) as Box<dyn BugReportHandler>)
50        } else {
51            None
52        };
53
54        Self {
55            product_name: builder.product_name,
56            service_name: builder.service_name,
57            log_to_file: builder.log_to_file,
58            log_dir: builder.log_dir,
59            exit_on_panic: builder.exit_on_panic,
60
61            on_panic: OnPanicEventManager::new(),
62            reporter,
63        }
64    }
65}
66
67#[derive(Clone)]
68pub struct PanicManager(Arc<PanicManagerImpl>);
69
70impl PanicManager {
71    pub fn new(builder: PanicBuilder) -> Self {
72        Self(Arc::new(PanicManagerImpl::new(builder)))
73    }
74
75    pub fn start(&self) {
76        let this = self.clone();
77        panic::set_hook(Box::new(move |info| {
78            let backtrace = Backtrace::new();
79            let pinfo = CyfsPanicInfo::new(backtrace, info);
80            let this = this.clone();
81
82            let _ = std::thread::spawn(move || {
83                this.on_panic(pinfo);
84            }).join();
85        }));
86    }
87
88    pub fn event(&self) -> OnPanicEventManager {
89        self.0.on_panic.clone()
90    }
91
92    fn on_panic(&self, info: CyfsPanicInfo) {
93        if self.0.log_to_file {
94            self.log_to_file(&info);
95        }
96
97        if let Some(reporter) = &self.0.reporter {
98            info!("will report panic......");
99            let _ = reporter.notify(&self.0.product_name, &self.0.service_name, &info);
100        }
101
102        // 触发事件
103        let _ = self.0.on_panic.emit(&info);
104
105        if self.0.exit_on_panic {
106            crate::CyfsLogger::flush();
107
108            error!("process will exit on panic......");
109            std::thread::sleep(std::time::Duration::from_secs(3));
110            error!("process exit on panic......");
111            std::process::exit(-1);
112        }
113    }
114
115    fn log_to_file(&self, info: &CyfsPanicInfo) {
116        if let Err(e) = std::fs::create_dir_all(&self.0.log_dir) {
117            error!(
118                "create panic dir failed! dir={}, {}",
119                self.0.log_dir.display(),
120                e
121            );
122            return;
123        }
124
125        let file_name = format!("{}_panic_{}.log", self.0.service_name, info.hash);
126
127        let now = std::time::SystemTime::now();
128        let datetime: DateTime<Local> = now.into();
129        let now = datetime.format("%Y_%m_%d %H:%M:%S.%f");
130
131        let content = format!("{}\n{}\n{}", now, info.msg_with_symbol, info.msg);
132
133        let file_path = self.0.log_dir.join(file_name);
134        if let Err(e) = std::fs::write(&file_path, content) {
135            error!("write panic log failed! dir={}, {}", file_path.display(), e);
136        }
137    }
138}
139
140pub struct PanicBuilder {
141    product_name: String,
142    service_name: String,
143
144    log_to_file: bool,
145    log_dir: PathBuf,
146
147    disable_bug_report: bool,
148    bug_reporter: Vec<Box<dyn BugReportHandler>>,
149
150    // Whether to end the process after PANIC
151    exit_on_panic: bool,
152}
153
154impl PanicBuilder {
155    pub fn new(product_name: &str, service_name: &str) -> Self {
156        assert!(!product_name.is_empty());
157        assert!(!service_name.is_empty());
158
159        let mut root = get_cyfs_root_path();
160        root.push("log/panic");
161        root.push(product_name);
162
163        Self {
164            product_name: product_name.to_owned(),
165            service_name: service_name.to_owned(),
166            log_to_file: true,
167            log_dir: root,
168            disable_bug_report: false,
169            bug_reporter: vec![],
170            exit_on_panic: false,
171        }
172    }
173
174    // panic信息是否输出到日志文件,默认输出
175    pub fn log_to_file(mut self, enable: bool) -> Self {
176        self.log_to_file = enable;
177        self
178    }
179
180    // panic输出到的日志目录,默认是{cyfs_root}/log/panic/{product_name}/
181    pub fn log_dir(mut self, log_dir: impl Into<PathBuf>) -> Self {
182        self.log_dir = log_dir.into();
183        self
184    }
185
186    pub fn bug_report(mut self, handler: Box<dyn BugReportHandler>) -> Self {
187        self.bug_reporter.push(handler);
188        self
189    }
190
191    // use default http bug_report impl for
192    pub fn http_bug_report(mut self, url: &str) -> Self {
193        let handler = HttpBugReporter::new(url);
194        self.bug_reporter.push(Box::new(handler));
195        self
196    }
197
198    // use dingtalk bug_report
199    pub fn dingtalk_bug_report(mut self, dingtalk_url: &str) -> Self {
200        let handler = DingtalkNotifier::new(dingtalk_url);
201        self.bug_reporter.push(Box::new(handler));
202        self
203    }
204
205    pub fn disable_bug_report(mut self) -> Self {
206        self.disable_bug_report = true;
207        self
208    }
209
210    // panic后是否结束进程,默认不结束
211    pub fn exit_on_panic(mut self, exit: bool) -> Self {
212        self.exit_on_panic = exit;
213        self
214    }
215
216    pub fn build(self) -> PanicManager {
217        PanicManager::new(self)
218    }
219}