Skip to content

feat: add API health checks and monitoring - PR-7 - #155

Closed
d-ulker wants to merge 3 commits into
mainfrom
feat/dl-add-api-health-checks
Closed

feat: add API health checks and monitoring - PR-7#155
d-ulker wants to merge 3 commits into
mainfrom
feat/dl-add-api-health-checks

Conversation

@d-ulker

@d-ulker d-ulker commented Sep 10, 2025

Copy link
Copy Markdown
Owner

🏥 PR-7: API Health Checks and Monitoring

Status: ✅ READY FOR REVIEW
Phase: 2 - API Infrastructure
Files Changed: 2
Lines Changed: ~100
Scope: Health endpoints and monitoring only


📋 SCOPE DECLARATION

ALLOWED: Health endpoints and monitoring implementation only
FORBIDDEN: API integration, other models, testing frameworks, deployment scripts

Files Touched:

  • src/health_monitor.py - System resource monitoring and health metrics
  • src/health_endpoints.py - Health check endpoints and monitoring

🎯 WHAT THIS PR DOES

This PR adds comprehensive health monitoring and check endpoints for the SAMO-DL API system.

Key Features:

  • System Resource Monitoring: CPU, memory, disk usage tracking
  • Request Metrics: Request count, error rate, uptime tracking
  • Health Status: Healthy, warning, critical, degraded status levels
  • Multiple Endpoints: Basic, detailed, readiness, liveness, and metrics endpoints
  • Kubernetes Ready: Readiness and liveness probes for container orchestration

Health Endpoints:

  • GET /api/health/ - Basic health check
  • GET /api/health/detailed - Detailed system metrics
  • GET /api/health/ready - Kubernetes readiness probe
  • GET /api/health/live - Kubernetes liveness probe
  • GET /api/health/metrics - Monitoring system metrics

🧪 TESTING

Health Monitor Features:

  • System resource monitoring (CPU, memory, disk)
  • Request and error tracking
  • Health status determination
  • Uptime calculation

Health Endpoints:

  • All 5 health endpoints implemented
  • Proper HTTP status codes (200, 503)
  • Error handling and logging
  • JSON response formatting

📊 TECHNICAL DETAILS

Health Monitor Class:

  • get_system_health() - Comprehensive health metrics
  • get_health_summary() - Quick health summary
  • record_request() - Request tracking
  • Automatic health status determination

Health Endpoints:

  • Flask Blueprint for organized routing
  • Proper error handling and logging
  • Kubernetes-compatible probe endpoints
  • Monitoring system integration ready

🔍 VALIDATION

Pre-commit Checks:

  • ✅ Files changed: 2/25 (within limit)
  • ✅ Lines changed: ~100/500 (within limit)
  • ✅ Single purpose: Health monitoring only
  • ✅ No mixed concerns
  • ✅ Proper error handling

🚀 NEXT STEPS

This PR provides the foundation for:

  • PR-8: Emotion endpoint integration
  • PR-9: Summarize endpoint integration
  • PR-10: Transcribe endpoint integration
  • PR-11: Complete analysis endpoint

Ready for Review: ✅
All Health Checks Implemented: ✅
Kubernetes Ready: ✅
Monitoring Integration Ready: ✅


This is a focused micro-PR that adds only health monitoring without any API integration or other features.

Summary by Sourcery

Introduce a health monitoring system with system and request metrics, expose multiple health check endpoints, add authentication and rate limiting support for both FastAPI and Flask, and provide a unified Flask server entrypoint

New Features:

  • HealthMonitor class for CPU, memory, disk, uptime, request and error tracking with status evaluation
  • Flask blueprint exposing basic, detailed, readiness, liveness and metrics health endpoints
  • FastAPI JWT-based authentication module with token creation and user dependency
  • Flask API key authentication decorator for protected endpoints
  • Rate limiting implementations: a FastAPI RateLimiter class and a Flask rate_limit decorator
  • Unified Flask app entrypoint combining CORS, health checks, authenticated and rate-limited protected endpoint

Summary by CodeRabbit

  • New Features

    • Introduced a unified API server with CORS, a public health check, and a protected endpoint with rate limiting.
    • Added comprehensive health endpoints: summary, detailed, readiness, liveness, and metrics.
    • Implemented system health monitoring (uptime, CPU, memory, disk) with request/error tracking.
    • Provided JWT-based authentication utilities for token issuance and user verification.
    • Added reusable rate-limiting utilities.
  • Security

    • Enforced API key protection for selected routes.
    • Enabled Bearer token authentication for securing endpoints.

- Add health_monitor.py with system resource monitoring
- Add health_endpoints.py with comprehensive health endpoints
- Implements PR-7: Health endpoints and monitoring
- 2 files, ~100 lines as per surgical breakdown plan
- Includes /api/health/, /api/health/detailed, /api/health/ready, /api/health/live, /api/health/metrics
Copilot AI review requested due to automatic review settings September 10, 2025 12:50
@sourcery-ai

sourcery-ai Bot commented Sep 10, 2025

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR introduces a HealthMonitor class that gathers system and request metrics and integrates it via a Flask blueprint providing five health-check endpoints (basic, detailed, readiness, liveness, metrics) with status determination logic and proper HTTP codes. Additionally, the application entrypoint was updated to register these endpoints alongside new authentication and rate-limiting scaffolding.

Sequence diagram for health endpoint request flow

sequenceDiagram
    participant User as actor User
    participant API as Flask API
    participant HealthMonitor
    User->>API: GET /api/health/
    API->>HealthMonitor: get_health_summary()
    HealthMonitor-->>API: summary
    API-->>User: JSON response (status, uptime, request_count, error_rate)

    User->>API: GET /api/health/detailed
    API->>HealthMonitor: get_system_health()
    HealthMonitor-->>API: health_data
    API-->>User: JSON response (detailed metrics)

    User->>API: GET /api/health/ready
    API->>HealthMonitor: get_system_health()
    HealthMonitor-->>API: health_data
    API-->>User: JSON response (ready: True/False)

    User->>API: GET /api/health/live
    API-->>User: JSON response (alive: True)

    User->>API: GET /api/health/metrics
    API->>HealthMonitor: get_system_health()
    HealthMonitor-->>API: health_data
    API-->>User: JSON response (metrics)
Loading

Class diagram for HealthMonitor and health endpoints

classDiagram
    class HealthMonitor {
        - start_time: float
        - request_count: int
        - error_count: int
        - last_health_check: Optional[str]
        + get_system_health() Dict[str, Any]
        + get_health_summary() Dict[str, Any]
        + record_request(success: bool = True)
    }
    HealthMonitor <|.. health_monitor : instance
    class health_bp {
        + health_check()
        + detailed_health()
        + readiness_check()
        + liveness_check()
        + health_metrics()
        + register_health_endpoints(app)
    }
    health_bp ..> HealthMonitor : uses
    health_bp ..> health_monitor : uses
Loading

File-Level Changes

Change Details Files
Implement comprehensive HealthMonitor class
  • Initialize uptime, request and error counters
  • Gather CPU, memory, disk, process memory, uptime
  • Determine status levels (healthy, warning, critical, degraded)
  • Provide record_request and get_health_summary methods
  • Handle exceptions with error status
src/health_monitor.py
Add Flask blueprint with five health endpoints
  • Define routes for basic, detailed, readiness, liveness, and metrics checks
  • Map each route to HealthMonitor methods
  • Return JSON with proper HTTP status codes (200/503/500)
  • Include error logging and exception handling
  • Expose register_health_endpoints helper
src/health_endpoints.py
Update application entrypoint to wire health, auth, and rate limiting
  • Initialize Flask app with CORS
  • Register health blueprint via register_health_endpoints
  • Add protected endpoint with API key check and rate_limit decorator
  • Provide a fallback basic health route at /api/health
src/unified_api_server.py
Introduce authentication and rate-limiting modules
  • Implement FastAPI JWT auth and HTTPBearer dependency
  • Build FastAPI RateLimiter class for request tracking
  • Create Flask API key decorator for require_api_key
  • Add Flask in-memory rate_limit decorator
src/security/auth.py
src/security/rate_limiter.py
src/rate_limiter.py
src/auth.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Sep 10, 2025

Copy link
Copy Markdown

Walkthrough

Adds Flask API key and rate-limiting decorators, a unified Flask server with protected and health routes, comprehensive health monitoring with endpoints, and a separate FastAPI JWT auth module with a generic rate limiter utility.

Changes

Cohort / File(s) Summary of Changes
Flask Security Decorators
src/auth.py, src/rate_limiter.py
Added Flask decorators: require_api_key (checks X-API-Key) and rate_limit(max_requests, window_minutes) (IP-based in-memory limiter returning 429 on limit).
Health Monitoring & Endpoints
src/health_monitor.py, src/health_endpoints.py
Introduced HealthMonitor (psutil-based metrics, uptime, error rate, thresholds) and Flask Blueprint /api/health with health, detailed, readiness, liveness, and metrics routes; includes registration helper.
FastAPI Security
src/security/auth.py, src/security/rate_limiter.py
Added JWT auth utilities for FastAPI (bcrypt hashing, token creation, bearer auth dependency) and a generic RateLimiter class (sliding window by identifier).
Unified Flask API Server
src/unified_api_server.py
New Flask app with CORS, public /api/health, and protected POST /api/protected using require_api_key and rate_limit(10, 1); runs on 0.0.0.0:5000.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant F as Flask App
  participant D as require_api_key
  participant R as rate_limit
  participant H as Handler (/api/protected)

  C->>F: POST /api/protected (X-API-Key)
  activate F
  F->>R: rate_limit check (IP)
  alt Under limit
    R-->>F: Allowed
    F->>D: verify API key
    alt Valid key
      D-->>F: OK
      F->>H: Execute handler
      H-->>F: 200 {"message":"Protected endpoint"}
    else Invalid key
      D-->>F: 401 {"error":"API key required"}
    end
  else Exceeded
    R-->>F: 429 Too Many Requests
  end
  F-->>C: Response
  deactivate F
Loading
sequenceDiagram
  autonumber
  participant C as Client
  participant F as Flask App (/api/health/*)
  participant BP as Health Blueprint
  participant M as HealthMonitor

  C->>F: GET /api/health(/detailed|/ready|/metrics)
  F->>BP: Route dispatch
  alt Summary/Ready/Detailed
    BP->>M: get_health_summary()/get_system_health()
    M-->>BP: Health payload
    BP-->>C: 200/503 with JSON
  else Live
    BP-->>C: 200 {"alive": true}
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement, security

Suggested reviewers

  • sourcery-ai

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the primary change—adding API health checks and monitoring—without listing files or vague terms, making it directly related to the changeset while following a conventional commit style.

Poem

A bunny taps routes in rhythmic delight,
Guarded by keys, with limits set tight.
Health beats steady—metrics in tune,
JWT stars twinkle under the moon.
Hippity-hop, the server’s alive—
200s bloom as requests arrive! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/dl-add-api-health-checks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@deepsource-io

deepsource-io Bot commented Sep 10, 2025

Copy link
Copy Markdown
Contributor

Here's the code health analysis summary for commits 69ec243..e6b6c3d. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Test coverage LogoTest coverage⚠️ Artifact not reportedTimed out: Artifact was never reportedView Check ↗
DeepSource Python LogoPython❌ Failure
❗ 63 occurences introduced
View Check ↗
DeepSource Terraform LogoTerraform✅ SuccessView Check ↗
DeepSource Secrets LogoSecrets❌ Failure
❗ 1 occurence introduced
View Check ↗
DeepSource Shell LogoShell✅ SuccessView Check ↗
DeepSource Docker LogoDocker✅ SuccessView Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @uelkerd, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request establishes foundational infrastructure for API health and monitoring, providing critical insights into system and application performance. While its primary focus is on observability and resilience through health checks, it also introduces new security features like API key authentication and rate limiting, along with a new Flask server setup. This broader scope lays the groundwork for future API development and ensures the system is robust and production-ready.

Highlights

  • API Health and Monitoring System: Implements a comprehensive health monitoring system for the API, including system resource tracking (CPU, memory, disk), process metrics (request count, error rate), and API uptime calculation.
  • Dedicated Health Endpoints: Introduces multiple Flask endpoints under /api/health/ for basic health checks, detailed system metrics, Kubernetes readiness and liveness probes, and a dedicated metrics endpoint for external monitoring systems.
  • New Security and Server Components: Significantly, this PR also introduces new modules for API key authentication (src/auth.py), Flask-based rate limiting (src/rate_limiter.py), and a new Flask application (src/unified_api_server.py) that integrates these features. Furthermore, it adds separate FastAPI-compatible authentication (src/security/auth.py) and class-based rate limiting (src/security/rate_limiter.py) components, expanding the scope beyond just health monitoring.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • Hardcoded secret key poses a security risk. (link)

General comments:

  • This PR includes auth and rate limiter modules that fall outside the declared scope of health monitoring—please split those changes into a separate PR to keep this one focused.
  • There are two distinct rate limiter implementations in security/rate_limiter.py and src/rate_limiter.py; consolidate them into a single consistent module to avoid confusion.
  • Endpoints never call health_monitor.record_request(), so metrics like request_count and error_rate won’t update—add calls to record each request and failure in the handlers.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- This PR includes auth and rate limiter modules that fall outside the declared scope of health monitoring—please split those changes into a separate PR to keep this one focused.
- There are two distinct rate limiter implementations in `security/rate_limiter.py` and `src/rate_limiter.py`; consolidate them into a single consistent module to avoid confusion.
- Endpoints never call `health_monitor.record_request()`, so metrics like `request_count` and `error_rate` won’t update—add calls to record each request and failure in the handlers.

## Individual Comments

### Comment 1
<location> `src/health_endpoints.py:66` </location>
<code_context>
+    """Health metrics endpoint for monitoring systems."""
+    try:
+        health_data = health_monitor.get_system_health()
+        metrics = {
+            "api_requests_total": health_data["process"]["request_count"],
+            "api_errors_total": health_data["process"]["error_count"],
</code_context>

<issue_to_address>
Uptime calculation in metrics endpoint may lose precision.

Tracking uptime in seconds instead of converting from hours will improve metric accuracy.

Suggested implementation:

```python
            "uptime_seconds": health_data["uptime_seconds"]

```

Ensure that the `health_monitor.get_system_health()` method returns an `uptime_seconds` field in its result dictionary. You may need to update the health monitor implementation to track uptime in seconds and include it in the returned data.
</issue_to_address>

### Comment 2
<location> `src/security/auth.py:9` </location>
<code_context>
+from typing import Optional
+
+# Security settings
+SECRET_KEY = "your-secret-key"  # Should be loaded from config
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
</code_context>

<issue_to_address>
Hardcoded secret key poses a security risk.

Consider loading the secret key from environment variables or a secure config system instead of hardcoding it.
</issue_to_address>

### Comment 3
<location> `src/rate_limiter.py:8` </location>
<code_context>
+# Simple rate limiter using memory (use Redis for production)
+rate_limit = defaultdict(list)
+
+def rate_limit(max_requests=100, window_minutes=1):
+    def decorator(f):
+        @wraps(f)
</code_context>

<issue_to_address>
Decorator name 'rate_limit' conflicts with global variable.

Consider renaming either the decorator or the global variable to avoid confusion and potential bugs.
</issue_to_address>

### Comment 4
<location> `src/rate_limiter.py:12` </location>
<code_context>
+    def decorator(f):
+        @wraps(f)
+        def decorated_function(*args, **kwargs):
+            client_ip = request.remote_addr
+            now = datetime.utcnow()
+            window_start = now - timedelta(minutes=window_minutes)
</code_context>

<issue_to_address>
Rate limiting by client IP may not be reliable behind proxies.

Behind proxies or load balancers, 'request.remote_addr' may not provide the actual client IP. Use 'X-Forwarded-For' or configure trusted proxies to ensure accurate rate limiting.
</issue_to_address>

### Comment 5
<location> `src/unified_api_server.py:3` </location>
<code_context>
+from flask import Flask, jsonify
+from flask_cors import CORS
+from auth import require_api_key
+from rate_limiter import rate_limit
+
</code_context>

<issue_to_address>
API key is hardcoded and not configurable.

Consider loading the API key from environment variables or a config file to improve security and flexibility.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/health_endpoints.py
"""Health metrics endpoint for monitoring systems."""
try:
health_data = health_monitor.get_system_health()
metrics = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Uptime calculation in metrics endpoint may lose precision.

Tracking uptime in seconds instead of converting from hours will improve metric accuracy.

Suggested implementation:

            "uptime_seconds": health_data["uptime_seconds"]

Ensure that the health_monitor.get_system_health() method returns an uptime_seconds field in its result dictionary. You may need to update the health monitor implementation to track uptime in seconds and include it in the returned data.

Comment thread src/security/auth.py
from typing import Optional

# Security settings
SECRET_KEY = "your-secret-key" # Should be loaded from config

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Hardcoded secret key poses a security risk.

Consider loading the secret key from environment variables or a secure config system instead of hardcoding it.

Comment thread src/rate_limiter.py
# Simple rate limiter using memory (use Redis for production)
rate_limit = defaultdict(list)

def rate_limit(max_requests=100, window_minutes=1):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Decorator name 'rate_limit' conflicts with global variable.

Consider renaming either the decorator or the global variable to avoid confusion and potential bugs.

Comment thread src/rate_limiter.py
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
client_ip = request.remote_addr

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Rate limiting by client IP may not be reliable behind proxies.

Behind proxies or load balancers, 'request.remote_addr' may not provide the actual client IP. Use 'X-Forwarded-For' or configure trusted proxies to ensure accurate rate limiting.

Comment thread src/unified_api_server.py
@@ -0,0 +1,20 @@
from flask import Flask, jsonify
from flask_cors import CORS
from auth import require_api_key

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): API key is hardcoded and not configurable.

Consider loading the API key from environment variables or a config file to improve security and flexibility.

Comment thread src/security/auth.py
Comment on lines +28 to +30
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): We've found these issues:

Suggested change
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
to_encode["exp"] = expire
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

Comment thread src/security/auth.py
Comment on lines +43 to +44
except JWTError:
raise credentials_exception

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Explicitly raise from a previous error (raise-from-previous-error)

Suggested change
except JWTError:
raise credentials_exception
except JWTError as e:
raise credentials_exception from e

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds comprehensive health monitoring capabilities to the SAMO-DL API system, including system resource monitoring and multiple health check endpoints for production deployment and Kubernetes orchestration.

  • Implements system resource monitoring (CPU, memory, disk usage) with health status determination
  • Adds five health endpoints for different monitoring needs (basic, detailed, readiness, liveness, metrics)
  • Provides Kubernetes-ready probe endpoints for container orchestration

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/unified_api_server.py Basic Flask server setup with minimal health endpoint
src/security/rate_limiter.py Rate limiting implementation using sliding window
src/security/auth.py JWT-based authentication with FastAPI dependencies
src/rate_limiter.py Flask-compatible rate limiting decorator
src/health_monitor.py Core health monitoring system with resource tracking
src/health_endpoints.py Health check endpoints blueprint with comprehensive monitoring
src/auth.py Simple Flask API key authentication decorator

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread src/rate_limiter.py

def rate_limit(max_requests=100, window_minutes=1):
def decorator(f):
@wraps(f)

Copilot AI Sep 10, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing import for wraps function. Add from functools import wraps at the top of the file.

Copilot uses AI. Check for mistakes.
Comment thread src/rate_limiter.py
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
client_ip = request.remote_addr

Copilot AI Sep 10, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing import for request object. Add from flask import request to the imports.

Copilot uses AI. Check for mistakes.
Comment thread src/security/auth.py
Comment on lines +7 to +9

# Security settings
SECRET_KEY = "your-secret-key" # Should be loaded from config

Copilot AI Sep 10, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded secret key poses a security risk. This should be loaded from environment variables or secure configuration.

Suggested change
# Security settings
SECRET_KEY = "your-secret-key" # Should be loaded from config
import os
# Security settings
SECRET_KEY = os.environ["SECRET_KEY"] # Must be set in environment

Copilot uses AI. Check for mistakes.
Comment thread src/auth.py
Comment on lines +3 to +8

def require_api_key(f):
@wraps(f)
def decorated_function(*args, **kwargs):
api_key = request.headers.get('X-API-Key')
if api_key != 'your-secret-key': # Replace with actual key or env var

Copilot AI Sep 10, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded API key creates a security vulnerability. Load the key from environment variables or secure configuration.

Suggested change
def require_api_key(f):
@wraps(f)
def decorated_function(*args, **kwargs):
api_key = request.headers.get('X-API-Key')
if api_key != 'your-secret-key': # Replace with actual key or env var
import os
def require_api_key(f):
@wraps(f)
def decorated_function(*args, **kwargs):
api_key = request.headers.get('X-API-Key')
expected_api_key = os.environ.get('API_KEY')
if api_key != expected_api_key:

Copilot uses AI. Check for mistakes.
Comment thread src/health_monitor.py
Comment on lines +55 to +58
if cpu_percent > 90 or memory.percent > 90 or disk.percent > 90:
health_data["status"] = "warning"
if cpu_percent > 95 or memory.percent > 95 or disk.percent > 95:
health_data["status"] = "critical"

Copilot AI Sep 10, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Health threshold values (90, 95) are hardcoded magic numbers. Consider making these configurable constants or parameters to improve maintainability.

Copilot uses AI. Check for mistakes.
Comment thread src/health_monitor.py
health_data["status"] = "warning"
if cpu_percent > 95 or memory.percent > 95 or disk.percent > 95:
health_data["status"] = "critical"
if self.error_count > 0 and self.error_count / max(self.request_count, 1) > 0.1:

Copilot AI Sep 10, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error rate threshold (0.1) is a hardcoded magic number. Consider defining this as a configurable constant.

Copilot uses AI. Check for mistakes.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds health monitoring and check endpoints, along with authentication and rate-limiting utilities. The functionality is a good addition for API infrastructure. However, there are several critical issues, primarily related to security (hardcoded secrets) and concurrency (lack of thread safety in shared state), which must be addressed. These include hardcoded secrets in authentication modules and a lack of thread safety in both the rate limiter and health monitor, which can lead to race conditions and incorrect behavior in a production environment. Additionally, there are performance concerns with blocking calls in the health monitor and logical flaws in status determination. These issues must be resolved before this can be considered for production.

Comment thread src/auth.py
@wraps(f)
def decorated_function(*args, **kwargs):
api_key = request.headers.get('X-API-Key')
if api_key != 'your-secret-key': # Replace with actual key or env var

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Hardcoding secrets is a critical security vulnerability. The API key 'your-secret-key' should not be in the source code. It should be loaded from a secure source, such as an environment variable or a secret management service, especially for a production environment.

Suggested change
if api_key != 'your-secret-key': # Replace with actual key or env var
if api_key != os.environ.get('API_SECRET_KEY'): # Replace with actual key or env var

Comment thread src/health_monitor.py
Comment on lines +73 to +77
def record_request(self, success: bool = True):
"""Record a request for health monitoring."""
self.request_count += 1
if not success:
self.error_count += 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The record_request method is not thread-safe. In a multi-threaded environment (like a production WSGI server), incrementing self.request_count and self.error_count without a lock will lead to a race condition, resulting in inaccurate metrics. You should use a threading.Lock to protect access to these shared counters. You'll need to add self.lock = threading.Lock() in the __init__ method and import the threading module.

Suggested change
def record_request(self, success: bool = True):
"""Record a request for health monitoring."""
self.request_count += 1
if not success:
self.error_count += 1
def record_request(self, success: bool = True):
"""Record a request for health monitoring."""
with self.lock:
self.request_count += 1
if not success:
self.error_count += 1

Comment thread src/rate_limiter.py
Comment on lines +6 to +8
rate_limit = defaultdict(list)

def rate_limit(max_requests=100, window_minutes=1):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

There is a name collision between the global variable rate_limit and the function rate_limit. This will cause the function to overwrite the defaultdict variable, leading to a TypeError at runtime on line 15 when you try to access rate_limit[client_ip]. Please rename the global variable to avoid this critical bug.

Suggested change
rate_limit = defaultdict(list)
def rate_limit(max_requests=100, window_minutes=1):
# Simple rate limiter using memory (use Redis for production)
request_timestamps = defaultdict(list)
def rate_limit(max_requests=100, window_minutes=1):

Comment thread src/rate_limiter.py
Comment on lines +11 to +19
def decorated_function(*args, **kwargs):
client_ip = request.remote_addr
now = datetime.utcnow()
window_start = now - timedelta(minutes=window_minutes)
rate_limit[client_ip] = [req_time for req_time in rate_limit[client_ip] if req_time > window_start]
if len(rate_limit[client_ip]) >= max_requests:
abort(429, description="Rate limit exceeded")
rate_limit[client_ip].append(now)
return f(*args, **kwargs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The rate limiting logic is not thread-safe. The global rate_limit dictionary (assuming the naming collision is fixed) is a shared resource that is modified by concurrent requests without any synchronization. This will cause race conditions in a multi-threaded server. You need to protect access to this dictionary with a threading.Lock.

Comment thread src/security/auth.py
from typing import Optional

# Security settings
SECRET_KEY = "your-secret-key" # Should be loaded from config

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Hardcoding a secret key is a critical security vulnerability. The SECRET_KEY should be loaded from environment variables or a secure secret management system, not committed to the repository.

Suggested change
SECRET_KEY = "your-secret-key" # Should be loaded from config
SECRET_KEY = os.environ.get("JWT_SECRET_KEY") # Should be loaded from config

Comment thread src/health_monitor.py
"""Get comprehensive system health metrics."""
try:
# System resource usage
cpu_percent = psutil.cpu_percent(interval=1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using psutil.cpu_percent(interval=1) creates a 1-second blocking call. This will make your health check endpoints slow to respond, which can cause problems for services that rely on them (like Kubernetes liveness/readiness probes). For a responsive API, consider using a non-blocking call with interval=None (after an initial call to start measurement) or a much smaller interval like 0.1.

Suggested change
cpu_percent = psutil.cpu_percent(interval=1)
cpu_percent = psutil.cpu_percent(interval=0.1)

Comment thread src/health_monitor.py
Comment on lines +55 to +60
if cpu_percent > 90 or memory.percent > 90 or disk.percent > 90:
health_data["status"] = "warning"
if cpu_percent > 95 or memory.percent > 95 or disk.percent > 95:
health_data["status"] = "critical"
if self.error_count > 0 and self.error_count / max(self.request_count, 1) > 0.1:
health_data["status"] = "degraded"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The logic for determining the overall health status is flawed. The sequence of if statements can lead to a more severe status (like critical) being overwritten by a less severe one (like degraded). You should use an if/elif structure to ensure the most severe status is reported. The order should be from most to least severe.

Suggested change
if cpu_percent > 90 or memory.percent > 90 or disk.percent > 90:
health_data["status"] = "warning"
if cpu_percent > 95 or memory.percent > 95 or disk.percent > 95:
health_data["status"] = "critical"
if self.error_count > 0 and self.error_count / max(self.request_count, 1) > 0.1:
health_data["status"] = "degraded"
# Determine overall health status
if cpu_percent > 95 or memory.percent > 95 or disk.percent > 95:
health_data["status"] = "critical"
elif self.error_count > 0 and self.error_count / max(self.request_count, 1) > 0.1:
health_data["status"] = "degraded"
elif cpu_percent > 90 or memory.percent > 90 or disk.percent > 90:
health_data["status"] = "warning"

Comment thread src/unified_api_server.py
Comment on lines +19 to +20
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using app.run() starts the Flask development server, which is not suitable for production use as it's not designed to be efficient, stable, or secure. For a production deployment, especially in a containerized environment like Kubernetes, you should use a production-grade WSGI server like Gunicorn or uWSGI to run the application.

Comment thread src/health_monitor.py
Comment on lines +34 to +52
health_data = {
"status": "healthy",
"timestamp": datetime.utcnow().isoformat(),
"uptime_hours": round(uptime_hours, 2),
"system": {
"cpu_percent": cpu_percent,
"memory_percent": memory.percent,
"memory_available_gb": round(memory.available / 1024**3, 2),
"disk_percent": disk.percent,
"disk_free_gb": round(disk.free / 1024**3, 2)
},
"process": {
"memory_mb": round(process_memory, 2),
"request_count": self.request_count,
"error_count": self.error_count,
"error_rate": round(self.error_count / max(self.request_count, 1) * 100, 2)
},
"last_health_check": self.last_health_check
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve efficiency and precision, consider adding uptime_seconds directly to the health_data dictionary. Currently, consumers like the /metrics endpoint have to recalculate it from uptime_hours, which involves floating-point arithmetic and can lead to precision loss. Providing the raw seconds value is cleaner.

Suggested change
health_data = {
"status": "healthy",
"timestamp": datetime.utcnow().isoformat(),
"uptime_hours": round(uptime_hours, 2),
"system": {
"cpu_percent": cpu_percent,
"memory_percent": memory.percent,
"memory_available_gb": round(memory.available / 1024**3, 2),
"disk_percent": disk.percent,
"disk_free_gb": round(disk.free / 1024**3, 2)
},
"process": {
"memory_mb": round(process_memory, 2),
"request_count": self.request_count,
"error_count": self.error_count,
"error_rate": round(self.error_count / max(self.request_count, 1) * 100, 2)
},
"last_health_check": self.last_health_check
}
health_data = {
"status": "healthy",
"timestamp": datetime.utcnow().isoformat(),
"uptime_hours": round(uptime_hours, 2),
"uptime_seconds": int(uptime_seconds),
"system": {
"cpu_percent": cpu_percent,
"memory_percent": memory.percent,
"memory_available_gb": round(memory.available / 1024**3, 2),
"disk_percent": disk.percent,
"disk_free_gb": round(disk.free / 1024**3, 2)
},
"process": {
"memory_mb": round(process_memory, 2),
"request_count": self.request_count,
"error_count": self.error_count,
"error_rate": round(self.error_count / max(self.request_count, 1) * 100, 2)
},
"last_health_check": self.last_health_check
}

Comment on lines +15 to +30
self.requests[identifier] = [
timestamp for timestamp in self.requests[identifier]
if timestamp > window_start
]
if len(self.requests[identifier]) < self.max_requests:
self.requests[identifier].append(now)
return True
return False

def get_remaining_requests(self, identifier: str) -> int:
now = time.time()
window_start = now - self.window_seconds
self.requests[identifier] = [
timestamp for timestamp in self.requests[identifier]
if timestamp > window_start
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is code duplication between is_allowed and get_remaining_requests. Both methods contain the same logic to filter out old timestamps. This logic should be extracted into a private helper method to improve maintainability and reduce redundancy.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (12)
src/security/rate_limiter.py (1)

1-4: Remove unused imports.

datetime, timedelta, and Optional are unused.

-from collections import defaultdict
-from datetime import datetime, timedelta
-from typing import Optional
-import time
+from collections import defaultdict
+import time
src/health_monitor.py (4)

22-23: Avoid 1s blocking call in hot path.

psutil.cpu_percent(interval=1) blocks; prefer a small interval (e.g., 0.1) or non-blocking sample with caching.

-            cpu_percent = psutil.cpu_percent(interval=1)
+            cpu_percent = psutil.cpu_percent(interval=0.1)

13-13: Use monotonic clock for uptime.

Wall-clock changes can skew uptime. Use time.monotonic().

-        self.start_time = time.time()
+        self.start_time = time.monotonic()
@@
-            uptime_seconds = time.time() - self.start_time
+            uptime_seconds = time.monotonic() - self.start_time

Also applies to: 31-33


66-71: Log exceptions with stack traces.

Use logger.exception and don’t swallow details.

-            logger.error(f"Health check failed: {e}")
+            logger.exception("Health check failed")

73-77: Counters are not thread-safe.

If served by multi-threaded workers, increments can race. Consider a lock or atomics.

Do you run this under threaded Gunicorn/uwsgi? If yes, I can add a lightweight threading.Lock around the counters.

src/unified_api_server.py (2)

3-4: Package-relative imports for robustness.

Depending on how the app is executed, from auth import ... may fail. Use package-qualified or relative imports.

Example:

from src.auth import require_api_key
from src.rate_limiter import rate_limit

or relative imports if this module is in the same package.


19-20: Bind-all warning is fine for dev; don’t use in prod.

Expose via a WSGI server (gunicorn/uvicorn) in production, not Flask’s dev server.

src/security/auth.py (2)

22-30: Honor configured expiry and add type clarity.

-def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
+def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
     to_encode = data.copy()
-    if expires_delta:
-        expire = datetime.utcnow() + expires_delta
-    else:
-        expire = datetime.utcnow() + timedelta(minutes=15)
+    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
     to_encode.update({"exp": expire})

43-44: Preserve original JWT error as cause.

-    except JWTError:
-        raise credentials_exception
+    except JWTError as err:
+        raise credentials_exception from err
src/health_endpoints.py (3)

1-1: Remove unused import.

request isn’t used.

-from flask import Blueprint, jsonify, request
+from flask import Blueprint, jsonify

17-22: Use logger.exception for tracebacks.

Improve diagnosability across endpoints.

-        logger.error(f"Health check failed: {e}")
+        logger.exception("Health check failed")
@@
-        logger.error(f"Detailed health check failed: {e}")
+        logger.exception("Detailed health check failed")
@@
-        logger.error(f"Readiness check failed: {e}")
+        logger.exception("Readiness check failed")
@@
-        logger.error(f"Liveness check failed: {e}")
+        logger.exception("Liveness check failed")
@@
-        logger.error(f"Metrics collection failed: {e}")
+        logger.exception("Metrics collection failed")

Also applies to: 31-36, 47-49, 57-59, 76-78


80-83: Blueprint registration: confirm central app wiring.

Ensure register_health_endpoints(app) is invoked (and remove any duplicate /api/health route).

I can update unified_api_server.py to register this blueprint and drop the overlapping route if you want.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69ec243 and e6b6c3d.

📒 Files selected for processing (7)
  • src/auth.py (1 hunks)
  • src/health_endpoints.py (1 hunks)
  • src/health_monitor.py (1 hunks)
  • src/rate_limiter.py (1 hunks)
  • src/security/auth.py (1 hunks)
  • src/security/rate_limiter.py (1 hunks)
  • src/unified_api_server.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/unified_api_server.py (2)
src/auth.py (1)
  • require_api_key (4-11)
src/rate_limiter.py (1)
  • rate_limit (8-21)
src/auth.py (1)
src/rate_limiter.py (1)
  • decorated_function (11-19)
src/health_endpoints.py (1)
src/health_monitor.py (2)
  • get_health_summary (79-87)
  • get_system_health (18-71)
src/security/auth.py (1)
tests/unit/test_jwt_manager_extra.py (1)
  • utcnow (56-58)
src/health_monitor.py (1)
src/unified_api_server.py (1)
  • health (10-11)
src/rate_limiter.py (1)
src/auth.py (1)
  • decorated_function (6-10)
🪛 Ruff (0.12.2)
src/unified_api_server.py

20-20: Possible binding to all interfaces

(S104)

src/health_endpoints.py

17-17: Do not catch blind exception: Exception

(BLE001)


18-18: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


31-31: Do not catch blind exception: Exception

(BLE001)


32-32: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


47-47: Do not catch blind exception: Exception

(BLE001)


48-48: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


57-57: Do not catch blind exception: Exception

(BLE001)


58-58: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


76-76: Do not catch blind exception: Exception

(BLE001)


77-77: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

src/security/auth.py

9-9: Possible hardcoded password assigned to: "SECRET_KEY"

(S105)


32-32: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


44-44: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

src/health_monitor.py

63-63: Consider moving this statement to an else block

(TRY300)


65-65: Do not catch blind exception: Exception

(BLE001)


66-66: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

src/rate_limiter.py

8-8: Redefinition of unused rate_limit from line 6

(F811)


10-10: Undefined name wraps

(F821)


12-12: Undefined name request

(F821)

🪛 ast-grep (0.38.6)
src/security/auth.py

[warning] 28-28: Hardcoded JWT secret or private key is used. This is a Insufficiently Protected Credentials weakness: https://cwe.mitre.org/data/definitions/522.html Consider using an appropriate security mechanism to protect the credentials (e.g. keeping secrets in environment variables).
Context: encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
Note: [CWE-522] Insufficiently Protected Credentials. [REFERENCES]
- https://semgrep.dev/blog/2020/hardcoded-secrets-unverified-tokens-and-other-common-jwt-mistakes/

(jwt-python-hardcoded-secret-python)

Comment thread src/auth.py
Comment on lines +7 to +10
api_key = request.headers.get('X-API-Key')
if api_key != 'your-secret-key': # Replace with actual key or env var
return jsonify({'error': 'API key required'}), 401
return f(*args, **kwargs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Eliminate hardcoded API key; use env var + constant-time compare.

Hardcoding secrets and using plain equality is insecure. Read from env and use hmac.compare_digest.

Apply within this block:

-        api_key = request.headers.get('X-API-Key')
-        if api_key != 'your-secret-key':  # Replace with actual key or env var
-            return jsonify({'error': 'API key required'}), 401
+        api_key = request.headers.get('X-API-Key', '')
+        expected = os.getenv('API_KEY', '')
+        if not expected or not hmac.compare_digest(api_key, expected):
+            return jsonify({'error': 'invalid or missing API key'}), 401

Add imports at top:

import os, hmac
🤖 Prompt for AI Agents
In src/auth.py around lines 7 to 10, the code currently uses a hardcoded API key
and plain equality; replace this by reading the expected key from an environment
variable (e.g., os.getenv('API_KEY', '')) and perform the comparison using
hmac.compare_digest to avoid timing attacks; also ensure you import os and hmac
at the top of the file, treat missing env var as empty string, and return the
same 401 response when the constant-time comparison fails.

Comment thread src/health_endpoints.py
Comment on lines +65 to +75
health_data = health_monitor.get_system_health()
metrics = {
"api_requests_total": health_data["process"]["request_count"],
"api_errors_total": health_data["process"]["error_count"],
"api_error_rate_percent": health_data["process"]["error_rate"],
"system_cpu_percent": health_data["system"]["cpu_percent"],
"system_memory_percent": health_data["system"]["memory_percent"],
"system_disk_percent": health_data["system"]["disk_percent"],
"uptime_seconds": health_data["uptime_hours"] * 3600
}
return jsonify(metrics), 200

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard metrics extraction when health_data indicates error.

If get_system_health() returns {"status":"error",...}, indexing process/system will KeyError.

-        health_data = health_monitor.get_system_health()
-        metrics = {
+        health_data = health_monitor.get_system_health()
+        if health_data.get("status") == "error":
+            return jsonify({"error": "Metrics unavailable"}), 503
+        metrics = {
             "api_requests_total": health_data["process"]["request_count"],
             "api_errors_total": health_data["process"]["error_count"],
             "api_error_rate_percent": health_data["process"]["error_rate"],
             "system_cpu_percent": health_data["system"]["cpu_percent"],
             "system_memory_percent": health_data["system"]["memory_percent"],
             "system_disk_percent": health_data["system"]["disk_percent"],
             "uptime_seconds": health_data["uptime_hours"] * 3600
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
health_data = health_monitor.get_system_health()
metrics = {
"api_requests_total": health_data["process"]["request_count"],
"api_errors_total": health_data["process"]["error_count"],
"api_error_rate_percent": health_data["process"]["error_rate"],
"system_cpu_percent": health_data["system"]["cpu_percent"],
"system_memory_percent": health_data["system"]["memory_percent"],
"system_disk_percent": health_data["system"]["disk_percent"],
"uptime_seconds": health_data["uptime_hours"] * 3600
}
return jsonify(metrics), 200
health_data = health_monitor.get_system_health()
if health_data.get("status") == "error":
return jsonify({"error": "Metrics unavailable"}), 503
metrics = {
"api_requests_total": health_data["process"]["request_count"],
"api_errors_total": health_data["process"]["error_count"],
"api_error_rate_percent": health_data["process"]["error_rate"],
"system_cpu_percent": health_data["system"]["cpu_percent"],
"system_memory_percent": health_data["system"]["memory_percent"],
"system_disk_percent": health_data["system"]["disk_percent"],
"uptime_seconds": health_data["uptime_hours"] * 3600
}
return jsonify(metrics), 200
🤖 Prompt for AI Agents
In src/health_endpoints.py around lines 65 to 75, the code assumes health_data
contains "process" and "system" and will KeyError if get_system_health() returns
{"status": "error", ...}; guard the extraction by first checking
health_data.get("status") == "ok" (or validate presence of "process" and
"system") and if not return an appropriate error response (e.g.,
jsonify({"error":"health unavailable"}), 500) or populate safe default metric
values, and optionally log the health error; implement this check before
building the metrics dict to avoid KeyError.

Comment thread src/health_monitor.py
Comment on lines +55 to +61
if cpu_percent > 90 or memory.percent > 90 or disk.percent > 90:
health_data["status"] = "warning"
if cpu_percent > 95 or memory.percent > 95 or disk.percent > 95:
health_data["status"] = "critical"
if self.error_count > 0 and self.error_count / max(self.request_count, 1) > 0.1:
health_data["status"] = "degraded"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Status precedence bug: “degraded” overrides “critical”.

If CPU/mem/disk >95%, status becomes critical, then is overwritten to degraded on error_rate >10%. Preserve highest severity.

-            if cpu_percent > 90 or memory.percent > 90 or disk.percent > 90:
-                health_data["status"] = "warning"
-            if cpu_percent > 95 or memory.percent > 95 or disk.percent > 95:
-                health_data["status"] = "critical"
-            if self.error_count > 0 and self.error_count / max(self.request_count, 1) > 0.1:
-                health_data["status"] = "degraded"
+            # Determine overall health with clear precedence: critical > degraded > warning > healthy
+            status = "healthy"
+            if cpu_percent > 90 or memory.percent > 90 or disk.percent > 90:
+                status = "warning"
+            if cpu_percent > 95 or memory.percent > 95 or disk.percent > 95:
+                status = "critical"
+            err_rate = self.error_count / max(self.request_count, 1)
+            if status != "critical" and self.error_count > 0 and err_rate > 0.10:
+                status = "degraded"
+            health_data["status"] = status
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if cpu_percent > 90 or memory.percent > 90 or disk.percent > 90:
health_data["status"] = "warning"
if cpu_percent > 95 or memory.percent > 95 or disk.percent > 95:
health_data["status"] = "critical"
if self.error_count > 0 and self.error_count / max(self.request_count, 1) > 0.1:
health_data["status"] = "degraded"
# Determine overall health with clear precedence: critical > degraded > warning > healthy
status = "healthy"
if cpu_percent > 90 or memory.percent > 90 or disk.percent > 90:
status = "warning"
if cpu_percent > 95 or memory.percent > 95 or disk.percent > 95:
status = "critical"
err_rate = self.error_count / max(self.request_count, 1)
if status != "critical" and self.error_count > 0 and err_rate > 0.10:
status = "degraded"
health_data["status"] = status
🤖 Prompt for AI Agents
In src/health_monitor.py around lines 55 to 61, the status assignment logic
allows "degraded" (error rate >10%) to overwrite a previously set
higher-severity "critical" state; change the logic to enforce severity
precedence so the final status is the highest-severity condition met (e.g.,
critical > degraded > warning > healthy). Implement this by computing each
condition but only upgrading status when the new severity is higher than the
current one (or map statuses to numeric severity levels and pick the maximum),
so critical is never overridden by degraded or warning.

Comment thread src/rate_limiter.py
Comment on lines +1 to +21
from collections import defaultdict
from datetime import datetime, timedelta
from flask import abort, current_app

# Simple rate limiter using memory (use Redis for production)
rate_limit = defaultdict(list)

def rate_limit(max_requests=100, window_minutes=1):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
client_ip = request.remote_addr
now = datetime.utcnow()
window_start = now - timedelta(minutes=window_minutes)
rate_limit[client_ip] = [req_time for req_time in rate_limit[client_ip] if req_time > window_start]
if len(rate_limit[client_ip]) >= max_requests:
abort(429, description="Rate limit exceeded")
rate_limit[client_ip].append(now)
return f(*args, **kwargs)
return decorated_function
return decorator

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix name shadowing and missing imports causing runtime failures.

rate_limit function shadows the dict rate_limit, and wraps/request aren’t imported. This will raise TypeError and NameError.

Apply:

-from collections import defaultdict
-from datetime import datetime, timedelta
-from flask import abort, current_app
+from collections import defaultdict
+from datetime import datetime, timedelta
+from functools import wraps
+from flask import abort, request
@@
-# Simple rate limiter using memory (use Redis for production)
-rate_limit = defaultdict(list)
+# Simple rate limiter using memory (use Redis for production)
+_RATE_LIMIT_STORE = defaultdict(list)
@@
-def rate_limit(max_requests=100, window_minutes=1):
+def rate_limit(max_requests=100, window_minutes=1):
     def decorator(f):
         @wraps(f)
         def decorated_function(*args, **kwargs):
             client_ip = request.remote_addr
             now = datetime.utcnow()
             window_start = now - timedelta(minutes=window_minutes)
-            rate_limit[client_ip] = [req_time for req_time in rate_limit[client_ip] if req_time > window_start]
-            if len(rate_limit[client_ip]) >= max_requests:
+            _RATE_LIMIT_STORE[client_ip] = [t for t in _RATE_LIMIT_STORE[client_ip] if t > window_start]
+            if len(_RATE_LIMIT_STORE[client_ip]) >= max_requests:
                 abort(429, description="Rate limit exceeded")
-            rate_limit[client_ip].append(now)
+            _RATE_LIMIT_STORE[client_ip].append(now)
             return f(*args, **kwargs)
         return decorated_function
     return decorator

Optional hardening:

  • Use X-Forwarded-For when behind proxies.
  • Add a lock if running multi-threaded.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from collections import defaultdict
from datetime import datetime, timedelta
from flask import abort, current_app
# Simple rate limiter using memory (use Redis for production)
rate_limit = defaultdict(list)
def rate_limit(max_requests=100, window_minutes=1):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
client_ip = request.remote_addr
now = datetime.utcnow()
window_start = now - timedelta(minutes=window_minutes)
rate_limit[client_ip] = [req_time for req_time in rate_limit[client_ip] if req_time > window_start]
if len(rate_limit[client_ip]) >= max_requests:
abort(429, description="Rate limit exceeded")
rate_limit[client_ip].append(now)
return f(*args, **kwargs)
return decorated_function
return decorator
from collections import defaultdict
from datetime import datetime, timedelta
from functools import wraps
from flask import abort, request
# Simple rate limiter using memory (use Redis for production)
_RATE_LIMIT_STORE = defaultdict(list)
def rate_limit(max_requests=100, window_minutes=1):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
client_ip = request.remote_addr
now = datetime.utcnow()
window_start = now - timedelta(minutes=window_minutes)
_RATE_LIMIT_STORE[client_ip] = [
t for t in _RATE_LIMIT_STORE[client_ip]
if t > window_start
]
if len(_RATE_LIMIT_STORE[client_ip]) >= max_requests:
abort(429, description="Rate limit exceeded")
_RATE_LIMIT_STORE[client_ip].append(now)
return f(*args, **kwargs)
return decorated_function
return decorator
🧰 Tools
🪛 Ruff (0.12.2)

8-8: Redefinition of unused rate_limit from line 6

(F811)


10-10: Undefined name wraps

(F821)


12-12: Undefined name request

(F821)

🤖 Prompt for AI Agents
In src/rate_limiter.py lines 1-21 the top-level dict rate_limit is being
shadowed by the decorator function rate_limit and wraps and request are not
imported causing TypeError/NameError; rename the dict (e.g., rate_limit_store or
rate_limit_map) to avoid the name collision, add the missing imports from
functools import wraps and from flask import request, update all references in
the decorator to use the new dict name, and keep the decorator name rate_limit
(or rename the function if you prefer) so runtime errors are resolved;
optionally consider using X-Forwarded-For for proxied clients and protecting the
dict with a threading.Lock for multi-threaded environments.

Comment thread src/security/auth.py
Comment on lines +8 to +12
# Security settings
SECRET_KEY = "your-secret-key" # Should be loaded from config
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace hardcoded JWT secret and align expiry default.

Load secret from env/config and use ACCESS_TOKEN_EXPIRE_MINUTES consistently.

-SECRET_KEY = "your-secret-key"  # Should be loaded from config
+import os
+SECRET_KEY = os.getenv("JWT_SECRET_KEY", "")
@@
-ACCESS_TOKEN_EXPIRE_MINUTES = 30
+ACCESS_TOKEN_EXPIRE_MINUTES = 30

Ensure startup fails if SECRET_KEY is empty, or handle gracefully.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Security settings
SECRET_KEY = "your-secret-key" # Should be loaded from config
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# Security settings
import os
SECRET_KEY = os.getenv("JWT_SECRET_KEY", "")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
🧰 Tools
🪛 Ruff (0.12.2)

9-9: Possible hardcoded password assigned to: "SECRET_KEY"

(S105)

🤖 Prompt for AI Agents
In src/security/auth.py around lines 8 to 12, the JWT secret is hardcoded and
the expiry constant may be inconsistently used; replace the literal SECRET_KEY
with a value read from configuration or environment (e.g., os.environ or your
app config loader), validate that the loaded SECRET_KEY is non-empty at startup
and raise/exit with a clear error if it is missing, and ensure
ACCESS_TOKEN_EXPIRE_MINUTES is defined as the single canonical constant used
throughout the codebase (remove duplicate literals and reference this constant
wherever token expiry is set) so expiry behavior is consistent.

Comment thread src/unified_api_server.py
Comment on lines +9 to +12
@app.route('/api/health')
def health():
return jsonify({'status': 'healthy'})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Route duplication with health blueprint.

This overlaps with health_bp at /api/health/. Prefer the blueprint-only version to avoid divergent responses.

-@app.route('/api/health')
-def health():
-    return jsonify({'status': 'healthy'})
+# Register health blueprint instead of a separate route
+from health_endpoints import register_health_endpoints  # at top-level imports
+register_health_endpoints(app)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@app.route('/api/health')
def health():
return jsonify({'status': 'healthy'})
# Register health blueprint instead of a separate route
from health_endpoints import register_health_endpoints # at top-level imports
register_health_endpoints(app)
🤖 Prompt for AI Agents
In src/unified_api_server.py around lines 9 to 12, there's a duplicate
/api/health route defined directly on the app that conflicts with the health_bp
blueprint at /api/health/; remove this function-based route (the shown
@app.route('/api/health') health() handler) so only the blueprint-backed
endpoint remains, and verify health_bp is registered on the app with the
intended URL prefix.

@d-ulker

d-ulker commented Sep 25, 2025

Copy link
Copy Markdown
Owner Author

Closing redundant micro-PR: Health checks and monitoring functionality is now covered by the merged Phase 3A fortress-protected health monitoring system (PR #181). This provides comprehensive Cloud Run monitoring with quality gates.

@d-ulker d-ulker closed this Sep 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants