CloudWatch Monitoring & Logs
An app that nobody watches fails silently. Priya's exam portal ran out of memory on result day and she found out from angry students on Instagram. Amazon CloudWatch collects numbers (metrics) and messages (logs) from your AWS resources and applications, and alarms tell you about problems before your users do. AWS CloudTrail records who did what in your account.
In this lesson you will learn CloudWatch metrics and alarms, read and search logs, publish your own metrics, and use CloudTrail to answer "who changed this?".
Metrics
A metric is a series of numbers over time, such as CPU use every minute. Each metric belongs to a namespace (AWS/EC2, AWS/Lambda, AWS/RDS) and has dimensions that identify the resource, such as FunctionName=tiffin-bill.
Many AWS services publish metrics automatically:
- EC2: CPU, network and disk operations every 5 minutes (basic monitoring), or every minute with detailed monitoring. Memory and disk-space usage are not visible from outside the instance; install the CloudWatch agent to collect them.
- Lambda:
Invocations,Errors,Duration,ThrottlesandConcurrentExecutions. - RDS: CPU, free storage, connections, read and write latency.
- API Gateway, DynamoDB, S3, CloudFront and others publish their own.
Statistics such as Average, Maximum, Sum and percentiles (p99) summarise each period (for example 60 or 300 seconds).
Alarms
An alarm watches one metric (or a maths expression of several) and changes state:
OK: the metric is within the threshold.ALARM: the threshold was breached for the number of periods you set.INSUFFICIENT_DATA: not enough data yet, or the metric stopped arriving.
Alarms can notify an SNS topic (email, SMS, chat tools), scale an Auto Scaling group, or stop, reboot or recover an EC2 instance. This alarm emails the team if the tiffin function has any error in a 5-minute window:
aws cloudwatch put-metric-alarm --alarm-name tiffin-bill-errors \
--namespace AWS/Lambda --metric-name Errors \
--dimensions Name=FunctionName,Value=tiffin-bill \
--statistic Sum --period 300 --evaluation-periods 1 \
--threshold 0 --comparison-operator GreaterThanThreshold \
--treat-missing-data notBreaching \
--alarm-actions arn:aws:sns:ap-south-1:111122223333:ops-alerts
aws cloudwatch describe-alarms --alarm-name-prefix tiffin \
--query "MetricAlarms[].{Name: AlarmName, State: StateValue}"[
{
"Name": "tiffin-bill-errors",
"State": "OK"
}
]--treat-missing-data notBreaching matters for functions that are idle at night: no invocations means no Errors data, which should count as fine, not as a problem.
Logs
CloudWatch Logs stores log lines in log groups (one per application or function, for example /aws/lambda/tiffin-bill) made of log streams (one per execution environment, container or instance). Lambda, API Gateway access logs, VPC Flow Logs, ECS containers and the CloudWatch agent all send logs here.
New log groups keep logs forever by default, and storage grows every month. Set a retention period on every group:
aws logs put-retention-policy --log-group-name /aws/lambda/tiffin-bill \
--retention-in-days 30
aws logs tail /aws/lambda/tiffin-bill --since 10m --follow2026-10-30T10:15:02.418000+00:00 2026/10/30/[$LATEST]9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d START RequestId: 5e1d7c2a-0b3f-4a6e-9c8d-1f2e3a4b5c6d Version: $LATEST 2026-10-30T10:15:02.421000+00:00 2026/10/30/[$LATEST]9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d Order for Kabir: total 504 2026-10-30T10:15:02.425000+00:00 2026/10/30/[$LATEST]9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d END RequestId: 5e1d7c2a-0b3f-4a6e-9c8d-1f2e3a4b5c6d
aws logs tail --follow streams new lines live, like tail -f on Linux. Each line shows the time, the log stream and the message.
Search with Logs Insights
CloudWatch Logs Insights has a small query language for searching across log groups. In the console, choose the log group and run:
fields @timestamp, @message
| filter @message like /ERROR|Unknown dish/
| sort @timestamp desc
| limit 20Queries can also count and group, for example stats count(*) by bin(1h) to see errors per hour. If your logs are JSON, Logs Insights discovers their fields automatically, which is one more reason to log in JSON.
Custom metrics from your code
Business numbers, such as orders placed, are often more useful than CPU graphs. The simplest way to publish them from Lambda or containers is the Embedded Metric Format (EMF): print a JSON line in a special shape, and CloudWatch turns it into a metric with no extra API call:
import json
import time
def emit_metric(name, value, unit="Count", service="tiffin-bill", timestamp_ms=None):
"""Print one CloudWatch Embedded Metric Format (EMF) record."""
record = {
"_aws": {
"Timestamp": timestamp_ms or int(time.time() * 1000),
"CloudWatchMetrics": [{
"Namespace": "TiffinApp",
"Dimensions": [["Service"]],
"Metrics": [{"Name": name, "Unit": unit}],
}],
},
"Service": service,
name: value,
}
print(json.dumps(record))
emit_metric("OrdersPlaced", 1, timestamp_ms=1793354400000){"_aws": {"Timestamp": 1793354400000, "CloudWatchMetrics": [{"Namespace": "TiffinApp", "Dimensions": [["Service"]], "Metrics": [{"Name": "OrdersPlaced", "Unit": "Count"}]}]}, "Service": "tiffin-bill", "OrdersPlaced": 1}The fixed timestamp is only there to make the output repeatable; in real code leave it out. You can also call put_metric_data with boto3, but EMF is cheaper on API calls and keeps the log line for debugging. Each unique combination of dimension values creates a separate metric, so never use user IDs or order IDs as dimensions.
Dashboards
A dashboard puts graphs and alarm states on one page. A good first dashboard for a web app shows request count, error rate, latency (p50 and p99), and database CPU and connections. Share it with the team and look at it during busy days such as results day or a festival sale.
CloudTrail: who did what
AWS CloudTrail records API calls made in your account, from the console, CLI or SDKs: who called, from which IP address, when, and what changed. The event history of management events for the last 90 days is available in every account without setup:
aws cloudtrail lookup-events --region ap-south-1 \
--lookup-attributes AttributeKey=EventName,AttributeValue=DeleteBucket \
--query "Events[].{Time: EventTime, User: Username, Event: EventName}"[
{
"Time": "2026-10-29T16:41:09+05:30",
"User": "rohan-dev",
"Event": "DeleteBucket"
}
]For longer retention and organisation-wide auditing, create a trail that delivers logs to an S3 bucket (ideally in a separate, locked-down account), and alert on sensitive events such as root user sign-ins. The Logging, SIEM & Threat Detection lesson explains how security teams use these logs.
Common mistakes
- Leaving log retention at "Never expire" and paying to store years of debug logs.
- Expecting EC2 memory metrics without the CloudWatch agent.
- Alarms with no action, or sending them to an email nobody reads.
- High-cardinality dimensions such as customer IDs, which create thousands of metrics.
- Not keeping CloudTrail logs beyond 90 days when audits or incident investigations need them.
Key points
- CloudWatch metrics have namespaces, dimensions, statistics and periods; many services publish them automatically.
- EC2 memory and disk-space metrics need the CloudWatch agent.
- Alarms move between
OK,ALARMandINSUFFICIENT_DATAand can notify SNS or take automatic actions. - Logs live in log groups and streams; set retention, use
aws logs tailand Logs Insights to search. - Publish business metrics with the Embedded Metric Format or
put_metric_data. - CloudTrail records API activity; event history covers 90 days, and trails store logs long term.
Exercise
Write a CLI command for an alarm called exam-portal-high-cpu that notifies arn:aws:sns:ap-south-1:111122223333:ops-alerts when the average CPU of instance i-1234567890abcdef0 is above 80% for three consecutive 5-minute periods. Then write the command to set 14-day retention on the log group /exam-portal/app.
Show solution
aws cloudwatch put-metric-alarm --alarm-name exam-portal-high-cpu \
--namespace AWS/EC2 --metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
--statistic Average --period 300 --evaluation-periods 3 \
--threshold 80 --comparison-operator GreaterThanThreshold \
--alarm-actions arn:aws:sns:ap-south-1:111122223333:ops-alerts
aws logs put-retention-policy --log-group-name /exam-portal/app --retention-in-days 14Three evaluation periods of 300 seconds mean the CPU must stay high for 15 minutes, which avoids alerts for short spikes.
Quick Quiz
1. Which EC2 metric needs the CloudWatch agent?
Memory and disk-space usage are only visible from inside the operating system.
2. What is the default retention of a new CloudWatch log group?
Set a retention policy, or logs are kept and stored indefinitely.
3. An alarm for Lambda errors shows INSUFFICIENT_DATA every night when nobody uses the app. What setting helps?
Missing data during idle periods should be treated as healthy for an error alarm.
4. Which service tells you which user deleted an S3 bucket yesterday?
CloudTrail records API calls with the caller's identity, time and source.
Learn AWS with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
