CloudWatch
Floci supports both CloudWatch Logs and CloudWatch Metrics.
CloudWatch Logs
Protocol: JSON 1.1 (X-Amz-Target: Logs.*)
Endpoint: POST http://localhost:4566/
Supported Actions
| Action | Description |
|---|---|
CreateLogGroup |
Create a log group |
DeleteLogGroup |
Delete a log group |
DescribeLogGroups |
List log groups |
CreateLogStream |
Create a log stream inside a log group |
DeleteLogStream |
Delete a log stream |
DescribeLogStreams |
List log streams in a group |
PutLogEvents |
Write log events to a stream |
GetLogEvents |
Read log events from a stream |
FilterLogEvents |
Search log events with a filter pattern |
PutRetentionPolicy |
Set log retention (days) |
DeleteRetentionPolicy |
Remove log retention policy |
TagLogGroup |
Tag a log group |
UntagLogGroup |
Remove tags |
ListTagsLogGroup |
List tags |
TagResource |
Tag a log group by ARN |
UntagResource |
Remove tags from a log group by ARN |
ListTagsForResource |
List tags for a log group ARN |
PutSubscriptionFilter |
Create or update a subscription filter (stored only, see note below) |
DescribeSubscriptionFilters |
List subscription filters on a log group |
DeleteSubscriptionFilter |
Delete a subscription filter |
GetDataProtectionPolicy |
Return the resolved log group identifier (see note below) |
StartQuery |
Start a Logs Insights query (see Logs Insights) |
GetQueryResults |
Get the status and results of a Logs Insights query |
StopQuery |
Stop a query that has not completed yet |
Two actions are currently simplified:
PutSubscriptionFilterstores the filter so thatDescribeSubscriptionFiltersreturns it, but log events are not forwarded to the destination ARN. Lambda, Kinesis, and Firehose subscription destinations are not wired up.GetDataProtectionPolicydoes not model data-protection policies. It returns HTTP 200 with the resolvedlogGroupIdentifierand nopolicyDocument— including for a log group that does not exist, where real AWS returnsResourceNotFoundException.
Logs Insights
StartQuery / GetQueryResults / StopQuery run a subset of the Logs Insights
query language. Supported commands:
| Command | Notes |
|---|---|
fields a, b, ... |
Projection. Defaults to @timestamp, @message. display is an alias |
filter <field> = 'v' |
Equality. != and == are also accepted; where is an alias |
sort <field> [asc\|desc] |
order is an alias |
dedup <field, ...> |
Keeps the first row per unique tuple, applied after sorting |
limit N |
The effective cap is the smallest of this value, the StartQuery limit parameter, and FLOCI_SERVICES_CLOUDWATCHLOGS_MAX_EVENTS_PER_QUERY |
Fields may be @timestamp, @message, @ingestionTime, @ptr, or a dotted path into
a JSON log message (for example level or params.job_id). A @ptr column is always
included in each result row, appended unless fields already names it.
Unsupported syntax never fails the query, so it is worth knowing how each case degrades:
| Input | Result |
|---|---|
An unsupported command (stats, parse, ...) |
Skipped with a warning in the server log. No aggregation happens |
A filter whose operator is not =, != or == — for example like /ERROR/ or =~ /ERROR/ |
The whole stage is dropped with a warning, so every row is returned |
A filter using <, <=, > or >= |
The = is taken as the operator and the rest of the token becomes part of the field name, which then resolves to nothing — so the row never matches and you get no rows. No warning is logged |
| A projected field that does not exist | Rendered as an empty string. No warning |
A sort direction other than asc / desc |
Treated as ascending. No warning |
In short, a query can come back either wider or narrower than intended without any error. When a
result set looks wrong, check the server log for Ignoring unsupported Logs Insights ... — and
note that the >= case above produces no log line at all.
For simple substring matching, FilterLogEvents is the more predictable option today. Note that
Floci matches --filter-pattern as a plain substring of the message; the real filter-pattern
syntax (?ERROR ?WARN, { $.level = "ERROR" }, and so on) is not parsed.
Configuration
| Variable | Default | Description |
|---|---|---|
FLOCI_SERVICES_CLOUDWATCHLOGS_ENABLED |
true |
Enable or disable the CloudWatch Logs service |
FLOCI_SERVICES_CLOUDWATCHLOGS_MAX_EVENTS_PER_QUERY |
10000 |
Maximum events returned per FilterLogEvents / GetLogEvents call, and the upper bound for a Logs Insights limit |
FLOCI_SERVICES_CLOUDWATCHLOGS_QUERY_COMPLETION_DELAY_MS |
0 |
Artificial Logs Insights query delay. With 0 a query completes immediately. A positive value emulates the real asynchronous lifecycle (Running → Complete after the delay), which also makes StopQuery on a still-running query return success=true |
Examples
export AWS_ENDPOINT_URL=http://localhost:4566
# Create a log group and stream
aws logs create-log-group --log-group-name /app/backend --endpoint-url $AWS_ENDPOINT_URL
aws logs create-log-stream \
--log-group-name /app/backend \
--log-stream-name 2025/01/app-1 \
--endpoint-url $AWS_ENDPOINT_URL
# Write log events
TIMESTAMP=$(date +%s%3N) # milliseconds
aws logs put-log-events \
--log-group-name /app/backend \
--log-stream-name 2025/01/app-1 \
--log-events "[{\"timestamp\":$TIMESTAMP,\"message\":\"Service started\"}]" \
--endpoint-url $AWS_ENDPOINT_URL
# Read log events
aws logs get-log-events \
--log-group-name /app/backend \
--log-stream-name 2025/01/app-1 \
--endpoint-url $AWS_ENDPOINT_URL
# Search logs
aws logs filter-log-events \
--log-group-name /app/backend \
--filter-pattern "ERROR" \
--endpoint-url $AWS_ENDPOINT_URL
# Run a Logs Insights query
QUERY_ID=$(aws logs start-query \
--log-group-name /app/backend \
--start-time $(($(date +%s) - 3600)) \
--end-time $(date +%s) \
--query-string 'fields @timestamp, @message | sort @timestamp desc | limit 20' \
--query queryId --output text \
--endpoint-url $AWS_ENDPOINT_URL)
aws logs get-query-results \
--query-id "$QUERY_ID" \
--endpoint-url $AWS_ENDPOINT_URL
# Set retention
aws logs put-retention-policy \
--log-group-name /app/backend \
--retention-in-days 30 \
--endpoint-url $AWS_ENDPOINT_URL
CloudWatch Metrics
Protocol: Query (XML) and JSON 1.1 (both supported)
Endpoint: POST http://localhost:4566/
Supported Actions
| Action | Description |
|---|---|
PutMetricData |
Publish custom metrics |
ListMetrics |
List available metrics |
GetMetricStatistics |
Get metric statistics (Average, Sum, etc.) |
GetMetricData |
Query metrics with math expressions |
PutMetricAlarm |
Create a metric alarm |
DescribeAlarms |
List alarms |
DeleteAlarms |
Delete alarms |
SetAlarmState |
Manually set alarm state |
Examples
export AWS_ENDPOINT_URL=http://localhost:4566
# Publish a custom metric
aws cloudwatch put-metric-data \
--namespace MyApp \
--metric-data '[{
"MetricName": "RequestCount",
"Value": 42,
"Unit": "Count",
"Dimensions": [{"Name":"Service","Value":"api"}]
}]' \
--endpoint-url $AWS_ENDPOINT_URL
# List metrics
aws cloudwatch list-metrics \
--namespace MyApp \
--endpoint-url $AWS_ENDPOINT_URL
# Get statistics
aws cloudwatch get-metric-statistics \
--namespace MyApp \
--metric-name RequestCount \
--dimensions Name=Service,Value=api \
--start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 300 \
--statistics Sum \
--endpoint-url $AWS_ENDPOINT_URL
# Create an alarm
aws cloudwatch put-metric-alarm \
--alarm-name high-error-rate \
--metric-name ErrorCount \
--namespace MyApp \
--statistic Sum \
--period 60 \
--threshold 10 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 1 \
--endpoint-url $AWS_ENDPOINT_URL