From 32aca6ba7edff34ed45a7e829e02faa3d7881363 Mon Sep 17 00:00:00 2001 From: GabrielBarberini Date: Sun, 9 Jun 2024 19:04:03 -0300 Subject: [PATCH] instrument API with open telemetry/uptrace --- Dockerfile | 2 +- lib/__init__.py | 2 +- lib/api.py | 2 +- lib/routes/environment.py | 21 ++++++++++++----- lib/routes/flight.py | 49 +++++++++++++++++++++++++-------------- lib/routes/motor.py | 29 +++++++++++++++-------- lib/routes/rocket.py | 31 +++++++++++++++++-------- lib/settings/gunicorn.py | 17 ++++++++++++++ pyproject.toml | 2 +- requirements.txt | 3 +++ 10 files changed, 111 insertions(+), 47 deletions(-) create mode 100644 lib/settings/gunicorn.py diff --git a/Dockerfile b/Dockerfile index 9ddbeb9..282f994 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,4 +16,4 @@ RUN apt-get update && \ COPY ./lib /app/lib -CMD ["gunicorn", "-w 1", "--threads=2", "-k", "uvicorn.workers.UvicornWorker", "lib:app", "--log-level", "Debug", "-b", "0.0.0.0:3000", "--timeout", "120"] +CMD ["gunicorn", "-c", "lib/settings/gunicorn.py", "-w", "1", "--threads=2", "-k", "uvicorn.workers.UvicornWorker", "lib:app", "--log-level", "Debug", "-b", "0.0.0.0:3000", "--timeout", "30"] diff --git a/lib/__init__.py b/lib/__init__.py index 5213401..db869db 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -25,4 +25,4 @@ def parse_error(error): return f"{exc_type} exception: {exc_obj}" -from .api import app # noqa +from .api import app # noqa diff --git a/lib/api.py b/lib/api.py index 332e575..e91e166 100644 --- a/lib/api.py +++ b/lib/api.py @@ -35,7 +35,7 @@ def custom_openapi(): return app.openapi_schema openapi_schema = get_openapi( title="RocketPy Infinity-API", - version="1.1.0 BETA", + version="1.2.0 BETA", description=( "

RocketPy Infinity-API is a RESTful Open API for RocketPy, a rocket flight simulator.

" "
" diff --git a/lib/routes/environment.py b/lib/routes/environment.py index 6647073..2417022 100644 --- a/lib/routes/environment.py +++ b/lib/routes/environment.py @@ -3,6 +3,7 @@ """ from fastapi import APIRouter +from opentelemetry import trace from lib.views.environment import ( EnvSummary, @@ -24,6 +25,8 @@ }, ) +tracer = trace.get_tracer(__name__) + @router.post("/") async def create_env(env: Env) -> EnvCreated: @@ -33,7 +36,8 @@ async def create_env(env: Env) -> EnvCreated: ## Args ``` models.Env JSON ``` """ - return await EnvController(env).create_env() + with tracer.start_as_current_span("create_env"): + return await EnvController(env).create_env() @router.get("/{env_id}") @@ -44,7 +48,8 @@ async def read_env(env_id: str) -> Env: ## Args ``` env_id: str ``` """ - return await EnvController.get_env_by_id(env_id) + with tracer.start_as_current_span("read_env"): + return await EnvController.get_env_by_id(env_id) @router.put("/{env_id}") @@ -58,7 +63,8 @@ async def update_env(env_id: str, env: Env) -> EnvUpdated: env: models.Env JSON ``` """ - return await EnvController(env).update_env_by_id(env_id) + with tracer.start_as_current_span("update_env"): + return await EnvController(env).update_env_by_id(env_id) @router.delete("/{env_id}") @@ -69,7 +75,8 @@ async def delete_env(env_id: str) -> EnvDeleted: ## Args ``` env_id: Environment ID hash ``` """ - return await EnvController.delete_env_by_id(env_id) + with tracer.start_as_current_span("delete_env"): + return await EnvController.delete_env_by_id(env_id) @router.get("/rocketpy/{env_id}") @@ -80,7 +87,8 @@ async def read_rocketpy_env(env_id: str) -> EnvPickle: ## Args ``` env_id: str ``` """ - return await EnvController.get_rocketpy_env_as_jsonpickle(env_id) + with tracer.start_as_current_span("read_rocketpy_env"): + return await EnvController.get_rocketpy_env_as_jsonpickle(env_id) @router.get("/{env_id}/simulate") @@ -91,4 +99,5 @@ async def simulate_env(env_id: str) -> EnvSummary: ## Args ``` env_id: str ``` """ - return await EnvController.simulate_env(env_id) + with tracer.start_as_current_span("simulate_env"): + return await EnvController.simulate_env(env_id) diff --git a/lib/routes/flight.py b/lib/routes/flight.py index 863862b..2d597c6 100644 --- a/lib/routes/flight.py +++ b/lib/routes/flight.py @@ -3,6 +3,7 @@ """ from fastapi import APIRouter +from opentelemetry import trace from lib.views.flight import ( FlightSummary, @@ -27,6 +28,8 @@ }, ) +tracer = trace.get_tracer(__name__) + @router.post("/") async def create_flight( @@ -38,9 +41,10 @@ async def create_flight( ## Args ``` Flight object as JSON ``` """ - return await FlightController( - flight, rocket_option=rocket_option, motor_kind=motor_kind - ).create_flight() + with tracer.start_as_current_span("create_flight"): + return await FlightController( + flight, rocket_option=rocket_option, motor_kind=motor_kind + ).create_flight() @router.get("/{flight_id}") @@ -51,7 +55,8 @@ async def read_flight(flight_id: str) -> Flight: ## Args ``` flight_id: Flight ID hash ``` """ - return await FlightController.get_flight_by_id(flight_id) + with tracer.start_as_current_span("read_flight"): + return await FlightController.get_flight_by_id(flight_id) @router.get("/rocketpy/{flight_id}") @@ -62,7 +67,10 @@ async def read_rocketpy_flight(flight_id: str) -> FlightPickle: ## Args ``` flight_id: Flight ID hash. ``` """ - return await FlightController.get_rocketpy_flight_as_jsonpickle(flight_id) + with tracer.start_as_current_span("read_rocketpy_flight"): + return await FlightController.get_rocketpy_flight_as_jsonpickle( + flight_id + ) @router.put("/{flight_id}/env") @@ -76,7 +84,10 @@ async def update_flight_env(flight_id: str, env: Env) -> FlightUpdated: env: env object as JSON ``` """ - return await FlightController.update_env_by_flight_id(flight_id, env=env) + with tracer.start_as_current_span("update_flight_env"): + return await FlightController.update_env_by_flight_id( + flight_id, env=env + ) @router.put("/{flight_id}/rocket") @@ -95,12 +106,13 @@ async def update_flight_rocket( rocket: Rocket object as JSON ``` """ - return await FlightController.update_rocket_by_flight_id( - flight_id, - rocket=rocket, - rocket_option=rocket_option, - motor_kind=motor_kind, - ) + with tracer.start_as_current_span("update_flight_rocket"): + return await FlightController.update_rocket_by_flight_id( + flight_id, + rocket=rocket, + rocket_option=rocket_option, + motor_kind=motor_kind, + ) @router.put("/{flight_id}") @@ -119,9 +131,10 @@ async def update_flight( flight: Flight object as JSON ``` """ - return await FlightController( - flight, rocket_option=rocket_option, motor_kind=motor_kind - ).update_flight_by_id(flight_id) + with tracer.start_as_current_span("update_flight"): + return await FlightController( + flight, rocket_option=rocket_option, motor_kind=motor_kind + ).update_flight_by_id(flight_id) @router.delete("/{flight_id}") @@ -132,7 +145,8 @@ async def delete_flight(flight_id: str) -> FlightDeleted: ## Args ``` flight_id: Flight ID hash ``` """ - return await FlightController.delete_flight_by_id(flight_id) + with tracer.start_as_current_span("delete_flight"): + return await FlightController.delete_flight_by_id(flight_id) @router.get("/{flight_id}/simulate") @@ -143,4 +157,5 @@ async def simulate_flight(flight_id: str) -> FlightSummary: ## Args ``` flight_id: Flight ID hash ``` """ - return await FlightController.simulate_flight(flight_id) + with tracer.start_as_current_span("simulate_flight"): + return await FlightController.simulate_flight(flight_id) diff --git a/lib/routes/motor.py b/lib/routes/motor.py index 3f74825..5c4cbff 100644 --- a/lib/routes/motor.py +++ b/lib/routes/motor.py @@ -3,6 +3,7 @@ """ from fastapi import APIRouter +from opentelemetry import trace from lib.views.motor import ( MotorSummary, @@ -24,6 +25,8 @@ }, ) +tracer = trace.get_tracer(__name__) + @router.post("/") async def create_motor(motor: Motor, motor_kind: MotorKinds) -> MotorCreated: @@ -33,9 +36,10 @@ async def create_motor(motor: Motor, motor_kind: MotorKinds) -> MotorCreated: ## Args ``` Motor object as a JSON ``` """ - return await MotorController( - motor=motor, motor_kind=motor_kind - ).create_motor() + with tracer.start_as_current_span("create_motor"): + return await MotorController( + motor=motor, motor_kind=motor_kind + ).create_motor() @router.get("/{motor_id}") @@ -46,7 +50,8 @@ async def read_motor(motor_id: str) -> Motor: ## Args ``` motor_id: Motor ID hash ``` """ - return await MotorController.get_motor_by_id(motor_id) + with tracer.start_as_current_span("read_motor"): + return await MotorController.get_motor_by_id(motor_id) @router.put("/{motor_id}") @@ -62,9 +67,10 @@ async def update_motor( motor: Motor object as JSON ``` """ - return await MotorController( - motor=motor, motor_kind=motor_kind - ).update_motor_by_id(motor_id) + with tracer.start_as_current_span("update_motor"): + return await MotorController( + motor=motor, motor_kind=motor_kind + ).update_motor_by_id(motor_id) @router.delete("/{motor_id}") @@ -75,7 +81,8 @@ async def delete_motor(motor_id: str) -> MotorDeleted: ## Args ``` motor_id: Motor ID hash ``` """ - return await MotorController.delete_motor_by_id(motor_id) + with tracer.start_as_current_span("delete_motor"): + return await MotorController.delete_motor_by_id(motor_id) @router.get("/rocketpy/{motor_id}") @@ -86,7 +93,8 @@ async def read_rocketpy_motor(motor_id: str) -> MotorPickle: ## Args ``` motor_id: Motor ID hash ``` """ - return await MotorController.get_rocketpy_motor_as_jsonpickle(motor_id) + with tracer.start_as_current_span("read_rocketpy_motor"): + return await MotorController.get_rocketpy_motor_as_jsonpickle(motor_id) @router.get("/{motor_id}/simulate") @@ -97,4 +105,5 @@ async def simulate_motor(motor_id: str) -> MotorSummary: ## Args ``` motor_id: Motor ID hash ``` """ - return await MotorController.simulate_motor(motor_id) + with tracer.start_as_current_span("simulate_motor"): + return await MotorController.simulate_motor(motor_id) diff --git a/lib/routes/rocket.py b/lib/routes/rocket.py index b8b9687..1d7301e 100644 --- a/lib/routes/rocket.py +++ b/lib/routes/rocket.py @@ -3,6 +3,7 @@ """ from fastapi import APIRouter +from opentelemetry import trace from lib.views.rocket import ( RocketSummary, @@ -25,6 +26,8 @@ }, ) +tracer = trace.get_tracer(__name__) + @router.post("/") async def create_rocket( @@ -36,9 +39,10 @@ async def create_rocket( ## Args ``` Rocket object as a JSON ``` """ - return await RocketController( - rocket=rocket, rocket_option=rocket_option, motor_kind=motor_kind - ).create_rocket() + with tracer.start_as_current_span("create_rocket"): + return await RocketController( + rocket=rocket, rocket_option=rocket_option, motor_kind=motor_kind + ).create_rocket() @router.get("/{rocket_id}") @@ -49,7 +53,8 @@ async def read_rocket(rocket_id: str) -> Rocket: ## Args ``` rocket_id: Rocket ID hash ``` """ - return await RocketController.get_rocket_by_id(rocket_id) + with tracer.start_as_current_span("read_rocket"): + return await RocketController.get_rocket_by_id(rocket_id) @router.put("/{rocket_id}") @@ -68,9 +73,10 @@ async def update_rocket( rocket: Rocket object as JSON ``` """ - return await RocketController( - rocket=rocket, rocket_option=rocket_option, motor_kind=motor_kind - ).update_rocket_by_id(rocket_id) + with tracer.start_as_current_span("update_rocket"): + return await RocketController( + rocket=rocket, rocket_option=rocket_option, motor_kind=motor_kind + ).update_rocket_by_id(rocket_id) @router.delete("/{rocket_id}") @@ -81,7 +87,8 @@ async def delete_rocket(rocket_id: str) -> RocketDeleted: ## Args ``` rocket_id: Rocket ID hash ``` """ - return await RocketController.delete_rocket_by_id(rocket_id) + with tracer.start_as_current_span("delete_rocket"): + return await RocketController.delete_rocket_by_id(rocket_id) @router.get("/rocketpy/{rocket_id}") @@ -92,7 +99,10 @@ async def read_rocketpy_rocket(rocket_id: str) -> RocketPickle: ## Args ``` rocket_id: Rocket ID hash ``` """ - return await RocketController.get_rocketpy_rocket_as_jsonpickle(rocket_id) + with tracer.start_as_current_span("read_rocketpy_rocket"): + return await RocketController.get_rocketpy_rocket_as_jsonpickle( + rocket_id + ) @router.get("/{rocket_id}/simulate") @@ -103,4 +113,5 @@ async def simulate_rocket(rocket_id: str) -> RocketSummary: ## Args ``` rocket_id: Rocket ID hash ``` """ - return await RocketController.simulate_rocket(rocket_id) + with tracer.start_as_current_span("simulate_rocket"): + return await RocketController.simulate_rocket(rocket_id) diff --git a/lib/settings/gunicorn.py b/lib/settings/gunicorn.py new file mode 100644 index 0000000..70742c6 --- /dev/null +++ b/lib/settings/gunicorn.py @@ -0,0 +1,17 @@ +import uptrace +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor +from lib.secrets import Secrets + + +def post_fork(server, worker): # pylint: disable=unused-argument + uptrace.configure_opentelemetry( + dsn=Secrets.get_secret("UPTRACE_DSN"), + service_name="infinity-api", + service_version="1.2.0", + deployment_environment="production", + ) + from app.lib import ( # pylint: disable=import-outside-toplevel + server as fastapi_server, + ) + + FastAPIInstrumentor.instrument_app(fastapi_server) diff --git a/pyproject.toml b/pyproject.toml index b78b26d..70a9e16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ dependencies = {file = ["requirements.txt"]} [project] name = "Infinity-API" -version = "1.1.0" +version = "1.2.0" description = "RESTFULL open API for rocketpy" dynamic = ["dependencies"] requires-python = ">=3.12" diff --git a/requirements.txt b/requirements.txt index be65cb7..9abaebd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,6 @@ jsonpickle gunicorn uvicorn rocketpy +opentelemetry.instrumentation.fastapi +opentelemetry-api +opentelemetry-sdk