How to Search CloudWatch Logs with AWS CLI Commands

Cloud log events flow through a command-line filter, isolating application errors for troubleshooting

What You’ll Learn

In this lesson, you’ll use AWS CLI CloudWatch Logs commands to investigate application failures from the command line. You will learn how to search across a log group, retrieve events from a specific stream, and shape the returned data into useful output.

  • Filter application logs for messages such as ERROR.
  • Convert a time range into the epoch milliseconds required by CloudWatch Logs.
  • Retrieve recent events from an individual log stream.
  • Avoid common issues involving pagination, permissions, and timestamps.

The Concept

CloudWatch Logs stores application output in log groups and log streams. A log group commonly represents an application or service, while a stream often represents a particular container, host, or execution instance.

The AWS CLI provides several commands for working with these logs:

  • filter-log-events searches events across one or more streams in a log group.
  • describe-log-streams lists streams and metadata such as their most recent event time.
  • get-log-events retrieves events from one specific stream.

For application troubleshooting, filter-log-events is usually the best starting point because you can search a time range and return only messages matching a filter pattern. Once you identify a stream of interest, get-log-events can provide more focused context.

CloudWatch Logs expects --start-time and --end-time values as epoch time in milliseconds. The examples use the Bash date command to calculate a recent time range.

Basic Example

The following command searches the /applications/orders log group for events containing ERROR from the last 15 minutes. It returns the event timestamp, message, and stream name.

#!/usr/bin/env bash

LOG_GROUP="/applications/orders"
START_TIME=$(date -d "15 minutes ago" +%s000)

aws logs filter-log-events \
    --log-group-name "$LOG_GROUP" \
    --start-time "$START_TIME" \
    --filter-pattern "ERROR" \
    --query 'events[*].[timestamp,logStreamName,message]' \
    --output table

The date -d syntax is available on GNU/Linux systems. On macOS, you can replace the time calculation with START_TIME=$(($(date +%s) - 900))000.

Expected Output

The exact events depend on your account and application. The table will have a structure similar to this:

--------------------------------------------------------------
|                       FilterLogEvents                     |
+---------------+----------------------+-------------------+
|  1710000123456| 2024-03-09/orders-1  | ERROR Database... |
|  1710000456789| 2024-03-09/orders-2  | ERROR Timeout...  |
+---------------+----------------------+-------------------+

How the Code Works

A flow showing Bash calculating an epoch-millisecond time range, sending a filter-log-events search to a CloudWatch log group, formatting matching error events, and optionally selecting a stream for focused get-log-events retrieval.
Use filter-log-events to find errors across a log group, then inspect a known stream and format the results for troubleshooting or automation.

--log-group-name identifies the group to search. The group must already exist, and its name is case-sensitive.

--start-time limits the search to events after the supplied epoch-millisecond value. If you omit --end-time, CloudWatch Logs searches through the current time.

--filter-pattern "ERROR" searches for events containing the term ERROR. CloudWatch Logs filter patterns support more than simple terms. For example, quoted phrases can search for an exact phrase, and JSON log fields can be filtered with expressions such as { $.level = "ERROR" } when your application writes structured JSON logs.

The --query option is a client-side JMESPath expression. Here, it selects three fields from every event:

  • timestamp: the event time in epoch milliseconds.
  • logStreamName: the stream that produced the event.
  • message: the application log message.

Finally, --output table makes the result easier to scan during an interactive investigation. Use --output json or --output text when another script will process the result.

Filtering happens on the CloudWatch Logs service, which is more useful than downloading every event and searching locally. However, the response is paginated. The AWS CLI normally follows pagination for this command, but a very large time range or broad filter can still produce a substantial response. Narrow the time range and use a specific filter pattern when possible.

Another Example

After finding a problem, you may want to inspect the most recently active stream directly. This example lists streams by their latest event time, selects the first stream, and retrieves up to 50 recent events from it.

#!/usr/bin/env bash

LOG_GROUP="/applications/orders"

LATEST_STREAM=$(aws logs describe-log-streams \
    --log-group-name "$LOG_GROUP" \
    --order-by LastEventTime \
    --descending \
    --max-items 1 \
    --query 'logStreams[0].logStreamName' \
    --output text)

if [ "$LATEST_STREAM" = "None" ] || [ -z "$LATEST_STREAM" ]; then
    printf 'No log streams were found in %s\n' "$LOG_GROUP"
    exit 0
fi

printf 'Recent events from stream: %s\n' "$LATEST_STREAM"

aws logs get-log-events \
    --log-group-name "$LOG_GROUP" \
    --log-stream-name "$LATEST_STREAM" \
    --limit 50 \
    --start-from-head false \
    --query 'events[*].[timestamp,message]' \
    --output table

This approach is useful when a service writes each container or host to a separate stream and you want to inspect the stream with the newest activity. The stream’s latest-event metadata can be briefly stale, so treat the result as a useful starting point rather than an absolute real-time guarantee.

Common Mistakes

  • Using seconds instead of milliseconds: date +%s returns seconds. CloudWatch Logs requires milliseconds, so the example appends 000.
  • Searching the wrong group or region: AWS CLI commands use the configured region unless you provide --region. Confirm both the log group and region before troubleshooting the command.
  • Expecting get-log-events to search a group: This command requires one specific stream. Use filter-log-events when you do not yet know which stream contains the error.
  • Forgetting permissions: The identity running these commands generally needs permissions such as logs:FilterLogEvents, logs:DescribeLogStreams, and logs:GetLogEvents.
  • Assuming a plain word is a full-text regular expression: CloudWatch filter patterns have their own syntax. They are not regular expressions by default. Use documented filter-pattern expressions for terms, phrases, exclusions, or JSON fields.

Try It Yourself

Choose a log group from your account and modify the first example so that it searches the last 30 minutes for messages containing Timeout. Return the timestamp, stream name, and message as JSON instead of a table.

As a follow-up, change the filter pattern to search for the exact phrase connection refused. Compare the results with a search for the individual term connection.

Challenge

Write a Bash script that searches the /applications/payments log group for the last 30 minutes and displays application errors.

Your script must:

  • Calculate the start time in epoch milliseconds.
  • Search for events containing ERROR.
  • Return the timestamp, log stream name, and message.
  • Limit the displayed results to 100 events.
  • Use JSON output so the results can be passed to another automation step later.
  • Print a useful message if no matching events are returned.

Solution

#!/usr/bin/env bash

LOG_GROUP="/applications/payments"
START_TIME=$(date -d "30 minutes ago" +%s000)

ERROR_EVENTS=$(aws logs filter-log-events \
    --log-group-name "$LOG_GROUP" \
    --start-time "$START_TIME" \
    --filter-pattern "ERROR" \
    --limit 100 \
    --query 'events[*].[timestamp,logStreamName,message]' \
    --output json)

if [ "$ERROR_EVENTS" = "[]" ]; then
    printf 'No ERROR events found in %s during the last 30 minutes.\n' "$LOG_GROUP"
else
    printf '%s\n' "$ERROR_EVENTS"
fi

The command searches only the required time window, limits the response to 100 events, and selects the three fields needed for investigation. Because the output is JSON, a later pipeline can pass it to a JSON parser or store it as an incident artifact. The empty-array check handles the case where the command succeeds but no matching events exist.

On macOS, replace the START_TIME assignment with:

START_TIME=$(($(date +%s) - 1800))000

Key Takeaways

  • filter-log-events searches for matching events across a CloudWatch Logs group.
  • get-log-events retrieves events from one known log stream.
  • CloudWatch Logs time arguments use epoch milliseconds, not seconds.
  • Use --query to select useful fields and --output to choose a format for people or scripts.
  • Narrow time ranges and precise filter patterns make log investigations faster and easier to automate.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top