run_with_logging.py
1 #!/usr/bin/env python3 2 """ 3 Streamlit wrapper with logging configuration 4 Ensures all Streamlit errors and logs are captured to daily log files 5 """ 6 7 import sys 8 import os 9 import logging 10 from datetime import datetime 11 from pathlib import Path 12 13 class StreamToLogger: 14 """File-like stream object that redirects writes to a logger instance.""" 15 def __init__(self, logger, log_level=logging.INFO): 16 self.logger = logger 17 self.log_level = log_level 18 self.linebuf = '' 19 20 def write(self, buf): 21 for line in buf.rstrip().splitlines(): 22 self.logger.log(self.log_level, line.rstrip()) 23 sys.__stdout__.write(buf) # Also write to real stdout 24 25 def flush(self): 26 pass 27 28 def setup_logging(script_name="dashboard"): 29 """Configure Python logging to write to daily log files""" 30 # Create logs directory 31 log_dir = Path("./logs") 32 log_dir.mkdir(exist_ok=True) 33 34 # Generate log filename with date 35 date = datetime.now().strftime("%Y-%m-%d") 36 log_file = log_dir / f"{script_name}-{date}.log" 37 38 # Configure root logger 39 logging.basicConfig( 40 level=logging.INFO, 41 format='[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s', 42 handlers=[ 43 logging.FileHandler(log_file, mode='a'), 44 logging.StreamHandler(sys.__stdout__) # Use original stdout 45 ] 46 ) 47 48 # Configure streamlit logger specifically 49 streamlit_logger = logging.getLogger('streamlit') 50 streamlit_logger.setLevel(logging.INFO) 51 52 # Configure watchdog logger (used by streamlit for file watching) 53 watchdog_logger = logging.getLogger('watchdog') 54 watchdog_logger.setLevel(logging.WARNING) 55 56 # Log startup 57 logger = logging.getLogger('dashboard') 58 logger.info("=" * 60) 59 logger.info(f"Starting Streamlit dashboard") 60 logger.info(f"Log file: {log_file}") 61 logger.info(f"PID: {os.getpid()}") 62 logger.info(f"CWD: {os.getcwd()}") 63 logger.info("=" * 60) 64 65 # Redirect stderr to logger 66 stderr_logger = logging.getLogger('stderr') 67 sys.stderr = StreamToLogger(stderr_logger, logging.ERROR) 68 69 return logger 70 71 def handle_exception(exc_type, exc_value, exc_traceback): 72 """Global exception handler to log uncaught exceptions""" 73 if issubclass(exc_type, KeyboardInterrupt): 74 # Call default handler for KeyboardInterrupt 75 sys.__excepthook__(exc_type, exc_value, exc_traceback) 76 return 77 78 logger = logging.getLogger('dashboard') 79 logger.error("Uncaught exception:", exc_info=(exc_type, exc_value, exc_traceback)) 80 81 def main(): 82 if len(sys.argv) < 2: 83 print("Usage: run_with_logging.py <streamlit_script> [args...]") 84 sys.exit(1) 85 86 script_name = Path(sys.argv[1]).stem 87 logger = setup_logging(script_name) 88 89 # Install global exception handler 90 sys.excepthook = handle_exception 91 92 try: 93 # Import and run streamlit 94 from streamlit.web import cli as stcli 95 96 # Prepare streamlit args 97 streamlit_args = sys.argv[1:] 98 99 logger.info(f"Launching: streamlit run {' '.join(streamlit_args)}") 100 101 # Run streamlit 102 sys.argv = ['streamlit', 'run'] + streamlit_args 103 sys.exit(stcli.main()) 104 105 except Exception as e: 106 logger.error(f"Failed to start Streamlit: {e}", exc_info=True) 107 sys.exit(1) 108 109 if __name__ == "__main__": 110 main()