Continuing my multi-service mock API journey on AWS Free Tier, I tackled logging, error handling, and authentication to enhance reliability for product managers and geeks alike. With the API live on EC2 (65.2.1XX.XX:8000), direct HTTP access posed challenges for monitoring and security, driving me to refine the setup.

Logging was critical for tracking ~10 daily calls. Initially, I faced No such file or directory errors configuring CloudWatch Logs via CLI, but the AWS Management Console saved the day. Using Systems Manager Run Command, I installed the CloudWatch agent, configuring it to collect /var/lib/docker/containers/*/*.log into multi-api-logs:

{
  "agent": {
    "metrics_collection_interval": 60,
    "logfile": "/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log"
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/lib/docker/containers/*/*.log",
            "log_group_name": "multi-api-logs",
            "log_stream_name": "{instance_id}"
          }
        ]
      }
    }
  }
}

A diagram of EC2 → CloudWatch logging illustrates this:

EC2 Logging to CloudWatch Source: AWS CloudWatch Documentation

I hit “No registered managed instances” in SSM due to IAM role issues (fixed with AmazonSSMManagedInstanceCore) and IMDSv2 misconfiguration, resolved via the console. Logs now stream to CloudWatch, staying within Free Tier’s 1 GB/month limit.

Next, I enhanced error handling and authentication. Initially using Client-ID and X-IBM-Key headers, I switched to a single X-API-Key for simplicity, adding internal status codes (101–109) and HTTP codes (200, 400, 401, etc.):

@app.post("/pan-to-gst", response_model=SimpleResponse)
async def pan_to_gst(request: PanRequest):
    pan = request.pan_number.strip().upper()
    if not request.headers.get("X-API-Key") == "my-secret-key-123":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail={"status": "error", "internal_status_code": 102, "message": "Invalid API key"}
        )
    if not re.match(PAN_PATTERN, pan):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail={"status": "error", "internal_status_code": 102, "message": "Invalid PAN format"}
        )
    value = PAN_TO_GST.get(pan)
    if not value:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail={"status": "error", "internal_status_code": 103, "message": "No records found"}
        )
    return {"status": "success", "internal_status_code": 101, "value": value}

This covered scenarios like invalid inputs (102/400), missing data (103/404), and simulated failures (108/500). The code simulates the error codes as per the original API’s; for realistic product testing. Also, integrated is a neat swagger powered documentation hosted on the same link.

This optimization makes the API production-ready for testing, balancing technical depth with testing needs. Check AWS docs for more!

References: