Skip to content

Commit 3580465

Browse files
committed
feat(backend): add observability instrumentation to routes
Add comprehensive observability to search.py, search_v2.py, auth.py, and api_keys.py routes using the existing observability module. Changes: - Add structured logging with context (user_id, repo_id, duration) - Add Sentry operation context and breadcrumbs for error tracing - Add performance tracking with track_time() for key operations - Add exception capture with full context for debugging - Add 17 new tests for observability coverage Each route now properly: - Sets Sentry operation context at request start - Adds breadcrumbs for request tracing - Logs request completion with metrics - Captures exceptions with full context - Tracks timing for cache checks, searches, auth operations Closes #163
1 parent 741796e commit 3580465

5 files changed

Lines changed: 795 additions & 56 deletions

File tree

backend/routes/api_keys.py

Lines changed: 84 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
"""API key management and metrics routes."""
2-
from fastapi import APIRouter, Depends
2+
from fastapi import APIRouter, HTTPException, Depends
33
from pydantic import BaseModel
44

55
from dependencies import api_key_manager, rate_limiter, metrics
66
from middleware.auth import require_auth, AuthContext
7+
from services.observability import (
8+
logger,
9+
capture_exception,
10+
track_time,
11+
add_breadcrumb,
12+
set_operation_context
13+
)
714

815
router = APIRouter(prefix="", tags=["API Keys"])
916

@@ -18,7 +25,19 @@ async def get_performance_metrics(
1825
auth: AuthContext = Depends(require_auth)
1926
):
2027
"""Get performance metrics and monitoring data."""
21-
return metrics.get_metrics()
28+
set_operation_context("get_metrics", user_id=auth.user_id)
29+
30+
logger.debug("Metrics requested", user_id=auth.user_id)
31+
32+
try:
33+
with track_time("get_metrics"):
34+
result = metrics.get_metrics()
35+
36+
return result
37+
except Exception as e:
38+
logger.error("Failed to get metrics", user_id=auth.user_id, error=str(e))
39+
capture_exception(e, operation="get_metrics", user_id=auth.user_id)
40+
raise HTTPException(status_code=500, detail="Failed to retrieve metrics")
2241

2342

2443
@router.post("/keys/generate")
@@ -27,33 +46,76 @@ async def generate_api_key(
2746
auth: AuthContext = Depends(require_auth)
2847
):
2948
"""Generate a new API key."""
30-
new_key = api_key_manager.generate_key(
31-
name=request.name,
32-
tier=request.tier,
33-
user_id=auth.user_id
49+
set_operation_context("generate_api_key", user_id=auth.user_id, tier=request.tier)
50+
add_breadcrumb("API key generation requested", category="api_keys", tier=request.tier)
51+
52+
logger.info(
53+
"API key generation requested",
54+
user_id=auth.user_id,
55+
key_name=request.name,
56+
tier=request.tier
3457
)
3558

36-
return {
37-
"api_key": new_key,
38-
"tier": request.tier,
39-
"name": request.name,
40-
"message": "Save this key securely - it won't be shown again"
41-
}
59+
try:
60+
with track_time("generate_api_key", tier=request.tier):
61+
new_key = api_key_manager.generate_key(
62+
name=request.name,
63+
tier=request.tier,
64+
user_id=auth.user_id
65+
)
66+
67+
logger.info(
68+
"API key generated successfully",
69+
user_id=auth.user_id,
70+
key_name=request.name,
71+
tier=request.tier
72+
)
73+
74+
return {
75+
"api_key": new_key,
76+
"tier": request.tier,
77+
"name": request.name,
78+
"message": "Save this key securely - it won't be shown again"
79+
}
80+
except Exception as e:
81+
logger.error(
82+
"API key generation failed",
83+
user_id=auth.user_id,
84+
key_name=request.name,
85+
error=str(e)
86+
)
87+
capture_exception(
88+
e,
89+
operation="generate_api_key",
90+
user_id=auth.user_id,
91+
key_name=request.name
92+
)
93+
raise HTTPException(status_code=500, detail="Failed to generate API key")
4294

4395

4496
@router.get("/keys/usage")
4597
async def get_api_usage(
4698
auth: AuthContext = Depends(require_auth)
4799
):
48100
"""Get current API usage stats."""
49-
usage = rate_limiter.get_usage(auth.identifier)
101+
set_operation_context("get_api_usage", user_id=auth.user_id, tier=auth.tier)
102+
103+
logger.debug("API usage requested", user_id=auth.user_id, tier=auth.tier)
50104

51-
return {
52-
"tier": auth.tier,
53-
"limits": {
54-
"free": {"minute": 20, "hour": 200, "day": 1000},
55-
"pro": {"minute": 100, "hour": 2000, "day": 20000},
56-
"enterprise": {"minute": 500, "hour": 10000, "day": 100000}
57-
}[auth.tier],
58-
"usage": usage
59-
}
105+
try:
106+
with track_time("get_api_usage"):
107+
usage = rate_limiter.get_usage(auth.identifier)
108+
109+
return {
110+
"tier": auth.tier,
111+
"limits": {
112+
"free": {"minute": 20, "hour": 200, "day": 1000},
113+
"pro": {"minute": 100, "hour": 2000, "day": 20000},
114+
"enterprise": {"minute": 500, "hour": 10000, "day": 100000}
115+
}[auth.tier],
116+
"usage": usage
117+
}
118+
except Exception as e:
119+
logger.error("Failed to get API usage", user_id=auth.user_id, error=str(e))
120+
capture_exception(e, operation="get_api_usage", user_id=auth.user_id)
121+
raise HTTPException(status_code=500, detail="Failed to retrieve usage data")

backend/routes/auth.py

Lines changed: 101 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
from typing import Optional, Dict, Any
88
from services.auth import get_auth_service
99
from middleware.auth import get_current_user
10+
from services.observability import (
11+
logger,
12+
capture_exception,
13+
track_time,
14+
add_breadcrumb,
15+
set_operation_context
16+
)
1017

1118
# Create router
1219
router = APIRouter(prefix="/auth", tags=["Authentication"])
@@ -45,12 +52,31 @@ async def signup(request: SignupRequest):
4552
4653
Returns user data and session tokens (access_token, refresh_token)
4754
"""
48-
auth_service = get_auth_service()
49-
return await auth_service.signup(
50-
email=request.email,
51-
password=request.password,
52-
github_username=request.github_username
53-
)
55+
set_operation_context("auth_signup", email=request.email)
56+
add_breadcrumb("Signup attempt", category="auth", email=request.email)
57+
58+
logger.info("Signup attempt", email=request.email, has_github=bool(request.github_username))
59+
60+
try:
61+
auth_service = get_auth_service()
62+
63+
with track_time("auth_signup"):
64+
result = await auth_service.signup(
65+
email=request.email,
66+
password=request.password,
67+
github_username=request.github_username
68+
)
69+
70+
logger.info("Signup successful", email=request.email)
71+
return result
72+
73+
except HTTPException:
74+
logger.warning("Signup failed (client error)", email=request.email)
75+
raise
76+
except Exception as e:
77+
logger.error("Signup failed", email=request.email, error=str(e))
78+
capture_exception(e, operation="auth_signup", email=request.email)
79+
raise HTTPException(status_code=500, detail="Signup failed")
5480

5581

5682
@router.post("/login", response_model=AuthResponse)
@@ -63,11 +89,30 @@ async def login(request: LoginRequest):
6389
6490
Returns user data and session tokens
6591
"""
66-
auth_service = get_auth_service()
67-
return await auth_service.login(
68-
email=request.email,
69-
password=request.password
70-
)
92+
set_operation_context("auth_login", email=request.email)
93+
add_breadcrumb("Login attempt", category="auth", email=request.email)
94+
95+
logger.info("Login attempt", email=request.email)
96+
97+
try:
98+
auth_service = get_auth_service()
99+
100+
with track_time("auth_login"):
101+
result = await auth_service.login(
102+
email=request.email,
103+
password=request.password
104+
)
105+
106+
logger.info("Login successful", email=request.email)
107+
return result
108+
109+
except HTTPException:
110+
logger.warning("Login failed (invalid credentials)", email=request.email)
111+
raise
112+
except Exception as e:
113+
logger.error("Login failed", email=request.email, error=str(e))
114+
capture_exception(e, operation="auth_login", email=request.email)
115+
raise HTTPException(status_code=500, detail="Login failed")
71116

72117

73118
@router.post("/refresh")
@@ -79,8 +124,27 @@ async def refresh(request: RefreshRequest):
79124
80125
Returns new access token
81126
"""
82-
auth_service = get_auth_service()
83-
return await auth_service.refresh_session(request.refresh_token)
127+
set_operation_context("auth_refresh")
128+
add_breadcrumb("Token refresh attempt", category="auth")
129+
130+
logger.debug("Token refresh attempt")
131+
132+
try:
133+
auth_service = get_auth_service()
134+
135+
with track_time("auth_refresh"):
136+
result = await auth_service.refresh_session(request.refresh_token)
137+
138+
logger.debug("Token refresh successful")
139+
return result
140+
141+
except HTTPException:
142+
logger.warning("Token refresh failed (invalid token)")
143+
raise
144+
except Exception as e:
145+
logger.error("Token refresh failed", error=str(e))
146+
capture_exception(e, operation="auth_refresh")
147+
raise HTTPException(status_code=500, detail="Token refresh failed")
84148

85149

86150
@router.post("/logout")
@@ -90,8 +154,25 @@ async def logout(user: Dict = Depends(get_current_user)):
90154
91155
Requires: Valid JWT token in Authorization header
92156
"""
93-
auth_service = get_auth_service()
94-
return await auth_service.logout(token="") # Supabase handles session
157+
user_id = user.get("id") or user.get("user_id")
158+
set_operation_context("auth_logout", user_id=user_id)
159+
add_breadcrumb("Logout attempt", category="auth", user_id=user_id)
160+
161+
logger.info("Logout attempt", user_id=user_id)
162+
163+
try:
164+
auth_service = get_auth_service()
165+
166+
with track_time("auth_logout"):
167+
result = await auth_service.logout(token="") # Supabase handles session
168+
169+
logger.info("Logout successful", user_id=user_id)
170+
return result
171+
172+
except Exception as e:
173+
logger.error("Logout failed", user_id=user_id, error=str(e))
174+
capture_exception(e, operation="auth_logout", user_id=user_id)
175+
raise HTTPException(status_code=500, detail="Logout failed")
95176

96177

97178
@router.get("/me")
@@ -103,4 +184,9 @@ async def get_current_user_info(user: Dict = Depends(get_current_user)):
103184
104185
Returns user profile data
105186
"""
187+
user_id = user.get("id") or user.get("user_id")
188+
set_operation_context("auth_me", user_id=user_id)
189+
190+
logger.debug("User info requested", user_id=user_id)
191+
106192
return {"user": user}

0 commit comments

Comments
 (0)