Prometheus Metrics Guide

Comprehensive guide to collecting and exposing metrics in Armature using Prometheus.

Table of Contents


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 /metrics endpoint
  • 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
  • โœ… /metrics Endpoint - 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:

  1. Three metric types - Counter, Gauge, Histogram
  2. Auto HTTP metrics - Use RequestMetricsMiddleware
  3. Custom business metrics - Track domain-specific events
  4. Labels for dimensions - But manage cardinality
  5. /metrics endpoint - Standard Prometheus format
  6. 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: