From 409966a478f180b643c943056d2599ca5d0a2842 Mon Sep 17 00:00:00 2001 From: I Luk Kim Date: Tue, 14 Apr 2026 10:30:46 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20error=5Flogs.path=20VARCHAR(500)=20?= =?UTF-8?q?=E2=86=92=20TEXT=20=E2=80=94=20=EC=84=9C=EB=B2=84=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EB=B6=88=EA=B0=80=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 멀티 티커 요청 URL이 500자를 초과하면서 error_logs INSERT가 StringDataRightTruncationError로 실패, 미들웨어에서 매 에러 응답마다 DB rollback 대기가 발생해 서버가 응답 불가 수준으로 느려졌음 (2562회 반복). - error_logs.path: String(500) → Text (model + migration j1b2c3d4e5f6) - request_logs.path: String(500) → Text (model + migration i0a1b2c3d4e5) - docker-compose: alembic/ + alembic.ini 볼륨 마운트 추가 (이전에는 컨테이너 내부에만 있어 마이그레이션 적용 불가) - middleware: 요청마다 찍히던 logger.info → logger.debug 로 변경 Co-Authored-By: Claude Sonnet 4.6 --- .../i0a1b2c3d4e5_fix_request_log_path_text.py | 37 ++++++++++++++++++ .../j1b2c3d4e5f6_fix_error_log_path_text.py | 39 +++++++++++++++++++ app/middleware/error_logger.py | 22 ++++------- app/models/error_log.py | 2 +- app/models/request_log.py | 2 +- docker-compose.yml | 2 + 6 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 alembic/versions/i0a1b2c3d4e5_fix_request_log_path_text.py create mode 100644 alembic/versions/j1b2c3d4e5f6_fix_error_log_path_text.py diff --git a/alembic/versions/i0a1b2c3d4e5_fix_request_log_path_text.py b/alembic/versions/i0a1b2c3d4e5_fix_request_log_path_text.py new file mode 100644 index 0000000..0abe073 --- /dev/null +++ b/alembic/versions/i0a1b2c3d4e5_fix_request_log_path_text.py @@ -0,0 +1,37 @@ +"""fix request_log path column to TEXT + +Revision ID: i0a1b2c3d4e5 +Revises: h9b0c1d2e3f4 +Create Date: 2026-04-13 +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +revision: str = "i0a1b2c3d4e5" +down_revision: Union[str, Sequence[str], None] = "h9b0c1d2e3f4" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # path stores the full URL (path + query string) and can exceed 500 chars + # for multi-ticker requests — widen to TEXT + op.alter_column( + "request_logs", + "path", + existing_type=sa.String(500), + type_=sa.Text(), + existing_nullable=True, + ) + + +def downgrade() -> None: + op.alter_column( + "request_logs", + "path", + existing_type=sa.Text(), + type_=sa.String(500), + existing_nullable=True, + ) diff --git a/alembic/versions/j1b2c3d4e5f6_fix_error_log_path_text.py b/alembic/versions/j1b2c3d4e5f6_fix_error_log_path_text.py new file mode 100644 index 0000000..5c07201 --- /dev/null +++ b/alembic/versions/j1b2c3d4e5f6_fix_error_log_path_text.py @@ -0,0 +1,39 @@ +"""fix error_log path column to TEXT + +Revision ID: j1b2c3d4e5f6 +Revises: i0a1b2c3d4e5 +Create Date: 2026-04-14 + +error_logs.path was VARCHAR(500), causing StringDataRightTruncationError +for multi-ticker requests whose URLs exceed 500 chars. This blocked every +error log INSERT and added rollback latency to each error response. +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +revision: str = "j1b2c3d4e5f6" +down_revision: Union[str, Sequence[str], None] = "i0a1b2c3d4e5" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.alter_column( + "error_logs", + "path", + existing_type=sa.String(500), + type_=sa.Text(), + existing_nullable=True, + ) + + +def downgrade() -> None: + op.alter_column( + "error_logs", + "path", + existing_type=sa.Text(), + type_=sa.String(500), + existing_nullable=True, + ) diff --git a/app/middleware/error_logger.py b/app/middleware/error_logger.py index de61a24..93a8f46 100644 --- a/app/middleware/error_logger.py +++ b/app/middleware/error_logger.py @@ -107,7 +107,7 @@ class ErrorLoggingMiddleware(BaseHTTPMiddleware): # Store request details for potential error logging request_info = self._extract_request_info_minimal(request) - logger.info(f"Processing request {request_id}: {request.method} {request.url.path}") + logger.debug(f"Processing request {request_id}: {request.method} {request.url.path}") try: # Process the request @@ -122,7 +122,7 @@ class ErrorLoggingMiddleware(BaseHTTPMiddleware): # Check if response indicates an error (4xx or 5xx) if response.status_code >= 400: - logger.info(f"Error response detected: {response.status_code} for request {request_id}") + logger.debug(f"Error response detected: {response.status_code} for request {request_id}") # Full extraction for error logging (body + headers) request_info = await self._extract_request_info(request) @@ -150,7 +150,7 @@ class ErrorLoggingMiddleware(BaseHTTPMiddleware): error_detail = {"raw_response": str(response_body)} # Log the error with response body - logger.info(f"Logging error for request {request_id}") + logger.debug(f"Logging error for request {request_id}") await self._log_error( request_id=request_id, request_info=request_info, @@ -271,23 +271,19 @@ class ErrorLoggingMiddleware(BaseHTTPMiddleware): ): """Log error to database""" - logger.info(f"_log_error called for request {request_id}, status {status_code}") - # Skip logging errors for log deletion endpoints to avoid logging the deletion of logs endpoint = request_info["endpoint"] method = request_info["method"] - + # Don't log errors for DELETE requests to log management endpoints - if (method == "DELETE" and - (endpoint.startswith("/api/v1/admin/requests/logs") or + if (method == "DELETE" and + (endpoint.startswith("/api/v1/admin/requests/logs") or endpoint.startswith("/api/v1/admin/errors/logs"))): - logger.info(f"Skipping error log for log deletion endpoint: {method} {endpoint}") return - + try: # Get database session from app.core.database import AsyncSessionLocal - logger.info(f"Creating database session for request {request_id}") async with AsyncSessionLocal() as db: # Extract error information from detail if available if error_detail and isinstance(error_detail, dict): @@ -328,10 +324,8 @@ class ErrorLoggingMiddleware(BaseHTTPMiddleware): ) db.add(error_log) - logger.info(f"Error log added to session for request {request_id}") await db.commit() - logger.info(f"Error log committed to database for request {request_id}") - + logger.error( f"Error logged - Request ID: {request_id}, " f"Endpoint: {request_info['endpoint']}, " diff --git a/app/models/error_log.py b/app/models/error_log.py index fbd1e90..1e2d31e 100644 --- a/app/models/error_log.py +++ b/app/models/error_log.py @@ -18,7 +18,7 @@ class ErrorLog(Base): request_id = Column(String(50), index=True) endpoint = Column(String(200), index=True) method = Column(String(10)) - path = Column(String(500)) + path = Column(Text) query_params = Column(JSON, nullable=True) request_body = Column(JSON, nullable=True) headers = Column(JSON, nullable=True) diff --git a/app/models/request_log.py b/app/models/request_log.py index c759d89..4444479 100644 --- a/app/models/request_log.py +++ b/app/models/request_log.py @@ -18,7 +18,7 @@ class RequestLog(Base): request_id = Column(String(50), index=True) endpoint = Column(String(200), index=True) method = Column(String(10)) - path = Column(String(500)) + path = Column(Text) query_params = Column(JSON, nullable=True) request_body = Column(JSON, nullable=True) headers = Column(JSON, nullable=True) diff --git a/docker-compose.yml b/docker-compose.yml index 77dbb7c..94d0075 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,6 +59,8 @@ services: - redis volumes: - ./app:/app/app # Mount app directory for development + - ./alembic:/app/alembic # Mount alembic for live migration access + - ./alembic.ini:/app/alembic.ini - ./stock_oracle_analyzer.py:/app/stock_oracle_analyzer.py - ./API_DOCUMENTATION.md:/app/API_DOCUMENTATION.md # API documentation - ./yfinance_plus:/app/yfinance_plus # Mount yfinance_plus for development