actually use fastapi lifetime manager to setup logging
Some checks failed
Run testing on the backend code / Build (pull_request) Has been cancelled
Run linting on the backend code / Build (pull_request) Has been cancelled
Build and deploy the backend to staging / Deploy to staging (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Has been cancelled
Some checks failed
Run testing on the backend code / Build (pull_request) Has been cancelled
Run linting on the backend code / Build (pull_request) Has been cancelled
Build and deploy the backend to staging / Deploy to staging (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Has been cancelled
This commit is contained in:
parent
bc63b57154
commit
4e07c10969
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
@ -4,17 +4,21 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
// backend - python app that launches a uvicorn server
|
||||
// backend - python using fastapi
|
||||
{
|
||||
"name": "Backend - debug",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "launcher.py",
|
||||
"env": {
|
||||
"DEBUG": "true"
|
||||
},
|
||||
"jinja": true,
|
||||
"cwd": "${workspaceFolder}/backend"
|
||||
"cwd": "${workspaceFolder}/backend",
|
||||
"module": "fastapi",
|
||||
"args": [
|
||||
"dev",
|
||||
"src/main.py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Backend - tester",
|
||||
|
@ -7,7 +7,6 @@ RUN pip install pipenv
|
||||
RUN pipenv install --deploy --system
|
||||
|
||||
COPY src src
|
||||
COPY launcher.py .
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
@ -17,4 +16,4 @@ ENV OSM_CACHE_DIR=/cache
|
||||
ENV MEMCACHED_HOST_PATH=none
|
||||
ENV LOKI_URL=none
|
||||
|
||||
CMD ["python", "launcher.py"]
|
||||
CMD ["fastapi", "src/main.py", "--port", "8000", "--workers", "$NUM_WORKERS"]
|
||||
|
@ -1,82 +0,0 @@
|
||||
"""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 = int(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()
|
@ -1,29 +0,0 @@
|
||||
version: 1
|
||||
disable_existing_loggers: False
|
||||
formatters:
|
||||
standard:
|
||||
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
handlers:
|
||||
console:
|
||||
class: logging.StreamHandler
|
||||
formatter: standard
|
||||
level: DEBUG
|
||||
rich:
|
||||
class: rich.logging.RichHandler
|
||||
level: DEBUG
|
||||
loki:
|
||||
class: loki_logger_handler.loki_logger_handler.LokiLoggerHandler
|
||||
level: DEBUG
|
||||
formatter: standard
|
||||
url: ${LOKI_URL}
|
||||
labels:
|
||||
app: anyway
|
||||
environment: ${ENVIRONMENT}
|
||||
loggers:
|
||||
uvicorn:
|
||||
handlers: [console, rich, loki]
|
||||
level: DEBUG
|
||||
propagate: False
|
||||
root:
|
||||
handlers: [console, rich, loki]
|
||||
level: DEBUG
|
56
backend/src/logging_config.py
Normal file
56
backend/src/logging_config.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""Sets up global logging configuration for the application."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def configure_logging():
|
||||
"""
|
||||
Called at startup of a FastAPI application instance to setup logging. Depending on the environment, it will log to stdout or to Loki.
|
||||
"""
|
||||
|
||||
is_debug = os.getenv('DEBUG', "false") == "true"
|
||||
is_kubernetes = os.getenv('KUBERNETES_SERVICE_HOST') is not None
|
||||
|
||||
|
||||
if not 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')
|
||||
loki_url = "http://localhost:3100/loki/api/v1/push"
|
||||
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=}")
|
||||
logging_handlers = [loki_handler, logging.StreamHandler()]
|
||||
logging_level = logging.DEBUG if is_debug else logging.INFO
|
||||
# no need for time since it's added by loki or can be shown in kube logs
|
||||
logging_format = '%(name)s - %(levelname)s - %(message)s'
|
||||
|
||||
else:
|
||||
# if we are in a debug (local) session, set verbose and rich logging
|
||||
from rich.logging import RichHandler
|
||||
logging_handlers = [RichHandler()]
|
||||
logging_level = logging.DEBUG if is_debug else logging.INFO
|
||||
logging_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
level = logging_level,
|
||||
format = logging_format,
|
||||
handlers = logging_handlers
|
||||
)
|
||||
|
||||
# also overwrite the uvicorn loggers
|
||||
logging.getLogger('uvicorn').handlers = logging_handlers
|
||||
logging.getLogger('uvicorn.access').handlers = logging_handlers
|
||||
logging.getLogger('uvicorn.error').handlers = logging_handlers
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
import logging
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from .logging_config import configure_logging
|
||||
from .structs.landmark import Landmark, Toilets
|
||||
from .structs.preferences import Preferences
|
||||
from .structs.linked_landmarks import LinkedLandmarks
|
||||
@ -13,15 +15,26 @@ from .utils.optimizer import Optimizer
|
||||
from .utils.refiner import Refiner
|
||||
from .cache import client as cache_client
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = FastAPI()
|
||||
manager = LandmarkManager()
|
||||
optimizer = Optimizer()
|
||||
refiner = Refiner(optimizer=optimizer)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Function to run at the start of the app"""
|
||||
logger.info("Setting up logging")
|
||||
configure_logging()
|
||||
yield
|
||||
logger.info("Shutting down logging")
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
|
||||
@app.post("/trip/new")
|
||||
def new_trip(preferences: Preferences,
|
||||
start: tuple[float, float],
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Definition of the Landmark class to handle visitable objects across the world."""
|
||||
|
||||
from typing import Optional, Literal
|
||||
from uuid import uuid4
|
||||
from uuid import uuid4, UUID
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
@ -29,12 +29,12 @@ class Landmark(BaseModel) :
|
||||
description (Optional[str]): A text description of the landmark.
|
||||
duration (Optional[int]): The estimated time to visit the landmark (in minutes).
|
||||
name_en (Optional[str]): The English name of the landmark.
|
||||
uuid (str): A unique identifier for the landmark, generated by default using uuid4.
|
||||
uuid (UUID): A unique identifier for the landmark, generated by default using uuid4.
|
||||
must_do (Optional[bool]): Whether the landmark is a "must-do" attraction.
|
||||
must_avoid (Optional[bool]): Whether the landmark should be avoided.
|
||||
is_secondary (Optional[bool]): Whether the landmark is secondary or less important.
|
||||
time_to_reach_next (Optional[int]): Estimated time (in minutes) to reach the next landmark.
|
||||
next_uuid (Optional[str]): UUID of the next landmark in sequence (if applicable).
|
||||
next_uuid (Optional[UUID]): UUID of the next landmark in sequence (if applicable).
|
||||
"""
|
||||
|
||||
# Properties of the landmark
|
||||
@ -52,7 +52,7 @@ class Landmark(BaseModel) :
|
||||
name_en : Optional[str] = None
|
||||
|
||||
# Unique ID of a given landmark
|
||||
uuid: str = Field(default_factory=uuid4)
|
||||
uuid: UUID = Field(default_factory=uuid4)
|
||||
|
||||
# Additional properties depending on specific tour
|
||||
must_do : Optional[bool] = False
|
||||
@ -60,7 +60,7 @@ class Landmark(BaseModel) :
|
||||
is_secondary : Optional[bool] = False
|
||||
|
||||
time_to_reach_next : Optional[int] = 0
|
||||
next_uuid : Optional[str] = None
|
||||
next_uuid : Optional[UUID] = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Definition of the Trip class."""
|
||||
|
||||
import uuid
|
||||
from uuid import uuid4, UUID
|
||||
from pydantic import BaseModel, Field
|
||||
from pymemcache.client.base import Client
|
||||
|
||||
@ -19,9 +19,9 @@ class Trip(BaseModel):
|
||||
Methods:
|
||||
from_linked_landmarks: create a Trip from LinkedLandmarks object.
|
||||
"""
|
||||
uuid: str = Field(default_factory=uuid.uuid4)
|
||||
uuid: UUID = Field(default_factory=uuid4)
|
||||
total_time: int
|
||||
first_landmark_uuid: str
|
||||
first_landmark_uuid: UUID
|
||||
|
||||
|
||||
@classmethod
|
||||
@ -31,7 +31,7 @@ class Trip(BaseModel):
|
||||
"""
|
||||
trip = Trip(
|
||||
total_time = landmarks.total_time,
|
||||
first_landmark_uuid = str(landmarks[0].uuid)
|
||||
first_landmark_uuid = landmarks[0].uuid
|
||||
)
|
||||
|
||||
# Store the trip in the cache
|
||||
|
Loading…
x
Reference in New Issue
Block a user