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

1""" 

2Authentication Metrics for Observability 

3 

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""" 

11 

12from opentelemetry import metrics as otel_metrics 

13 

14# Get meter for auth metrics 

15meter = otel_metrics.get_meter(__name__) 

16 

17 

18# Authentication Metrics 

19 

20auth_login_attempts = meter.create_counter( 

21 name="auth.login.attempts", description="Number of login attempts by provider and result", unit="1" 

22) 

23 

24auth_login_duration = meter.create_histogram( 

25 name="auth.login.duration", description="Login duration in milliseconds", unit="ms" 

26) 

27 

28auth_login_failures = meter.create_counter(name="auth.login.failures", description="Login failures by reason", unit="1") 

29 

30 

31# Token Management Metrics 

32 

33auth_token_created = meter.create_counter(name="auth.token.created", description="Tokens created by provider", unit="1") 

34 

35auth_token_verifications = meter.create_counter( 

36 name="auth.token.verifications", description="Token verification attempts by result", unit="1" 

37) 

38 

39auth_token_verify_duration = meter.create_histogram( 

40 name="auth.token.verify.duration", description="Token verification duration in milliseconds", unit="ms" 

41) 

42 

43auth_token_refresh_attempts = meter.create_counter( 

44 name="auth.token.refresh.attempts", description="Token refresh attempts by result", unit="1" 

45) 

46 

47 

48# JWKS Cache Metrics 

49 

50auth_jwks_cache_operations = meter.create_counter( 

51 name="auth.jwks.cache.operations", description="JWKS cache hits and misses", unit="1" 

52) 

53 

54auth_jwks_fetch_duration = meter.create_histogram( 

55 name="auth.jwks.fetch.duration", description="JWKS fetch duration in milliseconds", unit="ms" 

56) 

57 

58auth_jwks_refresh_count = meter.create_counter(name="auth.jwks.refresh", description="JWKS cache refresh count", unit="1") 

59 

60 

61# Session Management Metrics 

62 

63auth_sessions_active = meter.create_up_down_counter( 

64 name="auth.sessions.active", description="Number of active sessions", unit="1" 

65) 

66 

67auth_session_created = meter.create_counter(name="auth.session.created", description="Sessions created by backend", unit="1") 

68 

69auth_session_retrieved = meter.create_counter( 

70 name="auth.session.retrieved", description="Session retrieval attempts by result", unit="1" 

71) 

72 

73auth_session_refreshed = meter.create_counter(name="auth.session.refreshed", description="Sessions refreshed", unit="1") 

74 

75auth_session_revoked = meter.create_counter(name="auth.session.revoked", description="Sessions revoked (deleted)", unit="1") 

76 

77auth_session_expired = meter.create_counter( 

78 name="auth.session.expired", description="Sessions that expired naturally", unit="1" 

79) 

80 

81auth_session_duration = meter.create_histogram( 

82 name="auth.session.duration", description="Session lifetime in seconds", unit="seconds" 

83) 

84 

85auth_session_operations_duration = meter.create_histogram( 

86 name="auth.session.operations.duration", description="Session operation duration in milliseconds", unit="ms" 

87) 

88 

89 

90# User Provider Metrics 

91 

92auth_provider_calls = meter.create_counter( 

93 name="auth.provider.calls", description="User provider method calls by provider and method", unit="1" 

94) 

95 

96auth_provider_duration = meter.create_histogram( 

97 name="auth.provider.duration", description="User provider operation duration", unit="ms" 

98) 

99 

100auth_provider_errors = meter.create_counter( 

101 name="auth.provider.errors", description="User provider errors by provider and error type", unit="1" 

102) 

103 

104 

105# OpenFGA Integration Metrics 

106 

107auth_openfga_sync_attempts = meter.create_counter( 

108 name="auth.openfga.sync.attempts", description="OpenFGA role sync attempts by result", unit="1" 

109) 

110 

111auth_openfga_sync_duration = meter.create_histogram( 

112 name="auth.openfga.sync.duration", description="OpenFGA role sync duration in milliseconds", unit="ms" 

113) 

114 

115auth_openfga_tuples_written = meter.create_histogram( 

116 name="auth.openfga.tuples.written", description="Number of tuples written per sync", unit="tuples" 

117) 

118 

119auth_openfga_sync_errors = meter.create_counter( 

120 name="auth.openfga.sync.errors", description="OpenFGA sync errors by error type", unit="1" 

121) 

122 

123 

124# Authorization Metrics 

125 

126auth_authz_checks = meter.create_counter( 

127 name="auth.authorization.checks", description="Authorization checks by result", unit="1" 

128) 

129 

130auth_authz_duration = meter.create_histogram( 

131 name="auth.authorization.duration", description="Authorization check duration in milliseconds", unit="ms" 

132) 

133 

134auth_authz_cache_operations = meter.create_counter( 

135 name="auth.authorization.cache.operations", description="Authorization cache hits and misses", unit="1" 

136) 

137 

138 

139# Role Mapping Metrics 

140 

141auth_role_mapping_operations = meter.create_counter( 

142 name="auth.role_mapping.operations", description="Role mapping operations by mapping type", unit="1" 

143) 

144 

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) 

148 

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) 

152 

153auth_role_mapping_duration = meter.create_histogram( 

154 name="auth.role_mapping.duration", description="Role mapping duration in milliseconds", unit="ms" 

155) 

156 

157 

158# Concurrent Session Metrics 

159 

160auth_concurrent_sessions = meter.create_histogram( 

161 name="auth.concurrent.sessions", description="Number of concurrent sessions per user", unit="sessions" 

162) 

163 

164auth_session_limit_reached = meter.create_counter( 

165 name="auth.session.limit.reached", description="Times concurrent session limit was reached", unit="1" 

166) 

167 

168 

169# Helper functions for common metric patterns 

170 

171 

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}) 

176 

177 if result != "success": 

178 auth_login_failures.add(1, {"provider": provider, "reason": result}) 

179 

180 

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}) 

185 

186 

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}) 

201 

202 auth_session_operations_duration.record(duration_ms, {"operation": operation, "backend": backend}) 

203 

204 

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}) 

211 

212 if duration_ms is not None: 

213 auth_jwks_fetch_duration.record(duration_ms) 

214 

215 

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) 

220 

221 if result == "success": 

222 auth_openfga_tuples_written.record(tuple_count) 

223 else: 

224 auth_openfga_sync_errors.add(1, {"result": result}) 

225 

226 

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)