Prometheus Metrics Guide
Comprehensive guide to collecting and exposing metrics in Armature using Prometheus.
Table of Contents
- Overview
- Features
- Quick Start
- Metric Types
- Request Metrics
- Business Metrics
- Labels
- Custom Buckets
- Metrics Endpoint
- Best Practices
- Examples
- Summary
Overview
Armature provides built-in Prometheus metrics support through the armature-metrics crate. This allows you to:
- Collect HTTP request metrics automatically
- Create custom business metrics
- Expose metrics via
/metricsendpoint - Integrate with Prometheus/Grafana
Features
- โ Prometheus Integration - Native Prometheus client
- โ Auto HTTP Metrics - Request count, latency, errors
- โ Multiple Metric Types - Counter, Gauge, Histogram
- โ Labels - Multi-dimensional metrics
- โ Custom Buckets - Configurable histogram buckets
- โ
/metricsEndpoint - Standard Prometheus endpoint - โ Business Metrics - Easy custom metric registration
Quick Start
1. Add Dependency
[dependencies]
armature-metrics = "0.1"
2. Create Metrics
use armature_metrics::*;
// Counter
let requests = register_counter("requests_total", "Total requests")?;
requests.inc();
// Gauge
let active_users = register_gauge("active_users", "Active users")?;
active_users.set(42.0);
// Histogram
let latency = register_histogram("request_duration_seconds", "Request duration")?;
latency.observe(0.5);
3. Add Metrics Endpoint
use armature_core::*;
use armature_metrics::*;
let mut router = Router::new();
// Add /metrics endpoint
router.add_route(Route {
method: HttpMethod::GET,
path: "/metrics".to_string(),
handler: create_metrics_handler(),
constraints: None,
});
4. Add Request Metrics Middleware
use std::sync::Arc;
let metrics_middleware = Arc::new(RequestMetricsMiddleware::new());
let app = Application::new()
.router(router)
.middleware(metrics_middleware)
.build();
Metric Types
Counter
Counters only increase over time. Use for counting events.
use armature_metrics::*;
// Simple counter
let counter = register_counter("page_views", "Page views")?;
counter.inc();
counter.inc_by(5.0);
// Counter with builder
let counter = CounterBuilder::new("requests_total", "Total requests")
.register()?;
Use cases:
- Request counts
- Error counts
- Event counts
Gauge
Gauges can increase and decrease. Use for current state.
use armature_metrics::*;
// Simple gauge
let gauge = register_gauge("temperature", "Temperature")?;
gauge.set(72.5);
gauge.inc();
gauge.dec();
gauge.add(10.0);
gauge.sub(5.0);
// Gauge with builder
let gauge = GaugeBuilder::new("active_connections", "Active connections")
.register()?;
Use cases:
- Active connections
- Queue sizes
- Memory usage
Histogram
Histograms sample observations and count them in buckets.
use armature_metrics::*;
// Simple histogram
let histogram = register_histogram("request_duration", "Request duration")?;
histogram.observe(0.5);
// Histogram with builder
let histogram = HistogramBuilder::new("api_latency", "API latency")
.latency_buckets() // Use default latency buckets
.register()?;
// Custom buckets
let histogram = HistogramBuilder::new("response_size", "Response size")
.buckets(vec![100.0, 1000.0, 10000.0, 100000.0])
.register()?;
Use cases:
- Request latency
- Response sizes
- Processing times
Request Metrics
The RequestMetricsMiddleware automatically collects HTTP metrics.
Automatic Metrics
When you add the middleware, these metrics are collected automatically:
| Metric | Type | Description |
|---|---|---|
http_requests_total |
Counter | Total HTTP requests |
http_request_duration_seconds |
Histogram | Request latency |
http_requests_in_flight |
Gauge | Active requests |
http_request_size_bytes |
Histogram | Request size |
http_response_size_bytes |
Histogram | Response size |
Basic Usage
use armature_core::*;
use armature_metrics::*;
use std::sync::Arc;
let metrics_middleware = Arc::new(RequestMetricsMiddleware::new());
let app = Application::new()
.middleware(metrics_middleware)
.build();
Without Path Labels
To reduce cardinality in high-traffic applications:
let metrics_middleware = Arc::new(RequestMetricsMiddleware::without_path());
This groups all paths into a single label value.
Business Metrics
Create custom metrics for your business logic.
Example: E-commerce Metrics
use armature_metrics::*;
// Order metrics
let orders = CounterVecBuilder::new("orders_total", "Total orders")
.labels(&["status", "payment_method"])
.register()?;
orders.with_label_values(&["completed", "credit_card"]).inc();
orders.with_label_values(&["failed", "paypal"]).inc();
// Revenue tracking
let revenue = CounterBuilder::new("revenue_dollars", "Revenue in dollars")
.register()?;
revenue.inc_by(99.99);
// Cart size distribution
let cart_size = HistogramBuilder::new("cart_items", "Items in cart")
.buckets(vec![1.0, 5.0, 10.0, 20.0, 50.0])
.register()?;
cart_size.observe(3.0);
Example: Database Metrics
use armature_metrics::*;
// Query duration by operation
let query_duration = HistogramVecBuilder::new(
"db_query_duration_seconds",
"Database query duration"
)
.labels(&["operation", "table"])
.buckets(vec![0.001, 0.01, 0.1, 0.5, 1.0])
.register()?;
query_duration.with_label_values(&["SELECT", "users"]).observe(0.05);
query_duration.with_label_values(&["INSERT", "orders"]).observe(0.02);
// Connection pool metrics
let db_connections = GaugeVecBuilder::new(
"db_connections",
"Database connections"
)
.labels(&["pool", "state"])
.register()?;
db_connections.with_label_values(&["default", "active"]).set(10.0);
db_connections.with_label_values(&["default", "idle"]).set(5.0);
Labels
Labels add dimensions to metrics for filtering and grouping.
Adding Labels
use armature_metrics::*;
// Counter with labels
let requests = CounterVecBuilder::new("http_requests", "HTTP requests")
.labels(&["method", "endpoint", "status"])
.register()?;
requests.with_label_values(&["GET", "/api/users", "200"]).inc();
requests.with_label_values(&["POST", "/api/orders", "201"]).inc();
requests.with_label_values(&["GET", "/api/users", "500"]).inc();
Label Best Practices
โ Good:
- Use a limited set of label values
- Use labels for dimensions you need to query
- Keep cardinality manageable
// Good - limited values
let requests = CounterVecBuilder::new("requests", "Requests")
.labels(&["method", "status_class"]) // GET/POST/PUT, 2xx/3xx/4xx/5xx
.register()?;
โ Bad:
- Don't use unbounded label values
- Avoid high-cardinality labels
// BAD - unbounded values
let requests = CounterVecBuilder::new("requests", "Requests")
.labels(&["user_id", "timestamp"]) // Millions of combinations!
.register()?;
Custom Buckets
Histograms use buckets to count observations. Choose appropriate buckets for your use case.
Default Buckets
use armature_metrics::*;
// Latency buckets (milliseconds to seconds)
let histogram = HistogramBuilder::new("latency", "Latency")
.latency_buckets() // 0.001, 0.005, 0.01, ..., 10.0
.register()?;
// Size buckets (bytes)
let histogram = HistogramBuilder::new("size", "Size")
.size_buckets() // 100, 1000, 10000, ..., 100000000
.register()?;
Custom Buckets
// API response times (50ms to 5s)
let histogram = HistogramBuilder::new("api_duration", "API duration")
.buckets(vec![0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0])
.register()?;
// File sizes (1KB to 1GB)
let histogram = HistogramBuilder::new("file_size", "File size")
.buckets(vec![
1_000.0,
10_000.0,
100_000.0,
1_000_000.0,
10_000_000.0,
100_000_000.0,
1_000_000_000.0,
])
.register()?;
Bucket Guidelines
- Too few buckets - Loss of precision
- Too many buckets - Increased memory/storage
- Sweet spot - 10-20 buckets covering your expected range
Metrics Endpoint
The /metrics endpoint exposes metrics in Prometheus format.
Adding the Endpoint
use armature_core::*;
use armature_metrics::*;
let mut router = Router::new();
// Method 1: Using helper function
router.add_route(Route {
method: HttpMethod::GET,
path: "/metrics".to_string(),
handler: create_metrics_handler(),
constraints: None,
});
// Method 2: Using handler directly
router.add_route(Route {
method: HttpMethod::GET,
path: "/metrics".to_string(),
handler: Arc::new(|req| {
Box::pin(async move {
metrics_handler(req).await
})
}),
constraints: None,
});
Prometheus Configuration
Add to your prometheus.yml:
scrape_configs:
- job_name: 'armature-app'
static_configs:
- targets: ['localhost:3000']
metrics_path: '/metrics'
scrape_interval: 15s
Best Practices
1. Choose the Right Metric Type
- Counter - Things that only increase (requests, errors, sales)
- Gauge - Things that go up and down (temperature, active connections, queue size)
- Histogram - Distributions (latency, size, duration)
2. Use Meaningful Names
// โ
Good - descriptive
let counter = register_counter("http_requests_total", "Total HTTP requests")?;
let gauge = register_gauge("active_database_connections", "Active DB connections")?;
let histogram = register_histogram("api_request_duration_seconds", "API duration")?;
// โ Bad - vague
let counter = register_counter("counter1", "Counter")?;
let gauge = register_gauge("active", "Active")?;
3. Follow Naming Conventions
- Use snake_case for metric names
- Include units in the name (
_seconds,_bytes,_total) - Be descriptive but concise
// Good examples
"http_requests_total"
"database_query_duration_seconds"
"response_size_bytes"
"active_connections"
"errors_total"
4. Manage Cardinality
Avoid creating millions of unique metric combinations:
// โ
Good - bounded cardinality
let metric = CounterVecBuilder::new("requests", "Requests")
.labels(&["method", "status_class"]) // ~20 combinations
.register()?;
// โ Bad - unbounded cardinality
let metric = CounterVecBuilder::new("requests", "Requests")
.labels(&["user_id", "session_id", "timestamp"]) // Millions!
.register()?;
5. Use Appropriate Buckets
// โ
Good - covers expected range
let histogram = HistogramBuilder::new("api_latency", "API latency")
.buckets(vec![0.01, 0.05, 0.1, 0.5, 1.0, 5.0]) // 10ms to 5s
.register()?;
// โ Bad - too narrow
let histogram = HistogramBuilder::new("api_latency", "API latency")
.buckets(vec![0.1, 0.2, 0.3]) // Only 100-300ms
.register()?;
Summary
Key Points:
- Three metric types - Counter, Gauge, Histogram
- Auto HTTP metrics - Use
RequestMetricsMiddleware - Custom business metrics - Track domain-specific events
- Labels for dimensions - But manage cardinality
/metricsendpoint - Standard Prometheus format- Choose appropriate buckets - Match your use case
Quick Reference:
// Counter
let counter = register_counter("name", "help")?;
counter.inc();
// Gauge
let gauge = register_gauge("name", "help")?;
gauge.set(42.0);
// Histogram
let histogram = register_histogram("name", "help")?;
histogram.observe(0.5);
// With labels
let metric = CounterVecBuilder::new("name", "help")
.labels(&["label1", "label2"])
.register()?;
metric.with_label_values(&["value1", "value2"]).inc();
// Metrics endpoint
router.add_route(Route {
handler: create_metrics_handler(),
..route
});
// Request metrics middleware
let app = Application::new()
.middleware(Arc::new(RequestMetricsMiddleware::new()))
.build();
Resources: