Best Practices & Process

Understanding Metric Types: Counters, Gauges, and Histograms

Learn metric types: counters, gauges, and histograms. Understand when to use each for better observability, error tracking, and performance monitoring.

If you're building observability into your application, you've probably heard about metric types: counters, gauges, and histograms. These are the three fundamental building blocks of monitoring in systems like Prometheus, and choosing the right one for each measurement directly affects how well you can diagnose issues and understand your system's behavior. Most developers know they should collect metrics, but treating all measurements the same way is a quick path to noisy dashboards and missed signals.

In this guide, we'll walk through each metric type, show you when to use it, and explain why the distinction matters for building reliable, observable systems. By the end, you'll understand how to choose metrics that align with your observability strategy—from tracking error rates to monitoring request latency.

What Are Metric Types?

Prometheus defines three core metric types, each designed to answer different questions about your system:

  • Counters: monotonically increasing values (only go up or reset)
  • Gauges: point-in-time measurements that can go up or down
  • Histograms: distributions that bucket observations over time

These types are more than naming conventions—they determine how you'll aggregate data, run queries, and spot problems. A gauge measuring CPU usage at one snapshot tells you the current state; a counter measuring total HTTP requests tells you about traffic volume over time. Conflating them breaks your analysis.

Counters: Measuring Cumulative Growth

A counter tracks a monotonically increasing value. The classic examples are request counts, errors, or completed transactions. Once incremented, a counter's value never decreases (except on service restart or explicit reset, which is a reset of the counter state, not a decrement).

Here's a Node.js example using the prom-client library:

const prometheus = require('prom-client');

const httpRequestsTotal = new prometheus.Counter({
  name: 'http_requests_total',
  help: 'Total HTTP requests',
  labelNames: ['method', 'route', 'status']
});

// In your request handler
app.get('/api/data', (req, res) => {
  try {
    // ... your logic
    httpRequestsTotal.inc({ method: 'GET', route: '/api/data', status: 200 });
    res.json({ data: 'value' });
  } catch (err) {
    httpRequestsTotal.inc({ method: 'GET', route: '/api/data', status: 500 });
    res.status(500).json({ error: 'Internal server error' });
  }
});

Counters are perfect for calculating rates (e.g., requests per second) via the rate() function in Prometheus queries. They also pair naturally with error tracking: if you log every error with a counter labeled by error type or status code, you can measure both total errors and error rates over sliding windows. This is especially useful when combined with error budget monitoring to understand whether you're consuming your budget too quickly.

Use counters for anything that accumulates: requests, bytes transmitted, cache hits, task completions. When you care about "how fast is this growing?", a counter is your metric type.

Gauges: Snapshots of Current State

A gauge represents a single value that can move up or down—think of a speedometer or thermometer. Queue length, memory usage, active connections, or temperature are all gauges. Unlike counters, gauges have no direction bias; they're just the current state.

Here's an example in Python using prometheus_client:

from prometheus_client import Gauge

in_progress_requests = Gauge(
    'in_progress_requests',
    'Number of HTTP requests in progress',
    labelNames=['handler']
)

@app.before_request
def before():
    in_progress_requests.labels(handler=request.endpoint).inc()

@app.after_request
def after(response):
    in_progress_requests.labels(handler=request.endpoint).dec()
    return response

Gauges are useful for instantaneous system state. However, they're less reliable for detecting trends because a single spike might be missed if scrapes don't align with the spike's timing. You generally compute gauges differently than counters: instead of rate(), you'd query the current value or look at min(), max(), and avg() over a time window.

The danger of treating a gauge like a counter (or vice versa) is subtle. If you model queue depth as a counter, you can't ask "what's the queue length right now?" If you model total transactions as a gauge, you lose the ability to compute throughput. For better error tracking, knowing the difference helps you instrument meaningful signals.

Histograms: Understanding Distributions

A histogram buckets observations into ranges and tracks how many values fell into each bucket. This is ideal for latency measurements: you might have buckets for under 10ms, under 50ms, under 100ms, under 500ms, and so on. Instead of storing every individual request latency (infeasible at scale), you store only the bucket counts.

Here's a Java example using Micrometer (which works with LightTrace's distributed tracing integration):

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;

@RestController
public class ApiController {
    private final MeterRegistry meterRegistry;

    public ApiController(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    @GetMapping("/api/data")
    public ResponseEntity<?> getData() {
        Timer.Sample sample = Timer.start(meterRegistry);
        try {
            // ... your logic
            return ResponseEntity.ok(data);
        } finally {
            sample.stop(Timer.builder("api.request.duration")
                .description("API request latency")
                .tag("endpoint", "/api/data")
                .publishPercentiles(0.5, 0.95, 0.99)
                .register(meterRegistry));
        }
    }
}

Histograms let you compute percentiles (histogram_quantile(0.95, ...) in Prometheus), which is essential for SLO-based alerting. A 99th-percentile latency tells you more about user experience than an average. When you're tracking performance, histograms are usually what you want.

Histograms automatically include a _count metric (total observations) and a _sum metric (sum of all values), so you get both throughput and aggregate latency for free.

Choosing the Right Metric Type

Ask yourself these questions:

  1. Is this value monotonically increasing (or resetting)? → Counter.
  2. Can this value go up and down freely? → Gauge.
  3. Am I interested in the distribution (percentiles, buckets)? → Histogram.

A few practical rules:

  • Error rates: Use a counter for total errors, then compute rate() over time. This pairs well with error triage processes where you want to understand both total volume and recent spikes.
  • Request latency: Use a histogram. Your SLOs depend on percentiles, not averages.
  • Active connections: Gauge. It changes continuously and you care about the current snapshot.
  • Cache hits/misses: Two counters (one for hits, one for misses). Avoid a gauge that somehow represents both—it's confusing.

If you're unsure between counter and gauge, ask: "Do I care about how fast this is growing?" If yes, it's probably a counter. When in doubt, consider how you'll alert on it—alerts on counter rates are common, but alerts on gauges usually track thresholds.

Metric Types in Your Observability Strategy

Metric types don't exist in isolation. They're part of a broader observability practice that combines metrics, structured logging, and distributed tracing. LightTrace, for example, captures error rates, distributed traces across services, and performance monitoring—all of which depend on well-chosen metrics in your application code.

When your application emits counters for request counts and errors, you can:

  • Compute request rates and error rates for dashboards.
  • Set up alert rules based on error budgets and SLOs.
  • Correlate metric spikes with actual issues in your error tracking and tracing data.

When you emit histograms for latency, you gain visibility into performance degradation—especially the tail latencies that affect your users most. And when you emit gauges for resource utilization, you can spot bottlenecks before they cascade into errors. These signals together help you reduce MTTR when outages occur.

The principle is simple: the right metrics make your system transparent. Counters show you throughput and growth; gauges show you state; histograms show you distribution and tail behavior. Get the types right, and your dashboards become a powerful early-warning system for graceful degradation and incident response.

Avoid the anti-pattern of using a single gauge to represent multiple concepts (e.g., "system_health: 0=down, 1=degraded, 2=healthy"). Use structured tags and multiple metrics instead. This keeps queries clear and prevents confusion during incidents.

Putting It All Together

The next time you're instrumenting an application, spend a moment choosing the right metric type. A counter for throughput, a histogram for latency, and a gauge for state will give you much clearer insights than muddled data. Pair these metrics with proper error tracking and tracing to get the complete picture of your system's health.

Start tracking errors in minutes

Ready to put these insights into practice? Try LightTrace free to see how distributed tracing, performance metrics, and error tracking work together. Start free.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.