Skipping health/metrics endpoints (noise reduction)
Health checks and readiness probes run every few seconds. With 12 replicas and a 5-second interval, that’s 17,000 log entries per day per endpoint—before a single real request. This noise buries actual traffic and inflates log storage costs.
fapilog’s path filtering skips these endpoints while logging everything else.
The Problem
A typical Kubernetes deployment generates massive log volume from health checks:
# Per instance, per endpoint
1 check × 12 checks/minute × 60 minutes × 24 hours = 17,280 logs/day
# With 10 instances and 3 health endpoints (/health, /ready, /metrics)
17,280 × 10 × 3 = 518,400 logs/day
These logs are identical, contain no useful information, and obscure the logs that matter—actual user requests, errors, and business events.
The Solution
Skip health endpoints with skip_paths():
from fastapi import FastAPI
from fapilog.fastapi import FastAPIBuilder
app = FastAPI(
lifespan=FastAPIBuilder()
.with_preset("fastapi")
.skip_paths([
"/health",
"/healthz",
"/ready",
"/readiness",
"/metrics",
"/ping",
])
.build()
)
Requests to these paths are processed normally—they just don’t generate log entries.
Common Patterns to Skip
Pattern |
Used By |
Purpose |
|---|---|---|
|
Kubernetes, AWS ALB |
Liveness checks |
|
Kubernetes |
Readiness probes |
|
Prometheus |
Metrics scraping |
|
Load balancers, uptime monitors |
Basic availability |
|
Kubernetes (newer) |
Liveness (alternative) |
Kubernetes-Focused Setup
app = FastAPI(
lifespan=FastAPIBuilder()
.with_preset("fastapi")
.skip_paths(["/health", "/healthz", "/ready", "/readiness", "/livez"])
.build()
)
AWS / Generic Load Balancer Setup
app = FastAPI(
lifespan=FastAPIBuilder()
.with_preset("fastapi")
.skip_paths(["/health", "/ping", "/_health"])
.build()
)
Pattern Matching Syntax
skip_paths uses exact string matching:
Path in Request |
|
Logged? |
|---|---|---|
|
Match |
No |
|
No match |
Yes |
|
No match |
Yes |
|
No match |
Yes |
|
No match |
Yes |
This means:
Case-sensitive:
/Healthand/healthare differentNo wildcards:
/health/*won’t match anything (it’s a literal string)No regex: Use exact paths only
Trailing slashes matter: Include both
/healthand/health/if your API accepts both
Complete Health Check Coverage
If your endpoints accept trailing slashes or subpaths, list each variant:
app = FastAPI(
lifespan=FastAPIBuilder()
.with_preset("fastapi")
.skip_paths([
"/health",
"/health/",
"/ready",
"/ready/",
"/metrics",
])
.build()
)
Manual Middleware Configuration
If you need more control, configure the middleware directly:
from fastapi import FastAPI
from fapilog.fastapi.logging import LoggingMiddleware
from fapilog.fastapi.context import RequestContextMiddleware
app = FastAPI()
app.add_middleware(RequestContextMiddleware)
app.add_middleware(
LoggingMiddleware,
skip_paths=["/health", "/metrics"],
)
This gives identical behavior to FastAPIBuilder().skip_paths(...).
Impact on Log Volume
Skipping health endpoints typically reduces log volume by 50-90%, depending on:
Check frequency: 5-second intervals generate more than 30-second intervals
Number of instances: More replicas = more health checks
Number of health endpoints: Some setups have 3-5 different health paths
Actual traffic: Low-traffic services see the biggest percentage reduction
Before and After
Metric |
Before |
After (with skip_paths) |
|---|---|---|
Logs per day (10 instances) |
520,000 |
52,000 |
Storage cost |
$15/month |
$1.50/month |
Time to find errors |
Minutes |
Seconds |
The real benefit isn’t just cost—it’s signal-to-noise ratio. When health checks are 90% of your logs, finding an actual error requires wading through noise.
Errors on Skipped Paths
By default, unhandled exceptions on skipped paths are still logged at ERROR level. This ensures visibility into crashes even on health endpoints:
@app.get("/health")
async def health():
raise RuntimeError("Database connection failed") # Logged as request_failed
When the handler crashes, you’ll see request_failed logs for /health even though successful health checks are skipped.
Note: HTTPExceptions (like HTTPException(status_code=503)) are not logged because they’re handled by FastAPI’s exception handler and converted to responses—they’re expected error responses, not crashes.
Opting Out
If you need complete silence on skipped paths (no logs at all, even for crashes), use log_errors_on_skip(False):
app = FastAPI(
lifespan=FastAPIBuilder()
.with_preset("fastapi")
.skip_paths(["/health", "/metrics"])
.log_errors_on_skip(False) # Complete silence on skipped paths
.build()
)
This is useful when health endpoints are monitored externally and you don’t want any fapilog log entries for them.
Combining with Sampling
For high-traffic endpoints that aren’t health checks, combine path filtering with sampling:
app = FastAPI(
lifespan=FastAPIBuilder()
.with_preset("fastapi")
.skip_paths(["/health", "/metrics"]) # Skip entirely
.sample_rate(0.1) # Log 10% of other requests
.build()
)
This setup:
Never logs
/healthor/metricsLogs 10% of all other successful requests
Always logs errors (sample_rate doesn’t affect error logs)
Going Deeper
FastAPI Integration Guide - Complete middleware options
Log Sampling and Rate Limiting - Sampling and rate limiting for traffic spikes
Why Fapilog? - How fapilog compares to other logging libraries