Skip to content

RSPEED-2941: fix REST API metrics when root_path is set#1614

Merged
tisnik merged 1 commit into
lightspeed-core:mainfrom
major:fix/rspeed-2941-metrics-root-path
Apr 28, 2026
Merged

RSPEED-2941: fix REST API metrics when root_path is set#1614
tisnik merged 1 commit into
lightspeed-core:mainfrom
major:fix/rspeed-2941-metrics-root-path

Conversation

@major
Copy link
Copy Markdown
Contributor

@major major commented Apr 28, 2026

Description

The RestApiMetricsMiddleware reads scope["path"] directly, which contains the full request path including any root_path prefix set by FastAPI (e.g., /api/lightspeed/v1/infer). Since app_routes_paths stores only application-level paths (e.g., /v1/infer), the middleware's path check fails and both the ls_rest_api_calls_total counter and ls_response_duration_seconds histogram are never recorded for API traffic routed through 3scale.

This causes the Grafana "Request Rate by Path" and "Error Rate" panels to show blank, and "P95 Response Latency" to show only /metrics (the only path where Prometheus scrapes bypass 3scale and hit the pod directly).

The fix uses get_route_path(scope) from starlette._utils to strip the root_path prefix before matching, consistent with how Starlette's Router matches routes internally.

Paths lost in the mist
root strips what middleware missed
counters count at last

Type of change

  • Bug fix

Tools used to create PR

  • Assisted-by: Claude (analysis and code generation)
  • Generated by: N/A

Related Tickets & Documents

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

Testing

  1. Run middleware unit tests: pytest tests/unit/app/test_main_middleware.py -v
  2. All 8 tests pass, including two new ones:
    • test_rest_api_metrics_strips_root_path: verifies that when root_path="/api/lightspeed" and path is /api/lightspeed/v1/infer, metrics are recorded with label /v1/infer
    • test_rest_api_metrics_no_root_path_unchanged: verifies no behavioral change when root_path is not set
  3. Deploy to stage with ROOT_PATH=/api/lightspeed and verify Grafana panels populate

Summary by CodeRabbit

  • Bug Fixes

    • Fixed metrics tracking to correctly derive request paths in various routing configurations, improving metric label accuracy
    • Enhanced request path handling to ensure accurate metric collection across diverse deployment scenarios
  • Tests

    • Added tests to verify metrics behavior with and without routing path prefixes, ensuring consistency

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@major has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 46 minutes and 52 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e221b6a8-9f1f-4d88-95fd-2d2a068d11fb

📥 Commits

Reviewing files that changed from the base of the PR and between 764e65d and 3951e59.

📒 Files selected for processing (2)
  • src/app/main.py
  • tests/unit/app/test_main_middleware.py

Walkthrough

The changes modify RestApiMetricsMiddleware to use Starlette's get_route_path(scope) for request path derivation instead of directly accessing scope["path"]. This affects debug logging, route membership checks, and metrics label generation. Tests are updated to support optional ASGI root_path parameters and verify behavior with and without path prefixes.

Changes

Cohort / File(s) Summary
Middleware Path Resolution
src/app/main.py
Changed RestApiMetricsMiddleware to use get_route_path(scope) instead of scope["path"] for request path derivation, affecting debug logging, route membership checks, and metrics labels for response_duration_seconds and rest_api_calls_total.
Middleware Unit Tests
tests/unit/app/test_main_middleware.py
Added root_path parameter to test scope builder and two new tests validating metrics behavior when ASGI root_path is present (with path prefix stripping) and absent (unchanged behavior).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing REST API metrics when root_path is set, which aligns with the core bug fix of using get_route_path(scope) to strip root_path prefixes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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.

@major major force-pushed the fix/rspeed-2941-metrics-root-path branch 2 times, most recently from ca1dede to e096022 Compare April 28, 2026 15:33
Copy link
Copy Markdown
Contributor

@tisnik tisnik left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/main.py`:
- Line 12: Replace the private import get_route_path with a local path
computation: remove the import of get_route_path and wherever
get_route_path(scope) is used (the call site referencing scope), compute the
route path using scope["path"].removeprefix(scope.get("root_path", "")) and use
that local variable (e.g., path) instead of calling the private helper; ensure
the replacement uses scope, "root_path", and removeprefix so behavior matches
Starlette's internal logic.

In `@tests/unit/app/test_main_middleware.py`:
- Around line 15-26: Change _make_scope's return type from dict to the ASGI
Scope type by updating its signature to return Scope and importing Scope (e.g.,
from starlette.types import Scope); update the local variable annotation to
scope: Scope = { ... } so the constructed dict matches the Scope type (adjust
header types if needed) and keep the rest of the helper unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4b252b5f-3677-4142-829f-ed66be625d09

📥 Commits

Reviewing files that changed from the base of the PR and between 6ea1e91 and 764e65d.

📒 Files selected for processing (2)
  • src/app/main.py
  • tests/unit/app/test_main_middleware.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: E2E: library mode / ci / group 2
  • GitHub Check: E2E: server mode / ci / group 2
  • GitHub Check: E2E: server mode / ci / group 3
  • GitHub Check: E2E: server mode / ci / group 1
  • GitHub Check: E2E: library mode / ci / group 3
  • GitHub Check: E2E: library mode / ci / group 1
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use absolute imports for internal modules: from authentication import get_auth_dependency
Import FastAPI dependencies with: from fastapi import APIRouter, HTTPException, Request, status, Depends
Import Llama Stack client with: from llama_stack_client import AsyncLlamaStackClient
Check constants.py for shared constants before defining new ones
All modules start with descriptive docstrings explaining purpose
Use logger = get_logger(__name__) from log.py for module logging
Type aliases defined at module level for clarity
Use Final[type] as type hint for all constants
All functions require docstrings with brief descriptions
Complete type annotations for parameters and return types in functions
Use typing_extensions.Self for model validators in Pydantic models
Use modern union type syntax str | int instead of Union[str, int]
Use Optional[Type] for optional type hints
Use snake_case with descriptive, action-oriented function names (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead
Use async def for I/O operations and external API calls
Handle APIConnectionError from Llama Stack in error handling
Use standard log levels with clear purposes: debug, info, warning, error
All classes require descriptive docstrings explaining purpose
Use PascalCase for class names with standard suffixes: Configuration, Error/Exception, Resolver, Interface
Use ABC for abstract base classes with @abstractmethod decorators
Use @model_validator and @field_validator for Pydantic model validation
Complete type annotations for all class attributes; use specific types, not Any
Follow Google Python docstring conventions with Parameters, Returns, Raises, and Attributes sections

Files:

  • src/app/main.py
  • tests/unit/app/test_main_middleware.py
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Pydantic models extend ConfigurationBase for config, BaseModel for data models

Files:

  • src/app/main.py
tests/{unit,integration}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/{unit,integration}/**/*.py: Use pytest for all unit and integration tests
Do not use unittest; pytest is the standard for this project
Use pytest-mock for AsyncMock objects in tests
Use marker pytest.mark.asyncio for async tests
Unit tests require 60% coverage, integration tests 10%

Files:

  • tests/unit/app/test_main_middleware.py
🔇 Additional comments (2)
src/app/main.py (1)

158-184: Path normalization is correctly applied before metrics route checks.

Using the normalized route path before app_routes_paths membership and metric labels fixes the root-path mismatch and preserves expected counter/histogram behavior.

tests/unit/app/test_main_middleware.py (1)

183-236: Great regression coverage for root_path path-label behavior.

These tests validate both prefixed (root_path set) and non-prefixed flows and assert the exact metric labels/status values expected from the middleware fix.

Comment thread src/app/main.py Outdated
Comment thread tests/unit/app/test_main_middleware.py Outdated
The metrics middleware reads scope["path"] directly, which contains
the full request path including any root_path prefix (e.g.,
/api/lightspeed/v1/infer). Since app_routes_paths stores only
application-level paths (e.g., /v1/infer), the path check fails and
metrics are never recorded for any API traffic routed through 3scale.

Use get_route_path(scope) from starlette._utils to strip the
root_path prefix before matching, consistent with how Starlette
Router matches routes internally.

Signed-off-by: Major Hayden <major@redhat.com>
@major major force-pushed the fix/rspeed-2941-metrics-root-path branch from e096022 to 3951e59 Compare April 28, 2026 15:41
@tisnik tisnik merged commit c50425e into lightspeed-core:main Apr 28, 2026
26 of 27 checks passed
@major major deleted the fix/rspeed-2941-metrics-root-path branch April 28, 2026 16:18
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