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 ! (
23+ "\n === CRASH {} ===\n {}\n === CRASH END {} ===\n " ,
24+ header, body, header
25+ ) ;
26+
27+ let _ = std:: io:: Write :: write_all ( & mut std:: io:: stderr ( ) , msg. as_bytes ( ) ) ;
28+
29+ if let Some ( path) = LOG_FILE_PATH . get ( ) {
30+ let _ = std:: fs:: OpenOptions :: new ( )
31+ . create ( true )
32+ . append ( true )
33+ . open ( path)
34+ . and_then ( |mut f| std:: io:: Write :: write_all ( & mut f, msg. as_bytes ( ) ) ) ;
35+ }
36+ }
37+
38+ extern "C" fn sigsegv_handler ( sig : libc:: c_int ) {
39+ let bt = std:: backtrace:: Backtrace :: force_capture ( ) ;
40+ write_crash_report ( "SIGSEGV" , & format ! ( "signal {}\n {}" , sig, bt) ) ;
41+
42+ unsafe {
43+ libc:: signal ( sig, libc:: SIG_DFL ) ;
44+ libc:: raise ( sig) ;
45+ }
46+ }
47+
48+ /// Install both the panic hook and the SIGSEGV signal handler.
1949pub fn install_panic_hook ( ) {
20- PANIC_HOOK_INSTALLED . get_or_init ( || {
50+ CRASH_HANDLERS_INSTALLED . get_or_init ( || {
2151 let default_panic = std:: panic:: take_hook ( ) ;
22-
2352 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 > ( ) {
53+ let message = if let Some ( s) = panic_info. payload ( ) . downcast_ref :: < & str > ( ) {
2654 s. to_string ( )
27- } else if let Some ( s) = payload. downcast_ref :: < String > ( ) {
55+ } else if let Some ( s) = panic_info . payload ( ) . downcast_ref :: < String > ( ) {
2856 s. clone ( )
2957 } else {
3058 "Unknown panic payload" . to_string ( )
3159 } ;
3260
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- } ;
61+ let location = panic_info
62+ . location ( )
63+ . map ( |l| format ! ( "{}:{}:{}" , l. file( ) , l. line( ) , l. column( ) ) )
64+ . unwrap_or_else ( || "unknown location" . to_string ( ) ) ;
4365
44- // Always log to tracing (if initialized)
4566 tracing:: error!(
4667 panic. message = %message,
4768 panic. location = %location,
4869 "PANIC occurred in FFF"
4970 ) ;
5071
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-
72+ write_crash_report (
73+ "RUST PANIC" ,
74+ & format ! ( "Message: {}\n Location: {}" , message, location) ,
75+ ) ;
8276 default_panic ( panic_info) ;
8377 } ) ) ;
78+
79+ unsafe {
80+ libc:: signal (
81+ libc:: SIGSEGV ,
82+ sigsegv_handler as * const ( ) as libc:: sighandler_t ,
83+ ) ;
84+ }
8485 } ) ;
8586}
8687
8788/// 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.
9189pub fn parse_log_level ( level : Option < & str > ) -> tracing:: Level {
9290 match level. as_ref ( ) . map ( |s| s. trim ( ) . to_lowercase ( ) ) . as_deref ( ) {
9391 Some ( "trace" ) => tracing:: Level :: TRACE ,
@@ -100,29 +98,19 @@ pub fn parse_log_level(level: Option<&str>) -> tracing::Level {
10098}
10199
102100/// 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
113101pub 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-
117102 let log_path = Path :: new ( log_file_path) ;
118103 if let Some ( parent) = log_path. parent ( ) {
119104 std:: fs:: create_dir_all ( parent) ?;
120105 }
121106
107+ let _ = LOG_FILE_PATH . set ( log_path. to_path_buf ( ) ) ;
108+ install_panic_hook ( ) ;
109+
122110 let file_appender = std:: fs:: OpenOptions :: new ( )
123111 . create ( true )
124112 . write ( true )
125- . truncate ( true ) // creates a new file on every setup
113+ . truncate ( true ) // truncates a file on restart (instead of appending)
126114 . open ( log_path) ?;
127115
128116 let level = parse_log_level ( log_level) ;
@@ -137,8 +125,6 @@ pub fn init_tracing(log_file_path: &str, log_level: Option<&str>) -> Result<Stri
137125 . with_target ( true )
138126 . with_thread_ids ( false )
139127 . with_thread_names ( false )
140- // .with_file(true)
141- // .with_line_number(true)
142128 . with_ansi ( false )
143129 . with_span_events ( FmtSpan :: NEW | FmtSpan :: CLOSE ) ,
144130 )
0 commit comments