AWS for DevOps — CloudWatch Logs

3 minute read

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

Updated: