Skip to content
All essays
CraftJanuary 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.

Related essays

Next essay
Infrastructure as Code with Terraform: AWS Cloud