add 500 alert

This commit is contained in:
cheykrym 2026-08-06 00:50:22 +03:00
parent 6bd69cd7fb
commit 0d0db1209e
3 changed files with 103 additions and 4 deletions

View File

@ -1,6 +1,8 @@
import os import os
import uuid import uuid
import logging import logging
import asyncio
import traceback
from fastapi import FastAPI, Request, HTTPException from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
@ -16,11 +18,101 @@ from starlette.status import (
) )
from datetime import datetime from datetime import datetime
from config import settings from config import settings
from common_lib.utils.http_client import client
# Настраиваем логирование # Настраиваем логирование
logging.basicConfig(level=logging.ERROR) logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
FEEDBACK_SERVICE_URL = "http://yobble-feedback-service:8000"
ERROR_LOG_ENVIRONMENT = "beta"
ERROR_LOG_METHODS = {"GET", "POST", "PUT", "PATCH", "DELETE"}
def _get_service_name() -> str:
return (
getattr(settings, "SERVICE_NAME", None)
or os.getenv("SERVICE_NAME")
or "unknown"
)
def _parse_uuid(value: str | None) -> str | None:
if not value:
return None
try:
return str(uuid.UUID(str(value)))
except ValueError:
return None
async def _read_request_body(request: Request):
try:
body = await request.body()
except Exception:
return None
if not body:
return None
content_type = request.headers.get("content-type", "")
if "application/json" in content_type:
try:
return await request.json()
except Exception:
pass
return {
"raw": body.decode("utf-8", errors="replace")
}
async def _send_internal_error_log(request: Request, exc: Exception, error_id: str) -> None:
service_name = _get_service_name()
if service_name == "feedback_service":
return
method = request.method.upper()
if method not in ERROR_LOG_METHODS:
method = "GET"
current_user = getattr(request.state, "current_user", None)
user_id = getattr(current_user, "user_id", None)
session_id = getattr(current_user, "session_id", None)
payload = {
"user_id": str(user_id) if user_id else None,
"service_name": service_name,
"source": "api",
"endpoint": request.url.path,
"method": method,
"http_status": 500,
"request_headers": dict(request.headers),
"request_body": await _read_request_body(request),
"request_query_params": dict(request.query_params),
"response_body": {
"status": "error",
"errors": [{"field": "server", "message": "Internal Server Error"}],
},
"ip_address": request.client.host if request.client else None,
"user_agent": request.headers.get("user-agent"),
"session_id": str(session_id) if session_id else _parse_uuid(request.headers.get("x-session-id")),
"error_message": str(exc),
"error_stack": "".join(traceback.format_exception(type(exc), exc, exc.__traceback__)),
"error_code": error_id,
"severity": "high",
"environment": ERROR_LOG_ENVIRONMENT,
"correlation_id": _parse_uuid(request.headers.get("x-request-id")),
}
try:
await client.post(
f"{FEEDBACK_SERVICE_URL}/internal/error-log",
json=payload,
)
except Exception as log_exc:
print("error auto log failed:", log_exc)
def register_error_handlers(app: FastAPI): def register_error_handlers(app: FastAPI):
@ -156,6 +248,7 @@ def register_error_handlers(app: FastAPI):
) )
print("500 critical error:", full_log_message) print("500 critical error:", full_log_message)
asyncio.create_task(_send_internal_error_log(request, exc, error_id))
# with open(log_filename, "w", encoding="utf-8") as log_file: # with open(log_filename, "w", encoding="utf-8") as log_file:
# log_file.write(full_log_message) # log_file.write(full_log_message)

View File

@ -183,7 +183,9 @@ async def get_current_user(
detail="Not authenticated" detail="Not authenticated"
) )
return await _fetch_current_user(request, token, require_permissions=False, is_web=is_web) current_user = await _fetch_current_user(request, token, require_permissions=False, is_web=is_web)
request.state.current_user = current_user
return current_user
async def get_current_user_with_permissions( async def get_current_user_with_permissions(
@ -205,7 +207,9 @@ async def get_current_user_with_permissions(
detail="Not authenticated" detail="Not authenticated"
) )
return await _fetch_current_user(request, token, require_permissions=True, is_web=is_web) current_user = await _fetch_current_user(request, token, require_permissions=True, is_web=is_web)
request.state.current_user = current_user
return current_user
async def get_current_user_or_bot( async def get_current_user_or_bot(
@ -229,7 +233,9 @@ async def get_current_user_or_bot(
) )
is_bot_flag = str(is_bot).lower() in ("true", "1", "yes") if is_bot else False is_bot_flag = str(is_bot).lower() in ("true", "1", "yes") if is_bot else False
return await _fetch_current_user(request, token, require_permissions=False, is_bot=is_bot_flag, is_web=is_web) current_user = await _fetch_current_user(request, token, require_permissions=False, is_bot=is_bot_flag, is_web=is_web)
request.state.current_user = current_user
return current_user
def validate_username(value: str, def validate_username(value: str,

View File

@ -1,6 +1,6 @@
[project] [project]
name = "common-lib" name = "common-lib"
version = "0.0.57" version = "0.0.58"
description = "Библиотека общих компонентов для микросервисов yobble" description = "Библиотека общих компонентов для микросервисов yobble"
authors = [{ name = "cheykrym", email = "you@example.com" }] authors = [{ name = "cheykrym", email = "you@example.com" }]
license = "MIT" license = "MIT"