cuatrolabs-scm-ms / docs /logger_expection.md
MukeshKapoor25's picture
feat: Implement Trade Returns Database Schema and E2E Testing
c0b58c9

Logger & Error Handling Implementation Summary

Quick Reference for New Modules

1. Logger Setup (One line per module)

from app.core.logging import get_logger

logger = get_logger(__name__)

Error Handling Patterns

Pattern 1: Simple Error with Context

try:
    result = await operation()
except SpecificException as e:
    logger.error(
        "Operation failed",
        extra={
            "operation": "operation_name",
            "error": str(e),
            "error_type": type(e).__name__
        },
        exc_info=True
    )
    raise HTTPException(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        detail="Operation failed"
    )

Pattern 2: Validation Error with Details

if not email or "@" not in email:
    logger.warning(
        "Validation failed",
        extra={
            "field": "email",
            "value_provided": bool(email),
            "validation": "email_format",
            "user_id": user_id
        }
    )
    raise HTTPException(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        detail="Invalid email format"
    )

Pattern 3: Authentication Error

if not token:
    logger.warning(
        "Authentication failed",
        extra={
            "reason": "missing_token",
            "endpoint": request.url.path,
            "client_ip": request.client.host if request.client else None
        }
    )
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Missing authentication token"
    )

Pattern 4: Permission Denied

if user.role not in required_roles:
    logger.warning(
        "Access denied",
        extra={
            "user_id": str(user.id),
            "user_role": user.role,
            "required_role": required_roles,
            "resource": request.url.path
        }
    )
    raise HTTPException(
        status_code=status.HTTP_403_FORBIDDEN,
        detail="Insufficient permissions"
    )

Pattern 5: Database Error

try:
    result = await collection.insert_one(data)
except PyMongoError as e:
    logger.error(
        "Database operation failed",
        extra={
            "operation": "insert_one",
            "collection": "collection_name",
            "error": str(e),
            "error_type": type(e).__name__
        },
        exc_info=True
    )
    raise HTTPException(
        status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
        detail="Database operation failed"
    )

Pattern 6: Success with Context

logger.info(
    "User login successful",
    extra={
        "user_id": user.id,
        "username": user.username,
        "method": "password",
        "ip_address": request.client.host
    }
)

Global Exception Handlers (in main.py)

from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
from pydantic import ValidationError
from jose import JWTError
from pymongo.errors import PyMongoError, ConnectionFailure, OperationFailure

# 1. Request Validation Errors
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    errors = [
        {
            "field": " -> ".join(str(loc) for loc in error["loc"]),
            "message": error["msg"],
            "type": error["type"]
        }
        for error in exc.errors()
    ]
    
    logger.warning(
        "Validation error",
        extra={
            "path": request.url.path,
            "method": request.method,
            "error_count": len(errors),
            "errors": errors
        }
    )
    
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content={
            "success": False,
            "error": "Validation Error",
            "errors": errors
        }
    )

# 2. JWT Errors
@app.exception_handler(JWTError)
async def jwt_exception_handler(request: Request, exc: JWTError):
    logger.warning(
        "JWT authentication failed",
        extra={
            "path": request.url.path,
            "error": str(exc),
            "client_ip": request.client.host if request.client else None
        }
    )
    
    return JSONResponse(
        status_code=status.HTTP_401_UNAUTHORIZED,
        content={
            "success": False,
            "error": "Unauthorized",
            "detail": "Invalid or expired token"
        }
    )

# 3. MongoDB Errors
@app.exception_handler(PyMongoError)
async def mongodb_exception_handler(request: Request, exc: PyMongoError):
    logger.error(
        "Database error",
        extra={
            "path": request.url.path,
            "error": str(exc),
            "error_type": type(exc).__name__
        },
        exc_info=True
    )
    
    if isinstance(exc, ConnectionFailure):
        status_code = status.HTTP_503_SERVICE_UNAVAILABLE
        detail = "Database connection failed"
    elif isinstance(exc, OperationFailure):
        status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
        detail = "Database operation failed"
    else:
        status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
        detail = "Database error occurred"
    
    return JSONResponse(
        status_code=status_code,
        content={
            "success": False,
            "error": "Database Error",
            "detail": detail
        }
    )

# 4. General Exception Handler
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
    logger.error(
        "Unhandled exception",
        extra={
            "method": request.method,
            "path": request.url.path,
            "error": str(exc),
            "error_type": type(exc).__name__,
            "client_ip": request.client.host if request.client else None
        },
        exc_info=True
    )
    
    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={
            "success": False,
            "error": "Internal Server Error",
            "detail": "An unexpected error occurred"
        }
    )

Context Fields Reference

Field Usage Examples
user_id User identifier "usr_123"
username Username "john_doe"
email Email address "john@example.com"
operation Action being performed "insert_user", "delete_account"
error Error message str(exception)
error_type Exception class "ValidationError", "ConnectionFailure"
status_code HTTP status 400, 401, 403, 500
path Request path "/api/users/login"
method HTTP method "POST", "GET", "PUT"
client_ip Client IP "192.168.1.1"
reason Why it failed "invalid_password", "missing_token"
field Field name (validation) "email", "password"
required_role Expected role "admin", "super_admin"
user_role User's actual role "user"
collection MongoDB collection "system_users"

Response Format Template

# Success Response
{
    "success": True,
    "data": {...},
    "message": "Operation completed"
}

# Error Response
{
    "success": False,
    "error": "Error Type",
    "detail": "Detailed message",
    "errors": [...]  # For validation errors
}

Implementation Checklist for New Module

  • Import logger: from app.core.logging import get_logger
  • Initialize: logger = get_logger(__name__)
  • Import exceptions: from fastapi import HTTPException, status
  • Wrap operations in try-except
  • Log warnings for user errors (401, 403, 422)
  • Log errors for server errors (500, 503)
  • Include error_type in extras
  • Use exc_info=True for exceptions
  • Include user_id for actions
  • Include operation name for tracking
  • Use consistent field names (see reference table)

Common HTTP Status Codes

Code Meaning Logger Level Use Case
400 Bad Request warning Invalid input data
401 Unauthorized warning Missing/invalid token
403 Forbidden warning Insufficient permissions
404 Not Found info Resource doesn't exist
422 Validation Error warning Invalid field values
500 Server Error error Unexpected exception
503 Service Unavailable error DB connection failed

Quick Copy-Paste Template

"""
Module description.
"""
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import JSONResponse

from app.core.logging import get_logger
from app.system_users.services.service import SystemUserService
from app.dependencies.auth import get_system_user_service

logger = get_logger(__name__)
router = APIRouter(prefix="/api/path", tags=["Category"])


@router.post("/endpoint")
async def endpoint_function(
    data: RequestModel,
    service: SystemUserService = Depends(get_system_user_service)
):
    """
    Endpoint description.
    
    Raises:
        HTTPException: 400 - Invalid input
        HTTPException: 401 - Unauthorized
        HTTPException: 500 - Server error
    """
    try:
        # Validation
        if not data.field:
            logger.warning(
                "Validation failed",
                extra={
                    "field": "field_name",
                    "reason": "empty_value"
                }
            )
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Field is required"
            )
        
        # Business logic
        result = await service.operation(data)
        
        # Success logging
        logger.info(
            "Operation successful",
            extra={
                "operation": "operation_name",
                "result_id": result.id
            }
        )
        
        return {
            "success": True,
            "data": result,
            "message": "Operation completed"
        }
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(
            "Operation failed",
            extra={
                "operation": "operation_name",
                "error": str(e),
                "error_type": type(e).__name__
            },
            exc_info=True
        )
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Operation failed"
        )