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