# app/core/logging_config.py import logging import logging.config import sys import os # Added to potentially create log directory # Optional: Could make log level configurable via settings # from app.core.config import settings LOG_LEVEL = "INFO" # Default level (e.g., INFO, DEBUG, WARNING) # You could override this with an environment variable if needed: # LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper() # Define the logging configuration dictionary LOGGING_CONFIG = { "version": 1, "disable_existing_loggers": False, # Let default loggers (like uvicorn) work "formatters": { "default": { # Example format: timestamp | level | logger_name | function:line | message "format": "%(asctime)s | %(levelname)-8s | %(name)-30s | %(funcName)s:%(lineno)d | %(message)s", "datefmt": "%Y-%m-%d %H:%M:%S", }, "simple": { "format": "%(levelname)-8s | %(name)-20s | %(message)s", }, }, "handlers": { # Handler for console output "console": { "class": "logging.StreamHandler", "formatter": "default", # Use the detailed default formatter "stream": sys.stdout, # Log to standard output }, # --- Example File Handler (Optional) --- # Uncomment and configure if you want to log to a file # "file": { # "class": "logging.handlers.RotatingFileHandler", # "formatter": "default", # Use the detailed formatter for file logs too # "filename": "logs/chatbot_v2.log", # Path to log file # "maxBytes": 10 * 1024 * 1024, # 10 MB # "backupCount": 5, # Keep 5 backup files # "encoding": "utf-8", # }, # --- End File Handler --- }, "loggers": { # Root logger configuration "": { "handlers": ["console"], # Add "file" here if using file handler "level": LOG_LEVEL, # Default level for all loggers "propagate": False, # Prevent logs from propagating to higher-level handlers if any }, # Specific configuration for YOUR application's logger(s) # This assumes your code uses loggers like logging.getLogger("app.api.v2_endpoints") "app": { "handlers": ["console"], # Add "file" here if using file handler # Set your app's code to DEBUG for more detailed logs during development "level": "INFO", # Or keep as LOG_LEVEL if preferred "propagate": False, # Don't pass messages up to the root logger }, # Configuration for noisy third-party libraries "uvicorn": { "handlers": ["console"], "level": "INFO", # Keep uvicorn's own info messages "propagate": False, }, "uvicorn.error": { "handlers": ["console"], "level": "INFO", # Keep uvicorn errors visible "propagate": False, }, "uvicorn.access": { # Use simple formatter and higher level for access logs to reduce noise "handlers": ["console"], "level": "WARNING", # Change to INFO to see every request path "propagate": False, # "formatter": "simple", # Optional: Use simpler format }, "sqlalchemy": { # Catch-all for sqlalchemy loggers "handlers": ["console"], "level": "WARNING", # Set to INFO/DEBUG to see SQL statements/pool info "propagate": False, }, # Example: Quieten down specific noisy loggers if needed # "httpx": { # "handlers": ["console"], # "level": "WARNING", # "propagate": False, # }, # "openai": { # "handlers": ["console"], # "level": "WARNING", # "propagate": False, # }, }, } def get_logger(name: str = __name__) -> logging.Logger: """ Returns a logger configured with a StreamHandler and a basic formatter. """ logger = logging.getLogger(name) if not logger.handlers: handler = logging.StreamHandler() handler.setFormatter(logging.Formatter( "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s" )) logger.addHandler(handler) logger.setLevel(logging.INFO) return logger def setup_logging(): """ Applies the logging configuration using dictConfig. Creates log directory if file handler is used and directory doesn't exist. """ # --- Optional: Create log directory if file handler is used --- # if "file" in LOGGING_CONFIG["handlers"]: # log_dir = os.path.dirname(LOGGING_CONFIG["handlers"]["file"]["filename"]) # if log_dir and not os.path.exists(log_dir): # try: # os.makedirs(log_dir) # print(f"Created log directory: {log_dir}") # except OSError as e: # print(f"Error creating log directory {log_dir}: {e}", file=sys.stderr) # # Decide if this is critical and should prevent startup # --- End Optional Directory Creation --- try: logging.config.dictConfig(LOGGING_CONFIG) # Get a logger for this module *after* config is applied logger = logging.getLogger(__name__) logger.info(f"Logging configured successfully using dictConfig. Root level: {LOG_LEVEL}") except Exception as e: # Fallback to basic config if dictConfig fails for any reason print(f"Error configuring logging with dictConfig, falling back to basicConfig: {e}", file=sys.stderr) logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)-8s | %(name)-20s | %(message)s") logger = logging.getLogger(__name__) logger.exception("Logging setup failed, using basic config.")