Coverage for metrics_config.py: 69.23%

13 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2026-04-15 17:36 +0000

1""" 

2Prometheus Metrics Configuration for FastAPI 

3 

4Exposes a /metrics endpoint with standard HTTP metrics: 

5- http_requests_total: Counter of requests by method, path, status 

6- http_request_duration_seconds: Histogram of request latency 

7- http_requests_in_progress: Gauge of concurrent requests 

8 

9These metrics are scraped by Grafana Alloy and sent to Grafana Cloud. 

10 

11Usage: 

12 from metrics_config import setup_metrics 

13 setup_metrics(app) 

14""" 

15 

16import re 

17 

18from prometheus_fastapi_instrumentator import Instrumentator 

19from prometheus_fastapi_instrumentator.metrics import latency, requests 

20 

21 

22def normalize_path(path: str) -> str: 

23 """ 

24 Normalize dynamic path segments to prevent cardinality explosion. 

25 

26 Examples: 

27 /api/users/123 -> /api/users/{id} 

28 /api/matches/abc-def-123 -> /api/matches/{id} 

29 /api/teams/5/players/10 -> /api/teams/{id}/players/{id} 

30 """ 

31 # UUID pattern (with or without hyphens) 

32 path = re.sub(r"/[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}", "/{id}", path) 

33 # Numeric IDs 

34 path = re.sub(r"/\d+", "/{id}", path) 

35 # Short hex IDs (8+ chars) 

36 path = re.sub(r"/[0-9a-f]{8,}", "/{id}", path) 

37 return path 

38 

39 

40def setup_metrics(app): 

41 """ 

42 Configure Prometheus metrics instrumentation for FastAPI. 

43 

44 Adds a /metrics endpoint that exposes: 

45 - Request counts by method, path, status code 

46 - Request duration histograms 

47 - In-progress request gauge 

48 

49 Args: 

50 app: FastAPI application instance 

51 """ 

52 instrumentator = Instrumentator( 

53 should_group_status_codes=False, # Keep exact status codes (200, 201, 500, etc.) 

54 should_ignore_untemplated=True, # Ignore requests without route templates 

55 should_respect_env_var=False, # Always enable (don't check ENABLE_METRICS env) 

56 should_instrument_requests_inprogress=True, # Track concurrent requests 

57 excluded_handlers=[ 

58 "/health", 

59 "/healthz", 

60 "/ready", 

61 "/livez", 

62 "/metrics", # Don't instrument the metrics endpoint itself 

63 ], 

64 inprogress_name="http_requests_in_progress", 

65 inprogress_labels=True, 

66 ) 

67 

68 # Add default metrics with path normalization 

69 instrumentator.add( 

70 requests( 

71 metric_name="http_requests", 

72 metric_doc="Total HTTP requests", 

73 metric_namespace="", 

74 metric_subsystem="", 

75 should_include_handler=True, 

76 should_include_method=True, 

77 should_include_status=True, 

78 ) 

79 ).add( 

80 latency( 

81 metric_name="http_request_duration_seconds", 

82 metric_doc="HTTP request duration in seconds", 

83 metric_namespace="", 

84 metric_subsystem="", 

85 should_include_handler=True, 

86 should_include_method=True, 

87 should_include_status=True, 

88 buckets=(0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 10.0), 

89 ) 

90 ) 

91 

92 # Instrument the app and expose /metrics endpoint 

93 instrumentator.instrument(app).expose( 

94 app, 

95 endpoint="/metrics", 

96 include_in_schema=False, # Hide from OpenAPI docs 

97 should_gzip=False, # Prometheus prefers uncompressed 

98 ) 

99 

100 return instrumentator