Skip to main content

πŸ“Š Application Monitoring Strategies

You shipped your app to Kubernetes β€” now what? Monitoring is how you find out whether users are happy or quietly rage-quitting. This lesson gives you a mental model for observability, three battle-tested metric frameworks (RED, USE, and the Four Golden Signals), and the code to instrument a real service and alert on it without drowning in false alarms.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Distinguish monitoring from observability and describe the three pillars (metrics, logs, traces)
  • Apply the RED, USE, and Four Golden Signals methods to choose the right metrics
  • Instrument a Node.js service with the Prometheus client and expose a /metrics endpoint
  • Write SLO-based alerts that are actionable, not noisy, and understand error budgets
  • Compare the modern monitoring tool ecosystem and pick sensible defaults

Estimated Time: 35–45 minutes  β€’  Difficulty: Intermediate

Hands-on: Instrument a small Express service and define one meaningful SLO alert.

In This Lesson

Why Monitoring Matters

In a world of distributed systems, microservices, and cloud-native deployments, you cannot fix what you cannot see. Monitoring is the practice of collecting and inspecting signals from a running system so you can answer two questions fast: Is it working right now? and Is it about to stop working?

πŸ’‘ A useful analogy: Running a service without monitoring is like being a chef who never tastes the food, never checks whether customers are happy, and never times how long orders take. You might be fine β€” or the kitchen might be on fire and you would be the last to know.

Good monitoring is not about collecting every possible number. It is about collecting the right numbers so that when a pager goes off at 3 a.m., the person on call can understand and fix the problem quickly. Everything in this lesson serves that goal.

Monitoring vs. Observability

These two words get used interchangeably, but they mean different things:

  • Monitoring answers known questions β€” "Is CPU above 90%?" You decide in advance what to watch and set alerts on it.
  • Observability is the property of being able to answer new, unanticipated questions about your system from the data it already emits β€” "Why are only Android users in Brazil seeing slow checkouts?"

Observability rests on three complementary pillars. You need all three; each answers a different kind of question.

The three pillars of observability Three columns β€” Metrics, Logs, and Traces β€” each labelled with what it answers, sitting on a shared base labelled Observability. Metrics numbers over time "How much? How fast?" cheap Β· aggregatable Logs discrete events "What exactly happened?" detailed Β· verbose Traces request journeys "Where did time go?" per-request Β· linked Observability
Figure 1 β€” Metrics tell you that something is wrong, logs and traces tell you what and where. This lesson focuses on metrics; the next two cover logs and the full stack.

The Monitoring Pyramid

Not all metrics are equal. A useful way to organize them is as a pyramid: infrastructure at the base, user experience at the top. Lower layers are easy to measure but far from the user; higher layers are closer to what actually matters but harder to instrument.

flowchart TD L1[Infrastructure metrics
CPU Β· memory Β· disk Β· network] --> L2[Application metrics
latency Β· error rate Β· throughput] L2 --> L3[Business metrics
signups Β· orders Β· revenue] L3 --> L4[User experience
page load Β· Core Web Vitals]
LayerWhat it measuresExample question
InfrastructureThe machines and networkIs a node running out of memory?
ApplicationYour service's behaviorIs the checkout API returning 500s?
BusinessOutcomes that pay the billsDid orders per minute just drop 40%?
User experienceHow it feels to a real personAre pages taking 4 seconds to load?

⚠️ Don't over-index on the bottom layer

A server can sit at 30% CPU while every checkout fails. Infrastructure metrics are necessary but not sufficient β€” always pair them with application and business metrics that reflect real user impact.

RED, USE & the Four Golden Signals

Rather than inventing metrics from scratch, lean on three well-known frameworks. Each answers "which handful of numbers should I watch?" for a different kind of thing.

The RED method β€” for request-driven services

Coined by Tom Wilkie, RED is perfect for APIs and microservices:

  • Rate β€” requests per second the service is handling
  • Errors β€” how many of those requests are failing
  • Duration β€” how long requests take (as a distribution, not an average)

These map neatly to PromQL. Here is RED for an HTTP service:

# Rate β€” requests per second, averaged over 5 minutes
sum(rate(http_requests_total[5m]))

# Errors β€” rate of 5xx responses
sum(rate(http_requests_total{status=~"5.."}[5m]))

# Duration β€” 95th percentile latency in seconds
histogram_quantile(0.95,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le))

The USE method β€” for resources

Brendan Gregg's USE method is the counterpart for infrastructure resources (CPUs, disks, network links):

  • Utilization β€” the percentage of time the resource is busy
  • Saturation β€” how much extra work is queued and waiting
  • Errors β€” the count of error events
πŸ›£οΈ Highway analogy: Utilization is how many cars are on the road, saturation is the queue backing up at the on-ramp, and errors are the crashes blocking a lane. A road can be 100% utilized and still flowing smoothly β€” it is saturation that signals real trouble.

The Four Golden Signals β€” Google SRE's default

The Google SRE book distills monitoring to four signals that combine ideas from both methods:

SignalMeaningRestaurant analogy
LatencyTime to serve a requestHow fast food reaches the table
TrafficDemand on the systemHow many customers walk in
ErrorsRate of failed requestsHow many dishes get sent back
SaturationHow "full" the service isHow close the kitchen is to capacity

If you remember nothing else, start every new service with these four. They catch the overwhelming majority of user-facing problems.

Instrumenting a Service

Instrumentation is the code that emits metrics. Prometheus uses a pull model: your app exposes a plain-text /metrics endpoint, and Prometheus scrapes it on an interval. Here is a modern Express service instrumented with prom-client, capturing the Rate, Errors, and Duration of RED in one middleware.

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

const app = express();

// A registry holds all metrics; default metrics add Node.js process stats
const register = new client.Registry();
client.collectDefaultMetrics({ register });

// Rate + Errors: a counter labelled by method, route, and status
const httpRequestsTotal = new client.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status'],
  registers: [register],
});

// Duration: a histogram bucketed by response time
const httpRequestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status'],
  buckets: [0.05, 0.1, 0.3, 0.5, 1, 3, 5],
  registers: [register],
});

// One middleware records both metrics when the response finishes
app.use((req, res, next) => {
  const stopTimer = httpRequestDuration.startTimer();
  res.on('finish', () => {
    const route = req.route?.path ?? req.path;
    const labels = { method: req.method, route, status: res.statusCode };
    httpRequestsTotal.inc(labels);
    stopTimer(labels);
  });
  next();
});

app.get('/hello', (req, res) => res.json({ message: 'Hello World!' }));

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

app.listen(3000, () => console.log('Listening on http://localhost:3000'));

A scrape of /metrics returns lines like:

http_requests_total{method="GET",route="/hello",status="200"} 42
http_request_duration_seconds_bucket{le="0.1",method="GET",route="/hello",status="200"} 40
http_request_duration_seconds_sum{method="GET",route="/hello",status="200"} 1.83
http_request_duration_seconds_count{method="GET",route="/hello",status="200"} 42

πŸ“– Key Terms

Exporter: a small program that translates a system's stats into Prometheus format (e.g. node_exporter for host metrics).

Scrape: Prometheus fetching the /metrics endpoint on a fixed interval.

Label: a key–value pair (like route="/hello") that adds a dimension you can filter and group by.

⚠️ Watch your label cardinality

Every unique combination of label values creates a new time series. Never put unbounded values (user IDs, full URLs with query strings, timestamps) in labels β€” you will explode Prometheus's memory. Use the route pattern (/users/:id), never the concrete path.

Python and Java have equivalent libraries β€” prometheus_client and Micrometer β€” and the OpenTelemetry SDK offers a vendor-neutral option that works across all of them.

SLOs, SLIs & Alerting

Monitoring without alerting is just passive watching. But alerting badly is worse than not alerting at all β€” nothing burns out an on-call team faster than a pager that cries wolf. The discipline that fixes this is defining alerts against service level objectives.

πŸ“– The SLI / SLO / SLA hierarchy

SLI (Indicator): a measured number, e.g. "the fraction of requests served in under 300 ms."

SLO (Objective): the target you hold that indicator to, e.g. "99.9% of requests under 300 ms over 30 days."

SLA (Agreement): a contract with customers, with financial penalties, usually looser than your internal SLO.

flowchart LR SLI[SLI: measured performance] --> C{Meets SLO?} SLO[SLO: target] --> C C -->|Within budget| OK[No alert] C -->|Budget burning fast| A[Page a human]

The gap between "100%" and your SLO is your error budget. A 99.9% availability SLO means you are allowed to fail 0.1% of requests β€” roughly 43 minutes a month. Alert not on every error, but when you are burning that budget fast enough to run out. Here is a good and a bad alert:

# prometheus/rules/alerts.yml
groups:
  - name: api-slo
    rules:
      # GOOD: symptom-based, tied to user-facing latency, waits 5m to avoid flapping
      - alert: HighRequestLatency
        expr: |
          histogram_quantile(0.95,
            sum(rate(http_request_duration_seconds_bucket{job="api"}[5m])) by (le)
          ) > 0.5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "p95 latency above 500ms on the API"
          description: "95th percentile latency has exceeded 500ms for 5 minutes."
          runbook: "https://runbooks.example.com/high-latency"

      # GOOD: fast error-budget burn β€” page immediately
      - alert: ErrorBudgetBurn
        expr: |
          sum(rate(http_requests_total{status=~"5..",job="api"}[5m]))
            / sum(rate(http_requests_total{job="api"}[5m])) > 0.01
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Error rate above 1% β€” burning error budget"
          description: "More than 1% of requests are failing over the last 5 minutes."

πŸ’‘ Every alert needs a runbook

Attach a runbook link to each alert pointing at a short doc: what this alert means, how to confirm it is real, and the first three things to try. A page without a runbook forces the responder to reinvent the investigation every time.

The Tool Ecosystem

The landscape is large, but tools cluster into a few clear categories. You rarely pick just one β€” a typical stack combines a metrics tool, a dashboarding tool, a log store, and an alert router.

CategoryPopular optionsWhat it does
Metrics & TSDBPrometheus, VictoriaMetrics, InfluxDB, DatadogCollect and store time-series numbers
VisualizationGrafana, KibanaDashboards and charts over your data
APM & tracingOpenTelemetry, Jaeger, Tempo, DynatraceDistributed traces and code-level insight
Log managementLoki, ELK/OpenSearch, SplunkAggregate, search, and analyze logs
Alert routingAlertmanager, PagerDuty, OpsgenieDeduplicate, group, and page the right person
Synthetic checksBlackbox exporter, Pingdom, ChecklyProbe endpoints from outside like a user

βœ… A sensible open-source default

For most teams starting out: Prometheus for metrics, Grafana for dashboards, Loki for logs, Alertmanager for routing, and OpenTelemetry to instrument once and stay vendor-neutral. It is free, runs anywhere, and every concept transfers to paid SaaS tools later. The next two lessons build exactly this stack.

Hands-on Exercise

πŸ‹οΈ Instrument & alert on a service

Objective: Add RED metrics to a small Express app and define one meaningful SLO alert.

Instructions:

  1. Take the instrumented Express app from the "Instrumenting a Service" section and run it locally (npm install express prom-client).
  2. Add a second route, /slow, that waits 400–800 ms before responding, and a /flaky route that returns a 500 about 10% of the time.
  3. Hit all three routes a few dozen times (a for loop with curl works), then open /metrics and find your http_requests_total and duration lines.
  4. Write a PromQL expression for the error rate of /flaky as a percentage.
  5. Write one alert rule: page if p95 latency across the service exceeds 500 ms for 5 minutes.
πŸ’‘ Hint

For the error rate, divide the rate of status=~"5.." requests by the rate of all requests and multiply by 100. For the alert, reuse the HighRequestLatency rule shown earlier β€” the histogram_quantile(0.95, ...) pattern is exactly what you need.

βœ… Example solution

Error rate of /flaky:

sum(rate(http_requests_total{route="/flaky",status=~"5.."}[5m]))
  / sum(rate(http_requests_total{route="/flaky"}[5m])) * 100

Latency alert:

- alert: HighRequestLatency
  expr: |
    histogram_quantile(0.95,
      sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) > 0.5
  for: 5m
  labels: { severity: warning }
  annotations:
    summary: "p95 latency above 500ms"

The /slow route should push your p95 well past 0.5 s, firing the alert after the 5-minute for window β€” proof your instrumentation and rule work end to end.

Best Practices

βœ… Do❌ Avoid
Monitor from the user's perspective firstAlerting only on CPU and memory
Alert on symptoms (errors, latency)Alerting on causes that may be harmless
Establish baselines before setting thresholdsGuessing thresholds out of thin air
Attach a runbook to every alertPages with no guidance on what to do
Keep label cardinality boundedPutting user IDs or raw URLs in labels
Review and prune noisy alerts regularlyLetting the team learn to ignore the pager
πŸ’‘ The monitoring test: for every alert you create, ask "if this fires at 3 a.m., is there something a human must do right now?" If the answer is no, it should be a dashboard or a ticket β€” not a page.

Summary & Quiz

πŸŽ‰ Key Takeaways

  • Monitoring answers known questions; observability lets you answer new ones from metrics, logs, and traces.
  • Organize metrics as a pyramid β€” infrastructure up to user experience β€” and never stop at the bottom layer.
  • Use RED for services, USE for resources, and the Four Golden Signals as a universal starting point.
  • Instrument your app to expose /metrics, keeping label cardinality bounded.
  • Alert against SLOs and error budgets on symptoms, with a runbook on every page.

🎯 Quick Quiz

Question 1: Which method's three metrics β€” Rate, Errors, Duration β€” are the best fit for monitoring a request-driven HTTP microservice?

Question 2: Why is putting a raw user ID into a Prometheus metric label a mistake?

Question 3: An SLO of 99.9% availability over 30 days gives you an "error budget." What does that budget represent?

πŸ“š Further Reading

πŸš€ What's Next?

Metrics tell you that something broke. To learn what exactly happened, you need logs. Next we build a centralized logging pipeline β€” structured logs, shippers, and a searchable store β€” that complements everything you just learned.

πŸŽ‰ Great work!

You can now choose the right metrics, instrument a service, and alert like an SRE. On to logging.