Lesson 17 · 25 min read
Monitoring and Logging with CloudWatch
Knowing what your system is doing before your users tell you: metrics, logs, alarms and dashboards.
What you'll learn in this lesson
- Concept: knowing before your users tell you
- Why this matters
- Metrics, logs and traces
- The four golden signals
- CloudWatch vocabulary
- What you get for free — and what is missing
- Step 1 — a channel that reaches you
- Step 2 — the alarms every production app needs
- Step 3 — get the logs off the server
- Step 4 — write logs you can actually search
- Step 5 — search with Logs Insights
- Step 6 — turn logs and business events into metrics
- Step 7 — memory and disk with the CloudWatch agent
- Step 8 — one dashboard
- How to alert without burning out your team
- When the alarm fires: a debugging routine
- What this costs, and the log bill trap
- Troubleshooting table
- Interview corner
- Practice task before Lesson 18
- Conclusion
Concept
Monitoring is how you find out what your system is doing right now. Alerting is how the system tells you when something is wrong. On AWS the built-in tool is CloudWatch. It collects numbers (called metrics) and text (called logs). It draws them on dashboards, which are screens of graphs. It also sends a message when a number crosses a line you set. These rules are called alarms.
The dashboard shows your speed and fuel at a glance. These are the metrics. The warning lights tell you before the engine breaks down. These are the alarms. The black-box recorder lets investigators see what happened after a crash. These are the logs. A car without any of these still drives. But one day it stops, with no warning and no explanation.
Why this matters
- Without monitoring, your customers are your alarm system. The first sign of trouble is an angry email or a public post. You learn about an outage from the person who pays you.
- “It is slow” has no answer without data. Slow where? Since when? After which deploy? For whom? Metrics and logs answer these questions in seconds. Guessing takes hours.
- Everything you built can fail silently. Examples are a full disk, a database that reached its connection limit, an expired certificate, a health check that keeps switching between pass and fail (flapping), or an Auto Scaling group stuck at its maximum size. All of them look like “the site works for me” until it does not.
- You need proof that it is fixed. After a change, the graph either went back to normal or it did not.
- Capacity and cost planning need history. You must know how much traffic grows each month and how high the peak is.
Metrics, logs and traces
Telemetry means the data a system sends out about itself. It comes in three kinds, and each kind answers different questions. You want all three, but start in this order:
| Kind | What it is | Answers | AWS tool |
|---|---|---|---|
| Metrics | Numbers over time. They are cheap to store and to search | “How many 5xx per minute? What is CPU?” | CloudWatch Metrics, dashboards, alarms |
| Logs | Events with a time stamp and detail | “What exactly happened to request abc123?” | CloudWatch Logs, Logs Insights |
| Traces | One request followed through every service and query | “Where did those 3 seconds go?” | AWS X-Ray (the AWS tracing service), OpenTelemetry (an open standard and toolkit for creating traces) |
Metrics tell you that something is wrong. Logs tell you why. A typical investigation starts with a graph. For example, 5xx errors (server errors) rose at 14:02. Then you open the logs for that minute and find a database timeout on one endpoint. You use traces only for complex systems with many services. Our app is a monolith, which means one single application. For a monolith, metrics plus good logs solve most problems.
The four golden signals
Google's site-reliability book answers “what should I watch?” with four things. If you watch only these, you already see almost every problem that users can notice:
| Signal | Question | Where in our stack | And also |
|---|---|---|---|
| Latency | How long requests take. Use percentiles (p50 is the middle value, p95 and p99 are the slow end), not averages | ALB TargetResponseTime | RDS ReadLatency |
| Traffic | How much demand there is for your service | ALB RequestCount | Redis commands/s |
| Errors | How many requests fail | ALB HTTPCode_Target_5XX_Count | App error logs |
| Saturation | How full your resources are | ASG CPU, memory, disk % | RDS connections, FreeStorageSpace, Redis memory |
Suppose 99 requests take 100 ms and one takes 10 seconds. The average is about 200 ms, which looks fine, but one user in a hundred is suffering. A percentile fixes this. p95 means “95% of requests were faster than this time”. p99 shows the slowest 1% (the “tail”). Set alerts and write reports with percentiles.
You may hear two memory aids. RED is for services: Rate, Errors, Duration. USE is for resources: Utilisation, Saturation, Errors. They describe the same ideas from two sides.
CloudWatch vocabulary
| Word | Meaning | Example |
|---|---|---|
| Namespace | A folder that groups the metrics of one service | AWS/ApplicationELB, MyApp |
| Metric | A named list of numbers over time | HTTPCode_Target_5XX_Count |
| Dimension | A label that picks one item out of many | LoadBalancer=app/myapp-alb/… |
| Statistic | The way to combine the points inside one period | Sum, Average, Maximum, p95 |
| Period | The length of each time bucket, in seconds | 60 |
| Alarm | A rule on a metric. It has three states: OK, ALARM and INSUFFICIENT_DATA (not enough data to decide) | “5xx ≥ 5 for 2 of 3 minutes” |
| Log group / stream | A group of logs for one purpose (group) / one source inside the group (stream) | /myapp/web / one per container |
| SNS topic | A broadcast channel. Alarms send messages to it. Email, SMS and Slack (through AWS Chatbot) can subscribe to it | myapp-alerts |
What you get for free — and what is missing
As soon as you create resources, AWS starts publishing metrics for them at no charge. A few names you will see: EC2 is the AWS service that rents virtual servers. An Auto Scaling group is a group of servers that AWS adds to and removes automatically. An ALB (Application Load Balancer) spreads web traffic over the servers. RDS is the managed database. ElastiCache is the managed Redis cache.
- EC2: CPU, network in and out, disk operations and status checks, every 5 minutes. With “detailed monitoring” you get them every minute, for about $2 per instance per month.
- ALB: request count, response codes, latency and the number of healthy and unhealthy hosts. RDS: CPU, connections, free storage, IOPS (disk operations per second) and latency. ElastiCache: memory, hit rate and evictions.
The hypervisor (the AWS software that runs your virtual server) can see CPU and network use. It cannot see what is inside the operating system. So “memory 95%” and “disk 98% full” are invisible. These are two of the most common causes of crashes. You can see them only after you install the CloudWatch agent (Step 7). Do not assume that “AWS monitors my server” covers them.
Nothing collects your application logs by default either. They sit in a Docker file on a server. Auto Scaling may delete that server at any moment, and the evidence is deleted with it.
Step 1 — a channel that reaches you
An alarm needs a place to send its message. SNS (Simple Notification Service) is the AWS broadcast channel for this. Start with email. Later you can switch to Slack or a pager without changing any alarm.
In SNS, go to Topics and create a Standard topic called myapp-alerts. Then create a subscription with the protocol Email and your address. AWS sends you a confirmation email. Until you click “Confirm subscription”, you receive nothing.
Send alerts to a group mailbox, not to one person's inbox, because people go on holiday. Then prove that the whole path works before you need it. The topic's Publish message button sends a test. An alert channel that you have never tested is often broken.
Step 2 — the alarms every production app needs
You do not need fifty alarms. You need the right dozen. Start with these. Each one is tied to a symptom that users feel, or to a resource that is about to run out:
| Alarm on… | Metric | Threshold | Why |
|---|---|---|---|
| Users are seeing errors | ALB HTTPCode_Target_5XX_Count | ≥ 5 per minute, 2 of 3 minutes | The primary symptom alarm |
| The site is too slow | ALB TargetResponseTime p95 | > 1 second for 5 minutes | Slowness hurts before errors appear |
| A server is failing | ALB UnHealthyHostCount | ≥ 1 for 3 minutes | Auto Scaling will replace it, but you should still find out why |
| No servers left | ALB HealthyHostCount | < 1 for 1 minute | Total outage. Call a person (page them) |
| Nobody can reach us at all | ALB RequestCount | < 1 for 10 minutes (treat missing data as breaching) | Catches DNS, certificate and ALB failures that produce no errors |
| Database CPU | RDS CPUUtilization | > 80% for 10 minutes | A missing index or an instance that is too small |
| Database disk | RDS FreeStorageSpace | < 5 GB | A full disk freezes the database |
| Database connections | RDS DatabaseConnections | > 80% of max_connections | The outage from Lesson 8 that came with scaling |
| Cache memory | ElastiCache DatabaseMemoryUsagePercentage | > 80% | Evictions and out-of-memory errors are coming |
| Server memory / disk | MyApp/EC2 mem_used_percent, disk_used_percent | > 85% | EC2 does not report these by default |
| Money | AWS Budget / EstimatedCharges | 80% of budget | The alarm from Lesson 5. It is still the most valuable one |
Build the first one carefully. The others are small variations.
In CloudWatch, go to Alarms and choose Create alarm. Select the metric: ApplicationELB, then Per-AppELB Metrics, then HTTPCode_Target_5XX_Count for myapp-alb. Set the statistic to Sum and the threshold to ≥ 5. Send the notification to myapp-alerts. Under “Additional configuration” are the settings that make an alarm trustworthy:
| Setting | Meaning |
|---|---|
| Period: 1 minute | Look at one-minute time buckets. |
| Datapoints to alarm: 2 out of 3 | “2 of the last 3 minutes must go over the limit.” This ignores one short spike, and the alarm still reacts within about two minutes. |
| Missing data: treat as good | No errors means no data points. Treat silence as fine, not as an alarm. (For the “no traffic” alarm choose bad, because silence is the problem.) |
| Also notify on OK | Tell me when it recovers, so nobody has to wonder. |
| Description | Write it for the person who is woken at 3 a.m. Say what the alarm means and where to look first. |
The other rows of the table use the same screen with a different metric, statistic and threshold. Watch for two details. Latency uses the p95 statistic, not the average. The database disk threshold is in bytes. 5 GB is 5368709120 bytes.
Then prove that an alarm works. Do not trust it until it has fired once. You can force it into the alarm state without breaking anything:
aws cloudwatch set-alarm-state --alarm-name myapp-alb-5xx --state-value ALARM --state-reason "Testing the alert path"Sends the real notification to your email. The alarm goes back to OK at its next check
Step 3 — get the logs off the server
A container writes its log lines to stdout (the standard output stream of a program), and Docker captures them. By default Docker saves them in a JSON file on that server's disk. The file is gone when Auto Scaling replaces the instance, which is exactly when you want to read it. Docker's awslogs driver sends each line to CloudWatch Logs instead, almost at once.
Give myapp-ec2-role (the IAM role of the server) permission to create the log group and log streams and to put log events in log groups under /myapp/*. The permissions are logs:CreateLogGroup, logs:CreateLogStream and logs:PutLogEvents. Then change one thing in the boot script: the log driver.
docker run -d --name myapp --restart unless-stopped \
--env-file /etc/myapp.env \
--log-driver awslogs \
--log-opt awslogs-region=ap-south-1 \
--log-opt awslogs-group=/myapp/web \
--log-opt awslogs-create-group=true \
-p 80:3000 $REGISTRY/myapp:$TAGEach container becomes a log stream inside the /myapp/web group. So you can read one server or all servers together. Now here is the most important log setting of all.
Log groups keep data forever by default, and you pay for storage every month. On the day you create the group, set its retention (Actions, then Edit retention setting) to 14, 30 or 90 days. Then you can watch every server at once from your terminal:
aws logs tail /myapp/web --follow --since 10mWorks like docker logs -f, but for every server at once. Add --filter-pattern ERROR to see only errors
The load balancer can also write a record of every request to an S3 bucket. The record has the client IP, the path, the status and the timing. Storage costs about $0.02 per GB, and there are no CloudWatch charges. Turn it on in the ALB's attributes. When you need to study traffic patterns or an attack, search the records with Athena (an AWS service that runs SQL queries on files in S3). It is cheap and often very useful.
Step 4 — write logs you can actually search
Compare these two ways of logging the same event:
# Hard: you can only search it as plain text (grep), and every developer words it differently
Error saving order for user 42 after 1204ms: connection timeout
# Easy: one JSON object per line, and every value is a field you can search
{"level":"error","time":"2026-01-15T09:30:12.041Z","msg":"order save failed","requestId":"Root=1-65a4-1c9d","tenantId":"acme","userId":"42","route":"/api/orders","status":500,"durationMs":1204,"err":"connection timeout"}With the second form, “show me all errors for tenant acme on /api/orders slower than one second” is a one-line query. With the first form you would need a complex text pattern (a regex).
import pino from "pino";
export const log = pino({
level: process.env.LOG_LEVEL ?? "info", // use "debug" only while you are investigating a problem
base: { service: "myapp", version: process.env.APP_VERSION },
timestamp: pino.stdTimeFunctions.isoTime,
// Anything at these paths is replaced with "[Redacted]". Do this from day one
redact: ["*.password", "*.token", "*.secret", "req.headers.authorization", "req.headers.cookie"],
});
// per request:
// const requestId = req.headers.get("x-amzn-trace-id") ?? crypto.randomUUID(); // the ALB adds this header
// const l = log.child({ requestId, tenantId, route });
// l.info({ status: 200, durationMs }, "request completed");
// l.error({ err }, "order save failed");The rules of good logging
- One correlation ID for each request, on every log line. A correlation ID is a unique code that ties together all the lines of one request. The ALB already adds
X-Amzn-Trace-Id. Log it and also return it to the client. Then, when a user sends you an error ID, you can find the matching log lines in one second. - Include the tenant. A multi-tenant product serves many customers from one app. In such a product, “is it only one customer?” is the first question in every incident.
- Use log levels honestly.
errormeans a person should probably look.warnmeans something unexpected happened but the app handled it.infomeans normal business events.debugmeans very detailed messages, which stay off in production. - Never log secrets or personal data. This includes passwords, tokens, session IDs, full card numbers and national ID numbers. Many people copy, keep and read logs. Leaking such data is a legal and security problem, not a style problem.
- Do not log every health check. The load balancer checks every server every few seconds. These lines hide the useful ones and make the bill larger.
- One line for each event, and no multi-line stack traces. Put the stack trace (the list of function calls at the error) inside a field. Otherwise the lines are split and Insights cannot read them.
Step 5 — search with Logs Insights
Logs Insights is the CloudWatch tool for searching logs with queries. In the console, go to CloudWatch, then Logs, then Logs Insights. Select /myapp/web, pick a time range and paste a query. Insights finds the JSON fields by itself.
# Recent errors
fields @timestamp, msg, route, tenantId, err
| filter level = "error"
| sort @timestamp desc
| limit 50
# Which endpoints are slow?
filter ispresent(durationMs)
| stats count() as requests, pct(durationMs, 95) as p95_ms by route
| sort p95_ms descSave a few more queries. One shows everything for one requestId. One shows status codes over time in 5-minute groups. One shows errors grouped by tenantId.
Insights charges for the data it scans (about $0.005 per GB). So make the time range and the log group small before you run a query over weeks of data.
Step 6 — turn logs and business events into metrics
A metric filter watches a log group. Each time a line matches a pattern, it adds to a metric. So an error in your code can trigger an alarm even when AWS never sees a 5xx. For example, you may have handled the error and returned a friendly 200.
In the log group, choose Actions, then Create metric filter. Use the pattern { $.level = "error" }, the metric MyApp/AppErrors and the value 1. Then create an alarm on that metric like any other alarm. For example, alarm on 10 or more in five minutes.
The same trick can monitor the business, which is often what matters most. Everything can look healthy on a technical level while signups have silently stopped. So send a metric for the events you care about.
You can send one data point for each event from your code, using the AWS SDK's PutMetricData call. A cheaper way is to write the event in Embedded Metric Format. This is a special log format that CloudWatch turns into a metric, so you need no extra API call.
Good events to measure are signups, logins, successful and failed payments, emails sent, finished background jobs and queue depth (how many jobs are waiting). An alarm on “zero signups for an hour during business hours” has caught more real outages than most technical alarms.
Step 7 — memory and disk with the CloudWatch agent
The agent is a small program that runs on each server. It reads numbers from the operating system and publishes them as custom metrics. Put it in the boot recipe (the user-data script of Lesson 12), so every new server gets it automatically. First attach the AWS-managed policy CloudWatchAgentServerPolicy to the instance role.
The agent's config is a small JSON file. It says to collect mem_used_percent and the root disk's used_percent every 60 seconds. The metrics go into the namespace MyApp/EC2, grouped by Auto Scaling group. (CloudWatch shows the disk metric as disk_used_percent.) Keep the file in SSM Parameter Store (an AWS service that stores settings) as /myapp/cloudwatch-agent, so every server reads the same file.
The user-data script needs three extra steps. Install the agent package. Fetch the config from SSM. Start the agent with that config.
Grouping by Auto Scaling group gives you one graph for “memory across all servers”. Without it, you would get one line for each short-lived instance ID. Then add the memory and disk alarms from the table (namespace MyApp/EC2, metrics mem_used_percent and disk_used_percent).
Step 8 — one dashboard
A dashboard is for the moment when someone asks “is everything OK?” It should answer in five seconds without scrolling. In CloudWatch, go to Dashboards and choose Create. Here is a layout that works for this architecture. It goes from top to bottom in the order that a request travels:
| Row | Widgets |
|---|---|
| Is anything broken? | An alarm-status widget that shows every alarm as a green or red box; the 5xx count; the healthy host count |
| Traffic & latency | ALB request count; p50/p95/p99 TargetResponseTime |
| Servers | Desired and in-service instances; average CPU; memory % and disk % (from the agent) |
| Database | CPU; connections; free storage; read and write latency |
| Cache | Hit rate; memory %; evictions |
| The business | Signups, logins, payments per hour |
Your first three dashboards are free. After that, each costs about $3 a month. Keep one dashboard that the whole team knows by name. When you write Terraform for your setup, define the dashboard there too. Then it is rebuilt together with everything else.
The most useful question in any incident is “what changed?” Make the deploy pipeline (Lesson 14) send a metric or a log line on every release. Even a put-metric-data --metric-name Deploy command is enough. Add it to the dashboard. Suppose the error graph jumps at 14:02 and a deploy marker sits at 14:01. Then you have a suspect in ten seconds.
How to alert without burning out your team
Alarms that warn about nothing get muted. A muted alarm system is worse than no alarm system. These habits separate experienced teams from the rest:
- Alert on symptoms, not causes. A symptom is what users feel. “Users are getting errors” and “the site is slow” always deserve attention. “CPU is 75%” may be perfectly healthy. Put cause metrics on the dashboard to help you diagnose. Do not send them to your phone.
- Every alert must lead to an action. If your reaction to an alert is “ignore it, that always happens”, delete it or change its settings that day.
- Two levels. A page wakes a person now. Use it for a total outage, errors that continue, or data at risk. A ticket can wait until tomorrow. Use it for a disk at 70%, a certificate that expires in 20 days, or one failed job. Send the two levels to different channels.
- Put a runbook link in every alarm description. A runbook is a short guide that says what the alarm means, how to confirm it, the first three things to try and who to call next. The person at 3 a.m. is tired and may not be the person who built the system.
- Alert when good things stop. No traffic, no signups, no finished jobs. These silent failures produce no errors.
- Review your alerts every month. Which ones fired? Which ones mattered? Which never fired? Alerts, like tests, go out of date.
An SLI (service level indicator) is a measurement, such as the share of requests that succeed in under 500 ms. An SLO (service level objective) is your target for it, such as 99.9% over 30 days. The gap is 0.1%, or about 43 minutes a month. This gap is your error budget. It is the room you have for risky changes. When you have used up the budget, you slow down and work on reliability. This turns “how reliable should we be?” from an argument into a number.
When the alarm fires: a debugging routine
Have a routine. Then a tired mind can follow steps instead of guessing:
Is it real, and how big?
Open the dashboard. Are errors up? Is it for everyone, or for one part (one tenant, one endpoint, one Availability Zone)? Since when? Is there a deploy marker just before?
Stop the bleeding first
If a deploy caused it, roll back now (Lesson 14) and investigate afterwards. Getting the service back matters more than understanding the cause. You can also add servers, switch to a standby, or turn off a feature flag (a switch that turns a feature on or off). Use whatever restores service to users fastest.
Walk the golden signals down the stack
Start with the ALB errors or latency. Then check the servers behind it: healthy hosts, CPU and memory. Then check what they depend on: RDS CPU, connections and storage, and Redis memory and hit rate. The first red graph in this chain is usually the cause.
Read the logs for the exact minute
Run the “recent errors” and “errors by tenant” Insights queries for the time of the incident. Take one
requestIdand read its whole story.Write down what you learn, while it is fresh
Write the timeline, the cause and the fix. Then write the most valuable part: what would have caught it sooner? Add that alarm or log field. A blameless post-mortem is a review that looks for what to improve, not for who to blame. It turns each outage into a lasting improvement.
What this costs, and the log bill trap
| Item | Approximate price |
|---|---|
| AWS service metrics (EC2 5-min, ALB, RDS, ElastiCache) | Free |
| Standard alarms | First 10 free, then about $0.10 per alarm per month |
| Custom metrics (agent, business) | First 10 free, then about $0.30 per metric per month |
| Dashboards | First 3 free, then $3 each per month |
| Log ingestion | about $0.50 to $0.70 per GB. This is the one that surprises people |
| Log storage | about $0.03 per GB per month (free after the retention time ends, because the data is deleted) |
| Logs Insights queries | about $0.005 per GB scanned |
| SNS email notifications | Free (first 1,000 a month) |
Suppose a busy app logs 2 KB for each request at 50 requests a second. That makes about 8 GB a day. It costs over $100 a month just to take in the data (ingest it), before you store it. The usual causes are these: leaving debug on in production, logging every request body, logging health checks, and endless loops of retried errors that each write a log line. Set the level to info. Log only a sample of very frequent lines. Check the log group's stored bytes every month. The metrics, alarms and dashboard from this lesson cost only a few dollars. Logs are where careful habits save money.
Troubleshooting table
| Symptom | Likely cause | Fix |
|---|---|---|
| Alarm fired but no email arrived | The SNS email subscription was never confirmed, or the email went to spam | Run aws sns list-subscriptions-by-topic. “PendingConfirmation” means you must click the link in the confirmation email |
| Alarm stuck on INSUFFICIENT_DATA | There are no data points. The dimension value or namespace is wrong, or the metric exists only when events happen | Look up the exact dimension in the Metrics view of the console. Set --treat-missing-data to a suitable value |
| Log group exists but has no events | The role lacks logs:PutLogEvents, the log driver or agent is not running, or the region is wrong | Check the role policy. Run docker logs myapp to see driver errors. Check the region |
| Logs Insights shows no fields for my JSON | The log lines are not valid JSON (they have a prefix, or multi-line stack traces) | Log one JSON object per line. Put stack traces in a field |
| Memory/disk metrics missing | The CloudWatch agent is not installed, or the role lacks CloudWatchAgentServerPolicy | systemctl status amazon-cloudwatch-agent; check /opt/aws/amazon-cloudwatch-agent/logs/ |
| Alarm flaps between OK and ALARM | The threshold is at the level of normal noise, or the period is too short | Use more evaluation periods, change datapoints to alarm, or use a longer period |
| The bill jumped after enabling logging | Very detailed debug logging, or every health check is logged | Filter out health checks. Set LOG_LEVEL=info. Set a retention time |
| Dashboards show data from the wrong region | The region selector in the console is not set to ap-south-1 | Switch the region. CloudWatch data belongs to one region |
Interview corner
What would you monitor for a web application?
I watch the four golden signals: latency (using percentiles), traffic, errors and saturation. In practice that means load balancer 5xx errors and p95 latency, the healthy host count, server CPU, memory and disk, database CPU, connections and free storage, cache hit rate and memory, and business metrics such as signups and payments.
Why percentiles instead of averages?
Averages hide the slow tail. A few very slow requests barely change the average, but real users suffer. p95 and p99 show what the slowest users experience. SLOs should use them.
EC2 memory usage isn’t in CloudWatch by default. Why, and what do you do?
The hypervisor sees CPU and network, but not what happens inside the guest operating system. I install the CloudWatch agent, with an instance role, to publish memory and disk metrics. I add it to the launch template so every instance has it.
Metrics vs logs vs traces?
Metrics are cheap summary numbers. I use them to detect problems and to alert. Logs are detailed events. I use them to explain problems. Traces follow one request across services. I use them to find where time is lost. So I detect with metrics, diagnose with logs, and pinpoint with traces.
How do you avoid alert fatigue?
I alert on symptoms that users can see. I make every alert lead to an action, with a runbook. I separate pages from tickets. I set thresholds and evaluation windows so that short spikes are ignored. I delete alerts that nobody acts on, and I review the alerts regularly.
The site is down but there are no 5xx errors on the ALB. What alarm would have caught it?
A traffic-absence alarm would have caught it. It alarms when RequestCount is below a minimum, and it treats missing data as a breach. A health check from outside, such as a Route 53 health check or a synthetic check (a scripted test), would also work. DNS, certificate or network failures produce no errors, because no requests arrive.
Your CloudWatch bill tripled. Where do you look?
I look at log ingestion first. Which log group grew (the IncomingBytes metric shows this)? Is debug logging left on? Are health checks or request bodies logged? Are there error loops? Then I check how many different custom metrics there are (their cardinality) and the Logs Insights scans. I fix it with log levels, sampling, retention times and metric filters.
Practice task before Lesson 18
- Create the SNS topic, subscribe your email and confirm it. Publish a test message and receive it.
- Create the six alarms. Force each one into
ALARMwithset-alarm-state. Check that the email arrives and that its description would help a stranger. - Send logs to CloudWatch with the
awslogsdriver. Set a 30-day retention. Runaws logs tailwhile you click around the site. - Switch the app to JSON logging with request IDs and tenant IDs. Save three Logs Insights queries: recent errors, slowest routes and the story of one request.
- Break something on purpose. Stop the app on one server, or make a route throw an error. Then watch three things: an alarm fires, you find the cause in the logs, and the recovery email arrives.
- Install the CloudWatch agent on one server. Find
mem_used_percentin the console. Add a memory alarm. - Build the dashboard. Sort one alarm as page-worthy and one as ticket-worthy. Write a three-line runbook for each.
Create a free external uptime check (UptimeRobot or a similar service) on your public URL. It does not depend on AWS. So it also catches the case where your whole AWS setup, or your DNS, is down. Then try Container Insights or Application Signals in the console. They show what more automatic monitoring looks like.
Conclusion
You can now see the system, and it can call you. This changes how you run it. You no longer guess nervously. You follow a routine.
- Watch the four golden signals: latency (percentiles), traffic, errors and saturation. Remember that EC2 hides memory and disk until you install the agent.
- Alarms send messages to SNS. Confirm the subscription and test the path. Alert on symptoms, put a runbook in the description, and also alert when traffic disappears.
- Send logs off the server. Use structured JSON with request IDs and tenant IDs, and never log secrets. Search with Logs Insights. Turn log patterns and business events into metrics.
- One dashboard, ordered like the path of a request, with deploy markers.
- Log ingestion is the cost trap. Use the info level, set a retention time, and do not log health checks.
You can now observe the system. Next, make it hard to break into and impossible to lose. That means security hardening (making a system harder to attack) and backups.
End of Lesson 17. Next: Lesson 18 — Security Hardening and Backups.
Published by Jainik · Last updated · How these lessons are made
Keep learning
Part of the study guide
Related series