[0.9.25] - 2025-01-25
Some checks failed
Deploy on push / deploy (push) Failing after 24s

### 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:
2025-09-25 08:48:36 +03:00
parent 2ce8a5b957
commit 34738ae611
7 changed files with 765 additions and 650 deletions

View File

@@ -1,5 +1,22 @@
# Changelog
## [0.9.25] - 2025-01-25
### 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**: Улучшены сообщения об ошибках для более точной диагностики проблем
## [0.9.24] - 2025-09-24
### Fixed

View File

@@ -641,12 +641,15 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon
if not oauth_data:
logger.warning(f"🚨 OAuth state {state} not found or expired")
# Более информативная ошибка для пользователя
return JSONResponse({
"error": "oauth_state_expired",
"message": "OAuth session expired. Please try logging in again.",
"details": "The OAuth state was not found in Redis (expired or already used)",
"action": "restart_oauth_flow"
}, status_code=400)
return JSONResponse(
{
"error": "oauth_state_expired",
"message": "OAuth session expired. Please try logging in again.",
"details": "The OAuth state was not found in Redis (expired or already used)",
"action": "restart_oauth_flow",
},
status_code=400,
)
provider = oauth_data.get("provider")
if not provider:
@@ -713,7 +716,9 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon
logger.error(f"❌ Failed to get user profile for {provider} - empty profile returned")
return JSONResponse({"error": "Failed to get user profile"}, status_code=400)
logger.info(f"✅ Got user profile for {provider}: id={profile.get('id')}, email={profile.get('email')}, name={profile.get('name')}")
logger.info(
f"✅ Got user profile for {provider}: id={profile.get('id')}, email={profile.get('email')}, name={profile.get('name')}"
)
# 🔄 Step 3: Creating or updating user
logger.info(f"🔄 Step 3: Creating or updating user for {provider}...")

1293
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "publy-panel",
"version": "0.9.20",
"version": "0.9.25",
"type": "module",
"description": "Publy, a modern platform for collaborative text creation, offers a user-friendly interface for authors, editors, and readers, supporting real-time collaboration and structured feedback.",
"scripts": {
@@ -21,7 +21,7 @@
"@graphql-codegen/typescript-operations": "^5.0.0",
"@graphql-codegen/typescript-resolvers": "^5.0.0",
"@solidjs/router": "^0.15.3",
"@types/node": "^24.5.0",
"@types/node": "^24.5.2",
"@types/prismjs": "^1.26.5",
"graphql": "^16.11.0",
"graphql-tag": "^2.12.6",
@@ -30,7 +30,7 @@
"solid-js": "^1.9.9",
"terser": "^5.44.0",
"typescript": "^5.9.2",
"vite": "^7.1.5",
"vite": "^7.1.7",
"vite-plugin-solid": "^2.11.7"
},
"overrides": {

View File

@@ -1,6 +1,6 @@
[project]
name = "discours-core"
version = "0.9.20"
version = "0.9.25"
description = "Core backend for Discours.io platform"
authors = [
{name = "Tony Rewin", email = "tonyrewin@yandex.ru"}

View File

@@ -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:

View File

@@ -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.")