Coverage for src / mcp_server_langgraph / auth / metrics.py: 97%
76 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-03 00:43 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-03 00:43 +0000
1"""
2Authentication Metrics for Observability
4Defines comprehensive metrics for monitoring authentication workflows:
5- Login attempts and success rates
6- Token verification performance
7- Session management
8- JWKS cache efficiency
9- OpenFGA role synchronization
10"""
12from opentelemetry import metrics as otel_metrics
14# Get meter for auth metrics
15meter = otel_metrics.get_meter(__name__)
18# Authentication Metrics
20auth_login_attempts = meter.create_counter(
21 name="auth.login.attempts", description="Number of login attempts by provider and result", unit="1"
22)
24auth_login_duration = meter.create_histogram(
25 name="auth.login.duration", description="Login duration in milliseconds", unit="ms"
26)
28auth_login_failures = meter.create_counter(name="auth.login.failures", description="Login failures by reason", unit="1")
31# Token Management Metrics
33auth_token_created = meter.create_counter(name="auth.token.created", description="Tokens created by provider", unit="1")
35auth_token_verifications = meter.create_counter(
36 name="auth.token.verifications", description="Token verification attempts by result", unit="1"
37)
39auth_token_verify_duration = meter.create_histogram(
40 name="auth.token.verify.duration", description="Token verification duration in milliseconds", unit="ms"
41)
43auth_token_refresh_attempts = meter.create_counter(
44 name="auth.token.refresh.attempts", description="Token refresh attempts by result", unit="1"
45)
48# JWKS Cache Metrics
50auth_jwks_cache_operations = meter.create_counter(
51 name="auth.jwks.cache.operations", description="JWKS cache hits and misses", unit="1"
52)
54auth_jwks_fetch_duration = meter.create_histogram(
55 name="auth.jwks.fetch.duration", description="JWKS fetch duration in milliseconds", unit="ms"
56)
58auth_jwks_refresh_count = meter.create_counter(name="auth.jwks.refresh", description="JWKS cache refresh count", unit="1")
61# Session Management Metrics
63auth_sessions_active = meter.create_up_down_counter(
64 name="auth.sessions.active", description="Number of active sessions", unit="1"
65)
67auth_session_created = meter.create_counter(name="auth.session.created", description="Sessions created by backend", unit="1")
69auth_session_retrieved = meter.create_counter(
70 name="auth.session.retrieved", description="Session retrieval attempts by result", unit="1"
71)
73auth_session_refreshed = meter.create_counter(name="auth.session.refreshed", description="Sessions refreshed", unit="1")
75auth_session_revoked = meter.create_counter(name="auth.session.revoked", description="Sessions revoked (deleted)", unit="1")
77auth_session_expired = meter.create_counter(
78 name="auth.session.expired", description="Sessions that expired naturally", unit="1"
79)
81auth_session_duration = meter.create_histogram(
82 name="auth.session.duration", description="Session lifetime in seconds", unit="seconds"
83)
85auth_session_operations_duration = meter.create_histogram(
86 name="auth.session.operations.duration", description="Session operation duration in milliseconds", unit="ms"
87)
90# User Provider Metrics
92auth_provider_calls = meter.create_counter(
93 name="auth.provider.calls", description="User provider method calls by provider and method", unit="1"
94)
96auth_provider_duration = meter.create_histogram(
97 name="auth.provider.duration", description="User provider operation duration", unit="ms"
98)
100auth_provider_errors = meter.create_counter(
101 name="auth.provider.errors", description="User provider errors by provider and error type", unit="1"
102)
105# OpenFGA Integration Metrics
107auth_openfga_sync_attempts = meter.create_counter(
108 name="auth.openfga.sync.attempts", description="OpenFGA role sync attempts by result", unit="1"
109)
111auth_openfga_sync_duration = meter.create_histogram(
112 name="auth.openfga.sync.duration", description="OpenFGA role sync duration in milliseconds", unit="ms"
113)
115auth_openfga_tuples_written = meter.create_histogram(
116 name="auth.openfga.tuples.written", description="Number of tuples written per sync", unit="tuples"
117)
119auth_openfga_sync_errors = meter.create_counter(
120 name="auth.openfga.sync.errors", description="OpenFGA sync errors by error type", unit="1"
121)
124# Authorization Metrics
126auth_authz_checks = meter.create_counter(
127 name="auth.authorization.checks", description="Authorization checks by result", unit="1"
128)
130auth_authz_duration = meter.create_histogram(
131 name="auth.authorization.duration", description="Authorization check duration in milliseconds", unit="ms"
132)
134auth_authz_cache_operations = meter.create_counter(
135 name="auth.authorization.cache.operations", description="Authorization cache hits and misses", unit="1"
136)
139# Role Mapping Metrics
141auth_role_mapping_operations = meter.create_counter(
142 name="auth.role_mapping.operations", description="Role mapping operations by mapping type", unit="1"
143)
145auth_role_mapping_rules_applied = meter.create_histogram(
146 name="auth.role_mapping.rules.applied", description="Number of mapping rules applied per user", unit="rules"
147)
149auth_role_mapping_tuples_generated = meter.create_histogram(
150 name="auth.role_mapping.tuples.generated", description="Number of tuples generated by role mapper", unit="tuples"
151)
153auth_role_mapping_duration = meter.create_histogram(
154 name="auth.role_mapping.duration", description="Role mapping duration in milliseconds", unit="ms"
155)
158# Concurrent Session Metrics
160auth_concurrent_sessions = meter.create_histogram(
161 name="auth.concurrent.sessions", description="Number of concurrent sessions per user", unit="sessions"
162)
164auth_session_limit_reached = meter.create_counter(
165 name="auth.session.limit.reached", description="Times concurrent session limit was reached", unit="1"
166)
169# Helper functions for common metric patterns
172def record_login_attempt(provider: str, result: str, duration_ms: float) -> None:
173 """Record login attempt with all relevant metrics"""
174 auth_login_attempts.add(1, {"provider": provider, "result": result})
175 auth_login_duration.record(duration_ms, {"provider": provider})
177 if result != "success":
178 auth_login_failures.add(1, {"provider": provider, "reason": result})
181def record_token_verification(result: str, duration_ms: float, provider: str = "unknown") -> None:
182 """Record token verification metrics"""
183 auth_token_verifications.add(1, {"result": result, "provider": provider})
184 auth_token_verify_duration.record(duration_ms, {"provider": provider})
187def record_session_operation(operation: str, backend: str, result: str, duration_ms: float) -> None:
188 """Record session operation metrics"""
189 if operation == "create":
190 auth_session_created.add(1, {"backend": backend, "result": result})
191 if result == "success":
192 auth_sessions_active.add(1, {"backend": backend})
193 elif operation == "retrieve":
194 auth_session_retrieved.add(1, {"backend": backend, "result": result})
195 elif operation == "refresh":
196 auth_session_refreshed.add(1, {"backend": backend, "result": result})
197 elif operation == "revoke": 197 ↛ 202line 197 didn't jump to line 202 because the condition on line 197 was always true
198 auth_session_revoked.add(1, {"backend": backend, "result": result})
199 if result == "success": 199 ↛ 202line 199 didn't jump to line 202 because the condition on line 199 was always true
200 auth_sessions_active.add(-1, {"backend": backend})
202 auth_session_operations_duration.record(duration_ms, {"operation": operation, "backend": backend})
205def record_jwks_operation(operation: str, result: str, duration_ms: float | None = None) -> None:
206 """Record JWKS cache operation metrics"""
207 if operation in ["hit", "miss"]:
208 auth_jwks_cache_operations.add(1, {"type": operation})
209 elif operation == "refresh": 209 ↛ 212line 209 didn't jump to line 212 because the condition on line 209 was always true
210 auth_jwks_refresh_count.add(1, {"result": result})
212 if duration_ms is not None:
213 auth_jwks_fetch_duration.record(duration_ms)
216def record_openfga_sync(result: str, duration_ms: float, tuple_count: int) -> None:
217 """Record OpenFGA role sync metrics"""
218 auth_openfga_sync_attempts.add(1, {"result": result})
219 auth_openfga_sync_duration.record(duration_ms)
221 if result == "success":
222 auth_openfga_tuples_written.record(tuple_count)
223 else:
224 auth_openfga_sync_errors.add(1, {"result": result})
227def record_role_mapping(mapping_type: str, rules_count: int, tuples_count: int, duration_ms: float) -> None:
228 """Record role mapping metrics"""
229 auth_role_mapping_operations.add(1, {"type": mapping_type})
230 auth_role_mapping_rules_applied.record(rules_count)
231 auth_role_mapping_tuples_generated.record(tuples_count)
232 auth_role_mapping_duration.record(duration_ms)