CloudWatch Logs source
Pulls events from AWS CloudWatch Logs using the FilterLogEvents API.
Cheap, real-time, no async query lifecycle.
Minimal config
sources:
- name: lambda-prod
type: cloudwatchlogs
enable: true
cloudwatchlogs:
region: us-east-1
log_group_name: /aws/lambda/my-function
page_size: 500
Narrowed query
cloudwatchlogs:
region: us-east-1
log_group_name: /aws/ecs/api
log_stream_prefix: "ecs/api/" # only recent task streams
filter_pattern: '?ERROR ?Exception ?panic'
page_size: 1000
Full reference
cloudwatchlogs:
region: us-east-1 # REQUIRED.
log_group_name: /aws/lambda/my-fn # REQUIRED. Exact log group name.
log_stream_prefix: "" # restrict to streams starting with this string
filter_pattern: "" # CloudWatch filter syntax — NOT regex
page_size: 500 # max 10000
Authentication
The source uses the standard AWS SDK credential chain in this order:
- Environment variables:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN. - Shared credentials file:
~/.aws/credentials(profile fromAWS_PROFILE, default"default"). - ECS task role (when running in ECS / Fargate).
- EC2 instance profile / EKS IRSA (IAM Roles for Service Accounts).
Required IAM policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:FilterLogEvents", "logs:DescribeLogStreams"],
"Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/my-function:*"
}
]
}
For multiple log groups, list each Resource ARN — wildcards work
(arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/*).
Behavior
-
Cursor — The maximum event timestamp seen on the previous tick. CloudWatch event timestamps are milliseconds since epoch. The next call uses
startTime = cursor + 1msto avoid re-reading the boundary event. -
Pagination — Walks
NextTokenup to 20 pages OR untilpage_sizeevents are collected, whichever happens first. Anything beyond that is read on the next tick. -
Filter pattern syntax — CloudWatch filter syntax, NOT regex:
Pattern Matches ERRORevents containing the word ERROR?ERROR ?Exceptionevents containing ERRORORException"connection refused"exact phrase [time, level=ERROR, ...]space-delimited fields where level == "ERROR"{$.level = "error"}JSON event with level: "error"See the AWS docs for the full grammar.
Cost notes
FilterLogEventsis billed per GB scanned. Always setfilter_patternand/orlog_stream_prefixon chatty groups.page_sizecontrols the per-call cap;agent.poll_intervalcontrols the call rate. A noisy Lambda group withpoll_interval: 30smakes ~120 calls/hour per source.- Use
log_stream_prefixfor ECS/EKS where each task creates a new stream — you don't need to scan retired streams.
Troubleshooting
| Symptom | Likely cause |
|---|---|
AccessDeniedException | IAM policy missing logs:FilterLogEvents for the group ARN. |
ResourceNotFoundException | Wrong log_group_name (case-sensitive) or wrong region. |
ThrottlingException | Lower page_size or raise poll_interval; CloudWatch quotas are per-account-per-region. |
| Empty results despite events in console | filter_pattern is regex-style instead of CloudWatch syntax. |
Local testing without AWS
The integration tests in
pkg/signalsources/cloudwatchlogs_test.go
use httptest + the SDK's BaseEndpoint option to point at a local
mock — useful as a template if you want to wire your own integration
tests.