Nove zavislosti, logovani, refaktoring

This commit is contained in:
Michal Kváček
2025-04-03 17:35:50 +02:00
parent a7fda95c29
commit 71911c0905
8 changed files with 74 additions and 34 deletions
+1 -1
View File
@@ -36,4 +36,4 @@ COPY . /app
WORKDIR /app WORKDIR /app
CMD ["uvicorn", "src.asgi:app", "--reload", "--reload-dir=/app", "--port=8000", "--host=0.0.0.0", "--log-level=debug"] CMD [ "gunicorn", "src.asgi:app", "--config=/app/config/gunicorn.py" ]
+18
View File
@@ -0,0 +1,18 @@
import os
# https://docs.gunicorn.org/en/stable/settings.html#settings
workers = os.cpu_count()
bind = "0.0.0.0:8000"
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000 # default
threads = 1 # default
# restart workeru
max_requests = 500000
max_requests_jitter = 10000
timeout = 30 # default
graceful_timeout = 30 # default
keepalive = 600 # s klientem udrzuje nginx
-1
View File
@@ -3,7 +3,6 @@ version: "3.3"
services: services:
api: api:
image: "docker.kvacek.cz/poletani/api:latest" image: "docker.kvacek.cz/poletani/api:latest"
command: "uvicorn src.asgi:app --port=8000 --host=0.0.0.0 --workers 4"
network_mode: "host" network_mode: "host"
restart: "always" restart: "always"
env_file: env_file:
+6 -1
View File
@@ -3,7 +3,10 @@ fastapi-jwt[authlib]==0.3.0
bcrypt bcrypt
fastapi-mail==1.4.2 fastapi-mail==1.4.2
strawberry-graphql[fastapi]==0.258.0 strawberry-graphql[fastapi]==0.258.0
uvicorn==0.34.0
gunicorn == 23.0.0
uvicorn[standard] == 0.34.0
sqlalchemy[asyncio] == 2.0.37 sqlalchemy[asyncio] == 2.0.37
aiomysql==0.2.0 aiomysql==0.2.0
alembic==1.14.1 alembic==1.14.1
@@ -15,3 +18,5 @@ exif==1.6.1
aiocache==0.12.3 aiocache==0.12.3
aiohttp==3.11.11 aiohttp==3.11.11
lxml==5.3.0 lxml==5.3.0
colorlog
+4 -1
View File
@@ -1,4 +1,5 @@
import dataclasses import dataclasses
from datetime import datetime
from fastapi import BackgroundTasks, Depends, Security from fastapi import BackgroundTasks, Depends, Security
from fastapi_jwt import JwtAuthorizationCredentials from fastapi_jwt import JwtAuthorizationCredentials
from sqlalchemy import select from sqlalchemy import select
@@ -9,6 +10,7 @@ from jwt import access_security
@dataclasses.dataclass @dataclasses.dataclass
class GraphQLContext(BaseContext): class GraphQLContext(BaseContext):
request_start_time: datetime
user_id: int user_id: int
organization_ids: set[int] organization_ids: set[int]
jwt_auth_credentials: JwtAuthorizationCredentials jwt_auth_credentials: JwtAuthorizationCredentials
@@ -29,5 +31,6 @@ async def setup_graphql_context(credentials: JwtAuthorizationCredentials = Secur
user_id=user_id, user_id=user_id,
organization_ids=organization_ids, organization_ids=organization_ids,
jwt_auth_credentials=credentials, jwt_auth_credentials=credentials,
background_tasks=Depends(BackgroundTasks) background_tasks=Depends(BackgroundTasks),
request_start_time=datetime.now()
) )
+12 -19
View File
@@ -1,37 +1,30 @@
from datetime import datetime
import strawberry import strawberry
from strawberry.extensions import SchemaExtension, ValidationCache from strawberry.extensions import SchemaExtension, ValidationCache, ParserCache
from graphql_schema.extensions.schema.error_logging import ErrorLogging from graphql_schema.extensions.schema.error_logging import ErrorLogging
from logger import log
from .mutation import Mutation from .mutation import Mutation
from .query import Query from .query import Query
# Toto se da kdyztak pouzit jako extension do Schema
# class SQLAlchemySession(Extension):
# def on_request_start(self):
# session = async_session()
# print(self.execution_context.context)
# self.execution_context.context["db"] = session
#
# async def on_request_end(self):
# await self.execution_context.context["db"].close()
class LoggingExtension(SchemaExtension): class LoggingExtension(SchemaExtension):
def on_request_start(self): def on_request_end(self):
print("request start") # nastavuje se pri vytvareni GraphQL contextu
request_start = self.execution_context.context.request_start_time
async def on_request_end(self): request_end = datetime.now()
print("request end")
request_time_ms = round((request_end - request_start).microseconds / 1000)
op_name = self.execution_context.operation_name
log.info(f"Operation {op_name} took {request_time_ms}ms")
schema = strawberry.Schema( schema = strawberry.Schema(
query=Query, query=Query,
mutation=Mutation, mutation=Mutation,
extensions=[ extensions=[
ErrorLogging(),
LoggingExtension(), LoggingExtension(),
ErrorLogging(),
ParserCache(),
ValidationCache() ValidationCache()
], ],
) )
+16 -10
View File
@@ -1,22 +1,28 @@
import logging import logging
import sys import sys
from colorlog import ColoredFormatter
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG) log.setLevel(logging.DEBUG)
# Create handlers for logging to the standard output and a file
stdoutHandler = logging.StreamHandler(stream=sys.stdout) stdoutHandler = logging.StreamHandler(stream=sys.stdout)
# Set the log levels on the handlers
stdoutHandler.setLevel(logging.DEBUG) stdoutHandler.setLevel(logging.DEBUG)
# Create a log format using Log Record attributes
fmt = logging.Formatter( fmt = ColoredFormatter(
"%(name)s: %(asctime)s | %(levelname)s | %(filename)s:%(lineno)s | %(process)d >>> %(message)s" "%(log_color)s[%(levelname)s]%(reset)s %(blue)s%(message)s%(reset)s %(light_black)s[%(filename)s:%(lineno)d]]%(reset)s",
datefmt=None,
reset=True,
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red,bg_white',
},
secondary_log_colors={},
style='%'
) )
# Set the log format on each handler
stdoutHandler.setFormatter(fmt) stdoutHandler.setFormatter(fmt)
# Add each handler to the Logger object
log.addHandler(stdoutHandler) log.addHandler(stdoutHandler)
+17 -1
View File
@@ -1,6 +1,9 @@
import sentry_sdk import sentry_sdk
from fastapi import FastAPI, HTTPException, APIRouter from fastapi import FastAPI, HTTPException, APIRouter
from graphql import GraphQLError from graphql import GraphQLError
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.starlette import StarletteIntegration
from sentry_sdk.integrations.strawberry import StrawberryIntegration
from sqlalchemy.exc import NoResultFound from sqlalchemy.exc import NoResultFound
from starlette.background import BackgroundTasks from starlette.background import BackgroundTasks
from starlette.middleware.cors import CORSMiddleware from starlette.middleware.cors import CORSMiddleware
@@ -27,10 +30,23 @@ class App:
def create_app(self): def create_app(self):
if SENTRY_DSN: if SENTRY_DSN:
fast_api_params = dict(
transaction_style="endpoint",
failed_request_status_codes={403, *range(500, 599)},
http_methods_to_capture=("GET", "POST"),
)
sentry_sdk.init( sentry_sdk.init(
dsn=SENTRY_DSN, dsn=SENTRY_DSN,
enable_tracing=True, enable_tracing=True,
ignore_errors=[GraphQLError, HTTPException] traces_sample_rate=0.5,
profiles_sample_rate=0.5,
ignore_errors=[GraphQLError, HTTPException],
integrations=[
StrawberryIntegration(async_execution=True),
StarletteIntegration(**fast_api_params),
FastApiIntegration(**fast_api_params),
]
) )
app = FastAPI( app = FastAPI(