"""Launcher for the FastAPI application. Fundametally this replicates the functionality of the uvicorn and fastapi CLI interfaces, but we need this to setup the logging correctly (and most importantly globally)""" import os import uvicorn import logging logger = logging.getLogger(__name__) is_debug = os.getenv('DEBUG', "false") == "true" is_kubernetes = os.getenv('KUBERNETES_SERVICE_HOST', None) is not None def logger_setup(): """ Setup the global logging configuration """ logging_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' # Make uvicorn conform to the global logging configuration uvicorn_logger = logging.getLogger('uvicorn') uvicorn_logger.propagate = True uvicorn_logger.handlers = [] # Remove default handlers to avoid duplicate logs if is_kubernetes: # in that case we want to log to stdout and also to loki from loki_logger_handler.loki_logger_handler import LokiLoggerHandler loki_url = os.getenv('LOKI_URL') if loki_url is None: raise ValueError("LOKI_URL environment variable is not set") loki_handler = LokiLoggerHandler( url = loki_url, labels = {'app': 'anyway', 'environment': 'staging' if is_debug else 'production'} ) logger.info(f"Logging to Loki at {loki_url} with {loki_handler.labels} and {is_debug=}") if is_debug: logging.basicConfig( format = logging_format, level = logging.DEBUG, handlers = [loki_handler, logging.StreamHandler()] ) # we need to silence the debug logs made by the loki handler logging.getLogger('urllib3.connectionpool').setLevel(logging.INFO) else: logging.basicConfig( format = logging_format, level = logging.INFO, handlers = [loki_handler, logging.StreamHandler()] ) else: # if we are in a debug (local) session, set verbose and rich logging from rich.logging import RichHandler logging.basicConfig( format = logging_format, level = logging.DEBUG, handlers = [RichHandler()] ) def uvicorn_run(): """ Run the FastAPI application using uvicorn """ num_workers = os.getenv('NUM_WORKERS', 1) logger.info(f"Starting FastAPI+uvicorn with {num_workers=}, {is_debug=}, {is_kubernetes=}") uvicorn.run( # we could in theory directly import the app and pass it as an object # this 'import string' is required for hot reloading and scaling 'src.main:app', host = '0.0.0.0', port = 8000, log_config = None, access_log = True, # Disable uvicorn's logging configuration so that it inherits the global one workers = num_workers, # Hot reload breaks logging, so we leave it disabled # reload = is_debug ) if __name__ == '__main__': logger_setup() uvicorn_run()