Nove zavislosti, logovani, refaktoring
This commit is contained in:
+1
-1
@@ -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" ]
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user