Skip to content
All essays
ArchitectureJanuary 27, 202514 min

Monitoring with Prometheus and Grafana: Complete Guide

Set up comprehensive monitoring with Prometheus and Grafana. Learn metrics, alerting, and visualization best practices.

Ü
Ümit Uz
Mobile & Full Stack Developer

Why Monitoring?

  • Detect issues early: Fix problems before users notice
  • Understand performance: Identify bottlenecks
  • Make informed decisions: Base decisions on data

Prometheus Architecture

Prometheus collects and stores metrics as time series data.

Key Components

  • Prometheus Server: Scrapes and stores metrics
  • Alertmanager: Handles alerting
  • Pushgateway: For short-lived jobs
  • Exporters: Expose metrics for various services

Installation

yaml
# docker-compose.yml
version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  prometheus_data:
  grafana_data:

Prometheus Configuration

yaml
# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'myapp'
    static_configs:
      - targets: ['myapp:3000']
    metrics_path: '/metrics'

Metrics Types

Counter

javascript
// Only increases, never decreases
const requestCounter = new Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'status']
});

requestCounter.inc({ method: 'GET', status: '200' });

Gauge

javascript
// Can go up or down
const activeConnections = new Gauge({
  name: 'active_connections',
  help: 'Number of active connections'
});

activeConnections.set(42);
activeConnections.inc();
activeConnections.dec();

Histogram

javascript
// Observes values and counts them in configurable buckets
const requestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration',
  buckets: [0.1, 0.5, 1, 2, 5]
});

requestDuration.observe(0.3);

Summary

javascript
// Similar to histogram but calculates quantiles on client side
const responseSize = new Summary({
  name: 'http_response_size_bytes',
  help: 'HTTP response size'
});

responseSize.observe(1024);

Node.js Integration

javascript
import express from 'express';
import promClient from 'prom-client';

const app = express();
const register = new promClient.Registry();

// Default metrics (CPU, memory, etc.)
promClient.collectDefaultMetrics({ register });

// Custom metrics
const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration',
  registers: [register]
});

// Metrics endpoint
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
});

// Use in middleware
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    httpRequestDuration.observe(duration);
  });
  next();
});

Grafana Dashboards

Adding Prometheus Data Source

  1. 1Open Grafana
  2. 2Configuration → Data Sources → Add data source
  3. 3Select Prometheus
  4. 4Set URL to http://prometheus:9090
  5. 5Save & Test

Sample Dashboard Queries

promql
# Request rate
rate(http_requests_total[5m])

# Error rate
rate(http_requests_total{status=~"5.."}[5m])

# P95 latency
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

# CPU usage
rate(process_cpu_seconds_total[5m]) * 100

# Memory usage
process_resident_memory_bytes / 1024 / 1024

Alerting

Alertmanager Configuration

yaml
# alertmanager.yml
global:
  resolve_timeout: 5m

route:
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 12h
  receiver: 'default'

  routes:
  - match:
      severity: critical
    receiver: 'pagerduty'

receivers:
- name: 'default'
  email_configs:
  - to: 'team@example.com'

- name: 'pagerduty'
  pagerduty_configs:
  - service_key: '<service-key>'

Alert Rules

yaml
# alerts.yml
groups:
- name: api
  interval: 30s
  rules:
  - alert: HighErrorRate
    expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "High error rate detected"
      description: "Error rate is {{ $value }} errors/sec"

  - alert: HighLatency
    expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "High latency detected"
      description: "P95 latency is {{ $value }} seconds"

Best Practices

  1. 1Use meaningful labels: Make queries flexible
  2. 2Set appropriate scrape intervals: Balance accuracy and performance
  3. 3Monitor Prometheus: Ensure monitoring system is healthy
  4. 4Use recording rules: Pre-compute expensive queries
  5. 5Test alerts: Verify alerts work as expected
  6. 6Document dashboards: Help team understand metrics
  7. 7Review metrics regularly: Remove unused metrics

Recording Rules

yaml
groups:
- name: api_rules
  interval: 30s
  rules:
  - record: job:http_requests_total:rate5m
    expr: sum(rate(http_requests_total[5m])) by (job)

  - record: job:request_errors:rate5m
    expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)

Conclusion

Prometheus and Grafana provide powerful monitoring capabilities. Start with core metrics and expand based on needs.

Enjoyed this essay? Share it.

Related essays

Next essay
Infrastructure as Code with Terraform: AWS Cloud