### Added - 🔍 **OAuth Detailed Logging**: Добавлено пошаговое логирование OAuth callback для диагностики ошибок `auth_failed` - 🧪 **OAuth Diagnostic Tools**: Создан `oauth_debug.py` для анализа OAuth callback параметров и диагностики проблем - 📊 **OAuth Test Helper**: Добавлен `oauth_test_helper.py` для создания тестовых состояний OAuth в Redis - 🔧 **OAuth Provider Detection**: Автоматическое определение OAuth провайдера по формату authorization code ### Fixed - 🚨 **OAuth Callback Error Handling**: Улучшена обработка исключений в OAuth callback с детальным логированием каждого шага - 🔍 **OAuth Exception Tracking**: Добавлено логирование исключений на каждом этапе: token exchange, profile fetch, user creation, session creation - 📋 **OAuth Error Diagnosis**: Реализована система диагностики для выявления точной причины `error=auth_failed` редиректов ### Changed - 🔧 **OAuth Callback Flow**: Разделен OAuth callback на логические шаги с индивидуальным error handling - 📝 **OAuth Error Messages**: Улучшены сообщения об ошибках для более точной диагностики проблем
This commit is contained in:
@@ -15,7 +15,8 @@ _lib_path = Path(__file__).parents[1]
|
||||
_leng_path = len(_lib_path.as_posix())
|
||||
|
||||
|
||||
def filter(record: logging.LogRecord) -> bool:
|
||||
def console_filter(record: logging.LogRecord) -> bool:
|
||||
"""🔍 Фильтр для консольного вывода - подавляет спам авторизации"""
|
||||
# Define `package` attribute with the relative path.
|
||||
record.package = record.pathname[_leng_path + 1 :].replace(".py", "")
|
||||
record.emoji = (
|
||||
@@ -32,10 +33,10 @@ def filter(record: logging.LogRecord) -> bool:
|
||||
else ""
|
||||
)
|
||||
|
||||
# Подавляем логи ошибок авторизации и GraphQL трейсбеки
|
||||
# 🚫 Подавляем логи ошибок авторизации ТОЛЬКО в консоли (не в Sentry)
|
||||
if record.levelno >= logging.ERROR and record.getMessage():
|
||||
message = record.getMessage()
|
||||
# Подавляем ошибки авторизации
|
||||
# Подавляем ошибки авторизации в консоли
|
||||
if any(
|
||||
phrase in message
|
||||
for phrase in [
|
||||
@@ -60,9 +61,9 @@ def filter(record: logging.LogRecord) -> bool:
|
||||
"decorated_function",
|
||||
]
|
||||
):
|
||||
return False # Не логируем ошибки авторизации и их трейсбеки
|
||||
return False # Не показываем в консоли ошибки авторизации
|
||||
|
||||
# Подавляем повторяющиеся Ariadne логи
|
||||
# Подавляем повторяющиеся Ariadne логи в консоли
|
||||
if record.name in ["ariadne", "graphql"]:
|
||||
message = record.getMessage()
|
||||
if any(
|
||||
@@ -79,6 +80,26 @@ def filter(record: logging.LogRecord) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def basic_filter(record: logging.LogRecord) -> bool:
|
||||
"""🔍 Базовый фильтр для всех логов - только добавляет метаданные"""
|
||||
# Define `package` attribute with the relative path.
|
||||
record.package = record.pathname[_leng_path + 1 :].replace(".py", "")
|
||||
record.emoji = (
|
||||
"🔍"
|
||||
if record.levelno == logging.DEBUG
|
||||
else "ℹ︎"
|
||||
if record.levelno == logging.INFO
|
||||
else "🚧"
|
||||
if record.levelno == logging.WARNING
|
||||
else "❌"
|
||||
if record.levelno == logging.ERROR
|
||||
else "🧨"
|
||||
if record.levelno == logging.CRITICAL
|
||||
else ""
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
# Define the color scheme
|
||||
color_scheme = {
|
||||
"DEBUG": "light_black",
|
||||
@@ -141,9 +162,10 @@ class MultilineColoredFormatter(colorlog.ColoredFormatter):
|
||||
# Create a MultilineColoredFormatter object for colorized logging
|
||||
formatter = MultilineColoredFormatter(fmt_string, **fmt_config)
|
||||
|
||||
# Create a stream handler for logging output
|
||||
# Create a stream handler for logging output (только для консоли)
|
||||
stream = logging.StreamHandler()
|
||||
stream.setFormatter(formatter)
|
||||
stream.addFilter(console_filter) # 🔍 Применяем фильтр только к консольному выводу
|
||||
|
||||
|
||||
def get_colorful_logger(name: str = "main") -> logging.Logger:
|
||||
@@ -151,7 +173,7 @@ def get_colorful_logger(name: str = "main") -> logging.Logger:
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.addHandler(stream)
|
||||
logger.addFilter(filter)
|
||||
logger.addFilter(basic_filter) # 🔍 Базовый фильтр без подавления
|
||||
|
||||
return logger
|
||||
|
||||
@@ -160,7 +182,7 @@ def get_colorful_logger(name: str = "main") -> logging.Logger:
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
root_logger.addHandler(stream)
|
||||
root_logger.addFilter(filter)
|
||||
root_logger.addFilter(basic_filter) # 🔍 Базовый фильтр - позволяет Sentry получать все логи
|
||||
|
||||
ignore_logs = ["_trace", "httpx", "_client", "atrace", "aiohttp", "_client", "ariadne", "graphql"]
|
||||
for lgr in ignore_logs:
|
||||
|
||||
@@ -2,27 +2,57 @@ import logging
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.ariadne import AriadneIntegration
|
||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
|
||||
from sentry_sdk.integrations.starlette import StarletteIntegration
|
||||
|
||||
from settings import GLITCHTIP_DSN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# Настройка логирования для отправки логов в Sentry
|
||||
sentry_logging_handler = sentry_sdk.integrations.logging.SentryHandler(level=logging.WARNING)
|
||||
logger.addHandler(sentry_logging_handler)
|
||||
logger.setLevel(logging.DEBUG) # Более подробное логирование
|
||||
|
||||
|
||||
def start_sentry() -> None:
|
||||
"""🔍 Инициализация Sentry/GlitchTip с дублированием логов"""
|
||||
try:
|
||||
logger.info("[utils.sentry] Sentry init started...")
|
||||
|
||||
# 🧾 Настройка LoggingIntegration для дублирования логов
|
||||
# level=logging.WARNING - отправляем в GlitchTip только WARNING и выше
|
||||
# event_level=logging.ERROR - создаем события только для ERROR и выше
|
||||
sentry_logging = LoggingIntegration(
|
||||
level=logging.WARNING, # Захватываем WARNING+ в breadcrumbs
|
||||
event_level=logging.ERROR, # Создаем события только для ERROR+
|
||||
)
|
||||
|
||||
# 🔍 Настраиваем фильтрацию через before_send callback
|
||||
def before_send(event, hint):
|
||||
"""Фильтрует события перед отправкой в Sentry"""
|
||||
if "logentry" in event and "message" in event["logentry"]:
|
||||
message = event["logentry"]["message"]
|
||||
auth_spam_phrases = [
|
||||
"Требуется авторизация",
|
||||
"AuthorizationError",
|
||||
"load_drafts",
|
||||
"GetUserDocuments",
|
||||
"GetDrafts",
|
||||
"decorated_function",
|
||||
]
|
||||
if any(phrase in message for phrase in auth_spam_phrases):
|
||||
return None # Блокируем отправку
|
||||
return event
|
||||
|
||||
sentry_sdk.init(
|
||||
dsn=GLITCHTIP_DSN,
|
||||
traces_sample_rate=1.0, # Захват 100% транзакций
|
||||
profiles_sample_rate=1.0, # Профилирование 100% транзакций
|
||||
enable_tracing=True,
|
||||
integrations=[StarletteIntegration(), AriadneIntegration(), SqlalchemyIntegration()],
|
||||
before_send=before_send, # 🔍 Фильтрация спама авторизации
|
||||
integrations=[
|
||||
sentry_logging, # 🔍 Дублирование логов
|
||||
StarletteIntegration(),
|
||||
AriadneIntegration(),
|
||||
SqlalchemyIntegration(),
|
||||
],
|
||||
send_default_pii=True, # Отправка информации о пользователе (PII)
|
||||
)
|
||||
logger.info("[utils.sentry] Sentry initialized successfully.")
|
||||
|
||||
Reference in New Issue
Block a user