π 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
/metricsendpoint - 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 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.
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]
| Layer | What it measures | Example question |
|---|---|---|
| Infrastructure | The machines and network | Is a node running out of memory? |
| Application | Your service's behavior | Is the checkout API returning 500s? |
| Business | Outcomes that pay the bills | Did orders per minute just drop 40%? |
| User experience | How it feels to a real person | Are 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:
| Signal | Meaning | Restaurant analogy |
|---|---|---|
| Latency | Time to serve a request | How fast food reaches the table |
| Traffic | Demand on the system | How many customers walk in |
| Errors | Rate of failed requests | How many dishes get sent back |
| Saturation | How "full" the service is | How 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.
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.
| Category | Popular options | What it does |
|---|---|---|
| Metrics & TSDB | Prometheus, VictoriaMetrics, InfluxDB, Datadog | Collect and store time-series numbers |
| Visualization | Grafana, Kibana | Dashboards and charts over your data |
| APM & tracing | OpenTelemetry, Jaeger, Tempo, Dynatrace | Distributed traces and code-level insight |
| Log management | Loki, ELK/OpenSearch, Splunk | Aggregate, search, and analyze logs |
| Alert routing | Alertmanager, PagerDuty, Opsgenie | Deduplicate, group, and page the right person |
| Synthetic checks | Blackbox exporter, Pingdom, Checkly | Probe 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:
- Take the instrumented Express app from the "Instrumenting a Service" section and run it locally (
npm install express prom-client). - Add a second route,
/slow, that waits 400β800 ms before responding, and a/flakyroute that returns a 500 about 10% of the time. - Hit all three routes a few dozen times (a
forloop withcurlworks), then open/metricsand find yourhttp_requests_totaland duration lines. - Write a PromQL expression for the error rate of
/flakyas a percentage. - 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 first | Alerting only on CPU and memory |
| Alert on symptoms (errors, latency) | Alerting on causes that may be harmless |
| Establish baselines before setting thresholds | Guessing thresholds out of thin air |
| Attach a runbook to every alert | Pages with no guidance on what to do |
| Keep label cardinality bounded | Putting user IDs or raw URLs in labels |
| Review and prune noisy alerts regularly | Letting 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
- Google SRE Books β Monitoring & the Four Golden Signals
- Prometheus Documentation
- Grafana & Loki Documentation
- OpenTelemetry Documentation
π 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.