/ dashboard / run_with_logging.py
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()