Ravindra BagaleCourses & study guides मराठी Track your progress

How to Send Logs from Multiple EC2 Instances to CloudWatch Logs (Agent, SSM, Logs Insights)

Metrics tell you that something is wrong. Logs tell you why. In this chapter you send the logs of many EC2 servers (nginx, Apache, Node.js, Java, Python) into one place, CloudWatch Logs, and search them all with one query. It builds on Amazon CloudWatch Explained.

What you'll learn in this chapter

  • Log group, log stream, log event and retention
  • IAM role with CloudWatchAgentServerPolicy
  • Install the CloudWatch agent; config by wizard and by JSON
  • One log stream per instance with {instance_id}
  • Systems Manager: install and configure the agent on many servers at once
  • Logs Insights queries and a metric filter alarm

1. A story: five servers, one error

A food-delivery app runs on 5 web servers behind a load balancer. A customer says "payment failed at 8:14 PM". Which server served him? Without central logs you SSH into 5 servers and run grep 5 times. If Auto Scaling already terminated that server, its logs are gone for ever.

With CloudWatch Logs, all 5 servers send their logs to one log group. One query finds the error in seconds, even from a server that no longer exists.

Logs from many servers to one placeweb1, web2, web3 …nginx / app logsCloudWatch agenton each serverIAM roleallows PutLogEventsLog group /app/nginx/accessone stream per instanceLogs Insightsone query, all servers
The path of one log line, top to bottom.

2. Log groups, streams and retention

Why do we need this?

Millions of lines need folders, or you can never find anything. Old logs also cost money, so we must delete them on time.

What is it?

  • Log event: one line with a time. Example: one nginx request.
  • Log stream: all lines from one source. We use one stream per instance, named by instance ID.
  • Log group: a folder of streams of the same kind. Example: /app/nginx/access holds the access logs of every web server.
  • Retention: how many days a log group keeps data. Default is Never expire, which grows the bill for ever. Set it, for example 30 days for access logs, 90 or more for audit logs.
StackLog file on the serverSuggested log group
nginx/var/log/nginx/access.log, error.log/app/nginx/access, /app/nginx/error
Apache (Amazon Linux)/var/log/httpd/access_log, error_log/app/httpd/access, /app/httpd/error
Apache (Ubuntu)/var/log/apache2/access.log/app/apache2/access
Node.js with PM2/home/ec2-user/.pm2/logs/app-out.log/app/node
Java Spring Boot/opt/app/logs/spring.log/app/java
Python Flask + Gunicorn/var/log/gunicorn/error.log/app/python
PHP-FPM/var/log/php-fpm/www-error.log/app/php
MySQL/var/log/mysqld.log (Amazon Linux) /app/mysql
.NET on WindowsWindows Event Log: Application/app/windows/application
Go service/var/log/goapp/app.log/app/go
System/var/log/messages, /var/log/secure/sys/messages, /sys/secure

3. Step 1: the IAM role

Why do we need this?

The agent on the server must prove to AWS that it is allowed to write logs. We never put access keys on servers: keys leak. A role gives short-lived keys automatically.

What is it?

CloudWatchAgentServerPolicy is an AWS managed policy. It allows the agent to send metrics and logs (PutMetricData, CreateLogStream, PutLogEvents and so on) and to read its config from Parameter Store. AmazonSSMManagedInstanceCore lets Systems Manager manage the server (we use it in Step 5).

How to do it (step by step)

  1. Open IAM → Roles → Create role.
  2. Trusted entity type: AWS service. Use case: EC2. Next.
  3. Search and tick CloudWatchAgentServerPolicy.
  4. Search and tick AmazonSSMManagedInstanceCore. Next.
  5. Role name: EC2-CloudWatchAgent-Role. Create role.
  6. EC2 → select each server → Actions → Security → Modify IAM role → choose EC2-CloudWatchAgent-Role → Update IAM role.
  7. Verify on the server:
TOKEN=$(curl -s -X PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 60")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/info
# expected: "InstanceProfileArn" : "arn:aws:iam::...:instance-profile/EC2-CloudWatchAgent-Role"

4. Step 2: install the agent (one server)

  1. SSH into web1 (Amazon Linux 2023) and install nginx so we have logs:
sudo dnf install -y nginx amazon-cloudwatch-agent
sudo systemctl enable --now nginx
curl -s -o /dev/null -w "%{http_code}\n" http://localhost/   # expected: 200

On Ubuntu: download https://amazoncloudwatch-agent.s3.amazonaws.com/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb and run sudo dpkg -i -E ./amazon-cloudwatch-agent.deb. On Windows use the MSI from the same bucket.

5. Step 3: configure with the wizard

Why do we need this?

The config file has many options. The wizard asks simple questions and writes a correct file for you. Good for the first server.

How to do it (step by step)

  1. Run sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard.
  2. OS: linux. EC2 or on-premises: EC2. User: cwagent. Why cwagent: the agent should not run as root.
  3. StatsD: no. CollectD: no. Why: we do not use them; collectd also needs a package, or the agent fails to start.
  4. Monitor host metrics: yes. CPU per core: no. EC2 dimensions: yes. Aggregate: yes. Resolution: 60s. Level: Basic (mem and disk).
  5. Existing config: no. Monitor log files: yes.
  6. Log file path: /var/log/nginx/access.log. Log group: /app/nginx/access. Log group class: STANDARD. Stream name: {instance_id}. Retention: 30.
  7. Add another: /var/log/nginx/error.log → /app/nginx/error, same stream name and retention. Then no more files.
  8. Store config in SSM Parameter Store: yes, name AmazonCloudWatch-linux. Why: other servers can download the same config in Step 5.
  9. The file is saved at /opt/aws/amazon-cloudwatch-agent/bin/config.json.

Ravindra Bagale's Tip

nginx logs are readable only by root and the nginx group. If the agent runs as cwagent, give read access: sudo setfacl -m u:cwagent:rx /var/log/nginx; sudo setfacl -m u:cwagent:r /var/log/nginx/*.log, or choose user root in the wizard for the lab.

6. Step 4: the JSON file explained, and start the agent

This is the same config written by hand. Each part has a reason:

{
  "agent": { "metrics_collection_interval": 60, "run_as_user": "cwagent" },
  "metrics": {
    "namespace": "CWAgent",
    "append_dimensions": { "InstanceId": "${aws:InstanceId}" },
    "metrics_collected": {
      "mem":  { "measurement": ["mem_used_percent"] },
      "disk": { "measurement": ["used_percent"], "resources": ["/"] }
    }
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          { "file_path": "/var/log/nginx/access.log",
            "log_group_name": "/app/nginx/access",
            "log_stream_name": "{instance_id}",
            "retention_in_days": 30 },
          { "file_path": "/var/log/nginx/error.log",
            "log_group_name": "/app/nginx/error",
            "log_stream_name": "{instance_id}",
            "retention_in_days": 30 }
        ]
      }
    }
  }
}
KeyWhy
metrics_collection_interval: 60One value per minute, matches alarms with 60 s period
append_dimensions InstanceIdSo each server's memory is a separate line on graphs
log_stream_name {instance_id}One stream per server; you see which server wrote a line
retention_in_daysStops logs from piling up for ever
resources ["/"]Only the root disk; add "/data" for an extra volume
  1. Start the agent with this file:
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config -m ec2 -s \
  -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a status
# expected: "status": "running"
# from Parameter Store instead of a file:  -c ssm:AmazonCloudWatch-linux
tail -n 20 /opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log   # look for errors
  1. Make some traffic: for i in $(seq 1 50); do curl -s -o /dev/null http://localhost/; curl -s -o /dev/null http://localhost/nope; done.
  2. Verify: CloudWatch → Log groups → /app/nginx/access → a stream named like i-0abc… with your requests (200 and 404).
  3. Verify metrics: CloudWatch → Metrics → CWAgent → InstanceId → mem_used_percent.

Log entries for other stacks (add to collect_list):

{ "file_path": "/home/ec2-user/.pm2/logs/*-out.log", "log_group_name": "/app/node",   "log_stream_name": "{instance_id}", "retention_in_days": 14 },
{ "file_path": "/opt/app/logs/spring.log",           "log_group_name": "/app/java",   "log_stream_name": "{instance_id}", "retention_in_days": 30,
  "multi_line_start_pattern": "^\\d{4}-\\d{2}-\\d{2}" },
{ "file_path": "/var/log/gunicorn/error.log",        "log_group_name": "/app/python", "log_stream_name": "{instance_id}", "retention_in_days": 30 },
{ "file_path": "/var/log/httpd/access_log",          "log_group_name": "/app/httpd/access", "log_stream_name": "{instance_id}", "retention_in_days": 30 }

Why multi_line_start_pattern for Java: a Java stack trace is 30 lines. Without it, every line becomes a separate event and the error is hard to read. With it, a new event starts only at a line that begins with a date.

7. Step 5: many servers with Systems Manager

Why do we need this?

Doing Steps 2-4 by SSH on 20 servers is slow and each server may end up a little different. Systems Manager (SSM) runs the same command on all of them, with no SSH and no open port 22.

How to do it (step by step)

  1. Give every server the role EC2-CloudWatchAgent-Role (Step 1) and a tag Role = web.
  2. Open Systems Manager → Fleet Manager. Verify all servers appear as Online. (The SSM agent is pre-installed on Amazon Linux and Ubuntu AMIs; it needs outbound HTTPS.)
  3. Open Systems Manager → Run Command → Run command. Document: AWS-ConfigureAWSPackage.
  4. Action: Install. Name: AmazonCloudWatchAgent. Version: latest.
  5. Targets: Specify instance tags → Role = web. Why tags: new web servers are included without editing the command.
  6. Output options: untick S3 for the lab. Run. Verify: each target shows Success.
  7. Run command again. Document: AmazonCloudWatch-ManageAgent. Action configure, Mode ec2, Optional Configuration Source ssm, Optional Configuration Location AmazonCloudWatch-linux, Optional Restart yes. Same tag target. Run.
  8. Verify: the log group /app/nginx/access now has one stream per server.

The same with the CLI:

aws ssm put-parameter --name AmazonCloudWatch-linux --type String --overwrite \
  --value file://config.json
aws ssm send-command --document-name AWS-ConfigureAWSPackage \
  --targets Key=tag:Role,Values=web \
  --parameters '{"action":["Install"],"name":["AmazonCloudWatchAgent"]}'
aws ssm send-command --document-name AmazonCloudWatch-ManageAgent \
  --targets Key=tag:Role,Values=web \
  --parameters '{"action":["configure"],"mode":["ec2"],"optionalConfigurationSource":["ssm"],"optionalConfigurationLocation":["AmazonCloudWatch-linux"],"optionalRestart":["yes"]}'
aws logs describe-log-streams --log-group-name /app/nginx/access --query 'logStreams[].logStreamName'
# expected: ["i-0aaa...", "i-0bbb...", "i-0ccc..."]

Ravindra Bagale's Tip

For an Auto Scaling group, put the install and fetch-config commands in the launch template user data, or bake the agent into the AMI. Every new server then sends logs from its first minute.

8. Step 6: retention

  1. CloudWatch → Log groups → tick /app/nginx/access → Actions → Edit retention setting(s) → 1 month (30 days) → Save.
  2. CLI: aws logs put-retention-policy --log-group-name /app/nginx/error --retention-in-days 30.
  3. Verify: the Retention column shows 1 month instead of Never expire.

9. Step 7: search everything with Logs Insights

Why do we need this?

Scrolling through streams is slow. Logs Insights is a small query language that searches all streams of one or more log groups at once.

How to do it (step by step)

  1. CloudWatch → Logs Insights. Select log group /app/nginx/access. Time range: 1h.
  2. Paste a query below and click Run query. Start with the simplest.
# 1. latest 20 lines from all servers
fields @timestamp, @logStream, @message
| sort @timestamp desc
| limit 20

# 2. only 404 and 5xx lines
fields @timestamp, @logStream, @message
| filter @message like / 404 / or @message like / 50[0-9] /
| sort @timestamp desc

# 3. requests per server (which server gets the most?)
stats count(*) as requests by @logStream
| sort requests desc

# 4. errors per 5 minutes, ready for a graph
filter @message like / 50[0-9] /
| stats count(*) as errors by bin(5m)

# 5. top client IPs (nginx combined format: IP is the first word)
parse @message "* - * [*] \"* * *\" * *" as ip, user, time, method, path, proto, status, bytes
| stats count(*) as hits by ip
| sort hits desc
| limit 10

Verify: query 3 shows one row per instance ID, query 2 shows the /nope 404 lines you created.

Bonus: alarm on errors in logs (metric filter)

  1. Log group /app/nginx/access → Actions → Create metric filter.
  2. Filter pattern: [ip, id, user, ts, request, status=5*, size]. Test it, Next.
  3. Filter name nginx-5xx, namespace App/Web, metric name Nginx5xx, value 1, default value 0. Create.
  4. Create an alarm on App/Web Nginx5xx, statistic Sum, 5 minutes, > 10, SNS ops-alerts.

10. Cleanup

  1. Delete the log groups /app/nginx/access and /app/nginx/error (Actions → Delete log group(s)).
  2. Delete the metric filter alarm and the parameter: aws ssm delete-parameter --name AmazonCloudWatch-linux.
  3. Stop the agent (amazon-cloudwatch-agent-ctl -a stop) or terminate the servers. Why: the agent keeps sending custom metrics, which are billed.

11. Interview questions

Q. Which policy does the CloudWatch agent need?

CloudWatchAgentServerPolicy on an EC2 instance role (plus AmazonSSMManagedInstanceCore if you manage it with SSM).

Q. How do you tell which server wrote a log line?

Use log_stream_name {instance_id}, so each instance writes to its own stream; in Logs Insights use @logStream.

Q. What is the default retention of a log group?

Never expire. Always set a retention.

Q. How do you install the agent on 100 servers?

Systems Manager Run Command: AWS-ConfigureAWSPackage to install, AmazonCloudWatch-ManageAgent with the config in Parameter Store, targeted by tag.

Try at home

  1. Launch two servers with the role and tag Role=web. Use SSM to install and configure the agent on both.
  2. Run Logs Insights query 3 and confirm both instance IDs appear.
  3. Add /var/log/secure to the config and find failed SSH logins with filter @message like /Failed password/.

In short

  1. Log group = folder; log stream = one source; use {instance_id} as the stream name.
  2. Role with CloudWatchAgentServerPolicy, never access keys.
  3. Wizard for the first server, JSON in Parameter Store for all.
  4. SSM Run Command installs and configures the agent on many servers by tag.
  5. Set retention; search with Logs Insights; alarm with metric filters.

Practice Tasks

About this scenario

Bavaria Motors is a automobile business in Munich. Lukas Becker looks after its IT and cloud work. They count money in EUR and run their AWS resources in the eu-central-1 Region. In the tasks below you do real jobs for them using what this lesson teaches. All names, account IDs, IP addresses and numbers are made up for practice.

0 / 12

Easy Create a log group

Why: A log group holds log streams from all your servers for one app.

What to do: Create log group /bavariamotors/web.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Easy Find errors in a log

Why: grep shows only the lines that contain a word, so you find problems fast.

What to do: Show every line containing ERROR in /var/log/bavariamotors/app.log.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy List metrics in a namespace

Why: See which metrics AWS already collects for a service.

What to do: List the metrics in the AWS/EC2 namespace.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Easy Reboot an instance

Why: A reboot keeps the same disks and IPs, unlike stop/start which may change the public IP.

What to do: Reboot the instance i-040cb0cd42a011a2b.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Medium Set log retention

Why: Without retention, logs are kept forever and you keep paying.

What to do: Keep logs in /bavariamotors/web for 30 days.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Medium Follow logs live

Why: aws logs tail --follow is like tail -f for CloudWatch Logs.

What to do: Follow the log group /aws/lambda/bavariamotors-orders-fn live.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Medium Let a function write logs

Why: Without these actions, Lambda logs never appear in CloudWatch.

What to do: Allow logs:CreateLogGroup, logs:CreateLogStream, logs:PutLogEvents on arn:aws:logs:*:*:* only.

How:
  1. Start with { "Version": "2012-10-17", "Statement": [ ... ] }.
  2. In each statement write Effect, Action and Resource.
  3. Give only what the task needs (least privilege).
  4. Use double quotes and commas correctly. Click Check answer.

Medium See the last lines of a log

Why: New errors are at the end of a log file. You rarely need the whole file.

What to do: Show only the last 20 lines of /var/log/bavariamotors/app.log.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Publish a custom metric

Why: Custom metrics let you graph and alarm on business numbers, like orders per minute.

What to do: Publish metric OrdersPlaced with value 6 in namespace BavariaMotors/Shop.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Medium Check instance status checks

Why: System and instance status checks tell you if AWS hardware or your OS has a problem.

What to do: Show the status checks of instance i-040cb0cd42a011a2b.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Hard Search logs for errors

Why: filter-log-events searches all streams in a group at once.

What to do: Search /bavariamotors/web for events matching the pattern ERROR.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Hard Create a CPU alarm

Why: An alarm watches a metric and notifies you before users notice a problem.

What to do: Create alarm bavariamotors-cpu-high: AWS/EC2 CPUUtilization, Average over 300 seconds, 1 evaluation period, GreaterThanThreshold 80, for InstanceId=i-040cb0cd42a011a2b, notify arn:aws:sns:eu-central-1:106249807804:bavariamotors-alerts.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Revision, comparison and interview cards

Revise this lesson in a few minutes. Save the cards on your phone or download them as PNG files. The QR code on each card opens this lesson.

Revision card: Log group, stream and retention. Five servers, one error: without one place for logs you SSH into all five. Log event = one line. Log stream = lines from one source. Log group = folder of streams. Retention = days to keep. Use one stream per instance and set retention; the default is Never expire. Group /app/nginx/access holds streams i-0aaa, i-0bbb, i-0ccc. Retention 30 days.
Revision card: Log group, stream and retention · Download PNG
Revision card: IAM role for the CloudWatch agent. The agent must prove it may write logs. Access keys on servers can leak. An EC2 role gives short-lived keys automatically. CloudWatchAgentServerPolicy allows sending metrics and logs. IAM › Create role › EC2 › add CloudWatchAgentServerPolicy and AmazonSSMManagedInstanceCore. Role EC2-CloudWatchAgent-Role attached to web1 → agent writes to /app/nginx/access.
Revision card: IAM role for the CloudWatch agent · Download PNG
Revision card: CloudWatch agent config. EC2 cannot see memory, disk space or log files. The agent runs inside the server and sends them. A JSON file lists metrics (mem, disk) and log files with their log group, stream name and retention. Run the wizard once, or write the JSON, then load it with fetch-config. Check with -a status → "running". Logs appear in a minute.
Revision card: CloudWatch agent config · Download PNG
Revision card: CloudWatch Logs Insights. Scrolling streams of 5 servers is slow. One query should search them all. Logs Insights is a small query language over one or more log groups. Pick log group and time range, write fields / filter / stats, click Run query. filter @message like / 50[0-9] / shows every 5xx line from every server.
Revision card: CloudWatch Logs Insights · Download PNG
Comparison: Manual agent install vs SSM Run Command install. Manual agent install: SSH into a server, install and configure the CloudWatch agent by hand. SSM Run Command install: Systems Manager runs the install and config on many servers at once.
Comparison: Manual agent install vs SSM Run Command install · Download PNG
Comparison: Config wizard vs JSON config file. Config wizard: A program that asks questions and writes the agent config. JSON config file: The agent config written or edited by hand.
Comparison: Config wizard vs JSON config file · Download PNG
Comparison: Log group vs Log stream. Log group: A folder of streams of the same kind, with one retention. Log stream: All lines from one source, such as one instance.
Comparison: Log group vs Log stream · Download PNG
Comparison: Logs Insights vs Metric filter. Logs Insights: A query language you run over log groups. Metric filter: A rule that turns matching log lines into a metric.
Comparison: Logs Insights vs Metric filter · Download PNG
Interview card: Which IAM policy does the CloudWatch agent need on EC2?. CloudWatchAgentServerPolicy on an EC2 instance role. A role gives short-lived keys; access keys stored on servers can leak. Add AmazonSSMManagedInstanceCore too if Systems Manager will install the agent.
Interview card: Which IAM policy does the CloudWatch agent need on EC2? · Download PNG
Interview card: Logs come from 10 servers. How do you know which server wrote a line?. Set log_stream_name to {instance_id}, so each server writes its own stream. A customer complaint needs the exact server, even after Auto Scaling removed it. In Logs Insights read @logStream, or count with stats count(*) by @logStream.
Interview card: Logs come from 10 servers. How do you know which server wrote a line? · Download PNG
Interview card: How do you install the CloudWatch agent on 100 servers?. Systems Manager Run Command, targeted by a tag, with the config in Parameter Store. No SSH, no port 22, and every server gets the same config. AWS-ConfigureAWSPackage (install AmazonCloudWatchAgent), then AmazonCloudWatch-ManageAgent (configure, ssm).
Interview card: How do you install the CloudWatch agent on 100 servers? · Download PNG
Interview card: What is the default retention of a CloudWatch log group?. Never expire. Logs stay until you delete them. Stored logs are billed every month, so the bill keeps growing. Set 30 days for access logs, 90+ for audit logs.
Interview card: What is the default retention of a CloudWatch log group? · Download PNG

More cards: Ravi Sir's Cards.