AWS for DevOps — CloudWatch Logs
10.2 — CloudWatch Logs
CloudWatch Logs is the central place for all your application and infrastructure logs on AWS.
Log Structure
Log Group: /ecs/my-app
├── Log Stream: app/app/task-abc123
│ ├── 2026-03-30 10:00:01 INFO Server started on port 8080
│ ├── 2026-03-30 10:00:15 INFO Request GET /health 200 5ms
│ └── 2026-03-30 10:01:02 ERROR Connection to DB failed
└── Log Stream: app/app/task-def456
├── 2026-03-30 10:00:03 INFO Server started on port 8080
└── 2026-03-30 10:00:20 WARN Slow query detected: 2500ms
| Concept | Description |
|---|---|
| Log Group | Container for related log streams (e.g., /ecs/my-app) |
| Log Stream | Sequence of events from one source (e.g., one ECS task) |
| Log Event | A single log line with a timestamp |
Creating Log Groups
$ aws logs create-log-group --log-group-name /ecs/my-app
$ aws logs put-retention-policy --log-group-name /ecs/my-app --retention-in-days 30
| Retention | Use Case |
|---|---|
| 1 day | Development, debugging |
| 7 days | Staging |
| 30 days | Production |
| 90-365 days | Compliance requirements |
| Never expire | Audit logs (export to S3 for cost savings) |
Viewing Logs (CLI)
# Tail logs in real-time
$ aws logs tail /ecs/my-app --follow
# Filter for errors
$ aws logs tail /ecs/my-app --follow --filter-pattern "ERROR"
# Last 1 hour
$ aws logs tail /ecs/my-app --since 1h
Filter Patterns
Create metric filters to turn log patterns into CloudWatch metrics:
# Count ERROR occurrences
$ aws logs put-metric-filter \
--log-group-name /ecs/my-app \
--filter-name error-count \
--filter-pattern "ERROR" \
--metric-transformations \
metricName=ErrorCount,metricNamespace=Custom/MyApp,metricValue=1
# Then alarm on the metric
$ aws cloudwatch put-metric-alarm \
--alarm-name high-error-rate \
--namespace Custom/MyApp \
--metric-name ErrorCount \
--statistic Sum \
--period 300 \
--evaluation-periods 2 \
--threshold 10 \
--comparison-operator GreaterThanOrEqualToThreshold \
--alarm-actions arn:aws:sns:ap-south-1:123456789012:ops-alerts
Filter Pattern Syntax
| Pattern | Matches |
|---|---|
"ERROR" |
Lines containing ERROR |
"ERROR" -"DEBUG" |
Lines with ERROR but not DEBUG |
{ $.statusCode = 500 } |
JSON logs where statusCode is 500 |
{ $.duration > 1000 } |
JSON logs where duration > 1000 |
{ $.level = "ERROR" && $.service = "api" } |
JSON logs: ERROR level from api service |
CloudWatch Logs Insights
Powerful query language for analysing logs:
$ aws logs start-query \
--log-group-name /ecs/my-app \
--start-time $(date -d '1 hour ago' +%s) \
--end-time $(date +%s) \
--query-string '
fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 20
'
Common Insights Queries:
-- Top 10 most frequent error messages
fields @message
| filter @message like /ERROR/
| stats count(*) as errorCount by @message
| sort errorCount desc
| limit 10
-- Average response time by endpoint
fields @message
| parse @message "* * * * *ms" as method, path, status, duration
| stats avg(duration) as avgDuration by path
| sort avgDuration desc
-- Error rate over time (5-minute buckets)
fields @timestamp
| filter @message like /ERROR/
| stats count(*) as errors by bin(5m)
Log Export to S3
For long-term storage at lower cost:
$ aws logs create-export-task \
--log-group-name /ecs/my-app \
--from $(date -d '7 days ago' +%s)000 \
--to $(date +%s)000 \
--destination my-log-archive-bucket \
--destination-prefix "ecs/my-app"
Subscription Filters
Stream logs in real-time to Lambda, Kinesis, or Elasticsearch:
# Stream ERROR logs to a Lambda for alerting
$ aws logs put-subscription-filter \
--log-group-name /ecs/my-app \
--filter-name error-to-lambda \
--filter-pattern "ERROR" \
--destination-arn arn:aws:lambda:ap-south-1:123456789012:function:alert-on-error
Common Mistakes
| Mistake | Why It Hurts | Fix |
|---|---|---|
| No retention policy | Logs stored forever, costs grow | Set appropriate retention (7-90 days) |
| No metric filters on errors | Errors go unnoticed | Create filters + alarms for ERROR patterns |
Searching with filter-log-events API |
Slow for large log groups | Use Logs Insights for fast querying |
| Not using structured (JSON) logging | Hard to query and filter | Log in JSON format |
Quick Reference
| Task | Command |
|---|---|
| Create log group | aws logs create-log-group --log-group-name NAME |
| Set retention | aws logs put-retention-policy --log-group-name NAME --retention-in-days N |
| Tail logs | aws logs tail LOG_GROUP --follow |
| Tail with filter | aws logs tail LOG_GROUP --filter-pattern "ERROR" |
| Create metric filter | aws logs put-metric-filter --log-group-name NAME --filter-name FN --filter-pattern PAT ... |
| Query with Insights | aws logs start-query --log-group-name NAME --query-string 'QUERY' |
Next → CloudWatch Dashboards