11//! Shared logging utilities for FFF crates.
22//!
3- //! Provides file-based tracing initialization and a panic hook that writes
4- //! to both stderr and a fallback log file.
3+ //! Provides file-based tracing initialization and crash handlers (panic hook
4+ //! + SIGSEGV signal handler) that write diagnostics to both stderr and the
5+ //! configured log file.
56
67use std:: io;
7- use std:: path:: Path ;
8+ use std:: path:: { Path , PathBuf } ;
89use tracing_appender:: non_blocking;
910use tracing_subscriber:: fmt:: format:: FmtSpan ;
1011use tracing_subscriber:: { EnvFilter , fmt, prelude:: * } ;
1112
1213static TRACING_INITIALIZED : std:: sync:: OnceLock < tracing_appender:: non_blocking:: WorkerGuard > =
1314 std:: sync:: OnceLock :: new ( ) ;
1415
15- static PANIC_HOOK_INSTALLED : std:: sync:: OnceLock < ( ) > = std:: sync:: OnceLock :: new ( ) ;
16+ static CRASH_HANDLERS_INSTALLED : std:: sync:: OnceLock < ( ) > = std:: sync:: OnceLock :: new ( ) ;
1617
17- /// Install panic hook that writes to both stderr and a fallback file.
18- /// This is called separately from init_tracing to ensure panics are always logged.
18+ /// The log file path set by `init_tracing`. Crash handlers append to this file.
19+ static LOG_FILE_PATH : std:: sync:: OnceLock < PathBuf > = std:: sync:: OnceLock :: new ( ) ;
20+
21+ fn write_crash_report ( header : & str , body : & str ) {
22+ let msg = format ! ( "\n === CRASH {} ===\n {}\n === CRASH END {} ===\n " , header, body, header) ;
23+
24+ let _ = std:: io:: Write :: write_all ( & mut std:: io:: stderr ( ) , msg. as_bytes ( ) ) ;
25+
26+ if let Some ( path) = LOG_FILE_PATH . get ( ) {
27+ let _ = std:: fs:: OpenOptions :: new ( )
28+ . create ( true )
29+ . append ( true )
30+ . open ( path)
31+ . and_then ( |mut f| std:: io:: Write :: write_all ( & mut f, msg. as_bytes ( ) ) ) ;
32+ }
33+ }
34+
35+ extern "C" fn sigsegv_handler ( sig : libc:: c_int ) {
36+ let bt = std:: backtrace:: Backtrace :: force_capture ( ) ;
37+ write_crash_report ( "SIGSEGV" , & format ! ( "signal {}\n {}" , sig, bt) ) ;
38+
39+ unsafe {
40+ libc:: signal ( sig, libc:: SIG_DFL ) ;
41+ libc:: raise ( sig) ;
42+ }
43+ }
44+
45+ /// Install both the panic hook and the SIGSEGV signal handler.
1946pub fn install_panic_hook ( ) {
20- PANIC_HOOK_INSTALLED . get_or_init ( || {
47+ CRASH_HANDLERS_INSTALLED . get_or_init ( || {
2148 let default_panic = std:: panic:: take_hook ( ) ;
22-
2349 std:: panic:: set_hook ( Box :: new ( move |panic_info| {
24- let payload = panic_info. payload ( ) ;
25- let message = if let Some ( s) = payload. downcast_ref :: < & str > ( ) {
50+ let message = if let Some ( s) = panic_info. payload ( ) . downcast_ref :: < & str > ( ) {
2651 s. to_string ( )
27- } else if let Some ( s) = payload. downcast_ref :: < String > ( ) {
52+ } else if let Some ( s) = panic_info . payload ( ) . downcast_ref :: < String > ( ) {
2853 s. clone ( )
2954 } else {
3055 "Unknown panic payload" . to_string ( )
3156 } ;
3257
33- let location = if let Some ( location) = panic_info. location ( ) {
34- format ! (
35- "{}:{}:{}" ,
36- location. file( ) ,
37- location. line( ) ,
38- location. column( )
39- )
40- } else {
41- "unknown location" . to_string ( )
42- } ;
58+ let location = panic_info
59+ . location ( )
60+ . map ( |l| format ! ( "{}:{}:{}" , l. file( ) , l. line( ) , l. column( ) ) )
61+ . unwrap_or_else ( || "unknown location" . to_string ( ) ) ;
4362
44- // Always log to tracing (if initialized)
4563 tracing:: error!(
4664 panic. message = %message,
4765 panic. location = %location,
4866 "PANIC occurred in FFF"
4967 ) ;
5068
51- // Always print to stderr
52- eprintln ! ( "=== FFF PANIC ===" ) ;
53- eprintln ! ( "Message: {}" , message) ;
54- eprintln ! ( "Location: {}" , location) ;
55- eprintln ! ( "=================" ) ;
56-
57- // Try to write to fallback panic log file
58- if let Some ( cache_dir) = dirs:: cache_dir ( ) {
59- let panic_log = cache_dir. join ( "fff_panic.log" ) ;
60- let timestamp = std:: time:: SystemTime :: now ( )
61- . duration_since ( std:: time:: UNIX_EPOCH )
62- . map ( |d| d. as_secs ( ) )
63- . unwrap_or ( 0 ) ;
64-
65- let panic_entry = format ! (
66- "\n [{}] PANIC at {}\n Message: {}\n " ,
67- timestamp, location, message
68- ) ;
69-
70- let _ = std:: fs:: OpenOptions :: new ( )
71- . create ( true )
72- . append ( true )
73- . open ( & panic_log)
74- . and_then ( |mut f| {
75- use std:: io:: Write ;
76- f. write_all ( panic_entry. as_bytes ( ) )
77- } ) ;
78-
79- eprintln ! ( "Panic logged to: {}" , panic_log. display( ) ) ;
80- }
81-
69+ write_crash_report ( "PANIC" , & format ! ( "Message: {}\n Location: {}" , message, location) ) ;
8270 default_panic ( panic_info) ;
8371 } ) ) ;
72+
73+ unsafe {
74+ libc:: signal ( libc:: SIGSEGV , sigsegv_handler as * const ( ) as libc:: sighandler_t ) ;
75+ }
8476 } ) ;
8577}
8678
8779/// Parse a log level string into a `tracing::Level`.
88- ///
89- /// Accepts "trace", "debug", "info", "warn", "error" (case-insensitive).
90- /// Returns `tracing::Level::INFO` for unrecognised values.
9180pub fn parse_log_level ( level : Option < & str > ) -> tracing:: Level {
9281 match level. as_ref ( ) . map ( |s| s. trim ( ) . to_lowercase ( ) ) . as_deref ( ) {
9382 Some ( "trace" ) => tracing:: Level :: TRACE ,
@@ -100,29 +89,19 @@ pub fn parse_log_level(level: Option<&str>) -> tracing::Level {
10089}
10190
10291/// Initialize tracing with a single log file.
103- ///
104- /// Creates the parent directory if it doesn't exist, truncates the log file,
105- /// and sets up a non-blocking file appender with structured formatting.
106- ///
107- /// # Arguments
108- /// * `log_file_path` - Full path to the log file
109- /// * `log_level` - Log level (trace, debug, info, warn, error)
110- ///
111- /// # Returns
112- /// * `Result<String, io::Error>` - Full path to the log file on success
11392pub fn init_tracing ( log_file_path : & str , log_level : Option < & str > ) -> Result < String , io:: Error > {
114- // Install panic hook first (does nothing if already installed)
115- install_panic_hook ( ) ;
116-
11793 let log_path = Path :: new ( log_file_path) ;
11894 if let Some ( parent) = log_path. parent ( ) {
11995 std:: fs:: create_dir_all ( parent) ?;
12096 }
12197
98+ let _ = LOG_FILE_PATH . set ( log_path. to_path_buf ( ) ) ;
99+ install_panic_hook ( ) ;
100+
122101 let file_appender = std:: fs:: OpenOptions :: new ( )
123102 . create ( true )
124103 . write ( true )
125- . truncate ( true ) // creates a new file on every setup
104+ . truncate ( true )
126105 . open ( log_path) ?;
127106
128107 let level = parse_log_level ( log_level) ;
@@ -137,8 +116,6 @@ pub fn init_tracing(log_file_path: &str, log_level: Option<&str>) -> Result<Stri
137116 . with_target ( true )
138117 . with_thread_ids ( false )
139118 . with_thread_names ( false )
140- // .with_file(true)
141- // .with_line_number(true)
142119 . with_ansi ( false )
143120 . with_span_events ( FmtSpan :: NEW | FmtSpan :: CLOSE ) ,
144121 )
0 commit comments