second first commit
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
# 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 |
|
||||
| `PutLogGroupDeletionProtection` | Enable or disable deletion protection for a log group by name or ARN |
|
||||
| `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 |
|
||||
| `AssociateKmsKey` | Associate a KMS key with a log group (`logGroupName` or `resourceIdentifier`) |
|
||||
| `DisassociateKmsKey` | Remove a log group's KMS key association |
|
||||
| `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 |
|
||||
| `PutMetricFilter` | Create or replace a metric filter on a log group (see [Metric filters](#metric-filters)) |
|
||||
| `DescribeMetricFilters` | List metric filters by log group and name prefix, or by metric name and namespace |
|
||||
| `DeleteMetricFilter` | Delete a metric filter |
|
||||
| `TestMetricFilter` | Run a filter pattern over sample messages and return the matches with their extracted values |
|
||||
| `PutResourcePolicy` | Create or update an account-level resource policy |
|
||||
| `DescribeResourcePolicies` | List account-level resource policies |
|
||||
| `PutDestination` | Create or update a cross-account subscription destination |
|
||||
| `PutDestinationPolicy` | Create or update the access policy for a destination |
|
||||
| `PutAccountPolicy` | Create or update an account-level Logs policy |
|
||||
| `DescribeAccountPolicies` | List account-level Logs policies by type and optional name |
|
||||
| `GetDataProtectionPolicy` | Return the resolved log group identifier (see note below) |
|
||||
| `StartQuery` | Start a Logs Insights query (see [Logs Insights](#logs-insights)) |
|
||||
| `GetQueryResults` | Get the status and results of a Logs Insights query |
|
||||
| `StopQuery` | Stop a query that has not completed yet |
|
||||
|
||||
Log group deletion protection defaults to disabled and is persisted with the log group. When it is
|
||||
enabled, `DeleteLogGroup` returns `ValidationException` until protection is explicitly disabled
|
||||
with `PutLogGroupDeletionProtection`.
|
||||
|
||||
Three actions are currently simplified:
|
||||
|
||||
- **`PutSubscriptionFilter`** stores the filter so that `DescribeSubscriptionFilters`
|
||||
returns it, but log events are **not** forwarded to the destination ARN. Lambda,
|
||||
Kinesis, and Firehose subscription destinations are not wired up.
|
||||
- **`GetDataProtectionPolicy`** does not model data-protection policies. It returns
|
||||
HTTP 200 with the resolved `logGroupIdentifier` and no `policyDocument` — including for
|
||||
a log group that does not exist, where real AWS returns `ResourceNotFoundException`.
|
||||
- **`FilterLogEvents`** matches `filterPattern` as a plain substring of the message. The full
|
||||
filter pattern syntax described below is applied by metric filters.
|
||||
|
||||
### Metric filters {#metric-filters}
|
||||
|
||||
A metric filter turns newly ingested log events into CloudWatch metric samples. Each matching
|
||||
event contributes the literal `metricValue`, or its referenced field (`$.latency` in JSON,
|
||||
`$size` in space-delimited logs), at the **event timestamp**, not ingestion or filter-creation time.
|
||||
Events ingested before the filter existed are not replayed. Backdated and future event timestamps
|
||||
remain backdated and future metric timestamps.
|
||||
|
||||
A configured `defaultValue` contributes **once per nonmatching event**, including alongside matches
|
||||
in the same batch or minute. A matching event with a missing or JSON-null metric-value field also
|
||||
uses the default; a present nonnumeric value is skipped, not replaced by the default. Without a
|
||||
default, those nonmatches and missing/null values produce no sample. No events means no samples.
|
||||
Healthy publication is immediate and needs no subsequent ingestion, reads, or timer. For example,
|
||||
pattern `ERROR`, value 3 and default 7 produce Sum 13 / SampleCount 3 for `ERROR, INFO, ERROR`,
|
||||
and three quiet nonmatches produce Sum 21 / SampleCount 3. A late match appends its value without
|
||||
retracting earlier defaults. CloudWatch aggregates these contributions by period; Floci has no
|
||||
minute-default accumulator or deduplication history.
|
||||
|
||||
Ordinary transformation dimensions are all-or-none: a complete configured set is emitted, while
|
||||
a missing member produces a dimensionless sample, not a partial set. Requested system dimensions
|
||||
(`@aws.account`, `@aws.region`) are populated on matches using the resolved receiving account and
|
||||
region, including regular noncentralized Logs ingestion. **Pattern-nonmatch defaults are
|
||||
dimensionless**, even when system dimensions are requested. These scenarios are supported by the
|
||||
recorded [live AWS observations](cloudwatch-metric-filters-verification.md).
|
||||
|
||||
**Inferred emulator policy, not a live AWS claim:** matching missing/null metric-value fallbacks
|
||||
retain requested system dimensions. Likewise, an incomplete ordinary dimension set is discarded
|
||||
as a unit while requested system dimensions are retained. Null ordinary dimension values are
|
||||
treated as missing; empty strings retain the existing scalar-extraction behavior. These combinations
|
||||
were not measured by the recorded probes. Floci does not infer centralized source provenance.
|
||||
|
||||
**Publication runs off the request thread:** `PutLogEvents` evaluates the group's filters as they
|
||||
are at that moment, builds one immutable sample per contributing event (canonical account, region,
|
||||
group, filter, transformation snapshot, event timestamp and a stable internal publication ID) and
|
||||
hands them to a process-wide queue. One managed worker writes them to the Metrics store, so
|
||||
ingestion latency does not depend on the Metrics sink and a `PutMetricFilter` or a reset waits for
|
||||
at most one sample write. The queue holds **100,000 samples**; a batch that does not fit is written
|
||||
on the request thread under the filter-store monitor instead of being dropped, so accepted samples
|
||||
are never lost to the bound and only an overfull queue makes the request wait on the sink.
|
||||
A read issued immediately after `PutLogEvents` may run before the worker has written the sample,
|
||||
as on AWS; SDK tests should poll. Shutdown drains the queue before storage shuts down.
|
||||
|
||||
**Runtime publication failures:** accepted Logs ingestion remains successful if the Metrics sink
|
||||
reports a failure. Each failed sample keeps its snapshot and publication ID, and retries replace
|
||||
that same metric-store key, making partial or ambiguous writes safe without deduplicating distinct
|
||||
identical log events. The existing AWS `PutMetricData` APIs continue appending samples as before.
|
||||
|
||||
The same worker retries failed publications at one-second intervals while both Logs and Metrics
|
||||
services are enabled; idle ticks never create defaults. The retry map holds at most **10,000
|
||||
samples**, not 10,000 batches. This fixed conservative limit bounds outage memory without
|
||||
introducing a configuration surface. When full, new failed samples are dropped with an **ERROR**
|
||||
containing account, region, group, filter and dropped count; already queued samples and their IDs are
|
||||
preserved. Successful retries and cancellations release capacity. Each filter-owned outage logs
|
||||
one detailed stack, followed by a stack-free aggregate warning every 60 failed retry ticks.
|
||||
Recovery, cancellation and reset release that suppression. This is bounded best effort, not
|
||||
lossless delivery through an indefinite outage. Pending work is in memory only; crash-durable
|
||||
pending publication is not implemented. Storage failures that a backend only logs rather than
|
||||
throws are not observable to this retry path; configured storage durability semantics are unchanged.
|
||||
|
||||
A filter update or deletion affects new ingestion only: samples already accepted for a batch still
|
||||
publish under the definition that was current at ingestion, as on AWS, and deleting a filter or
|
||||
group cancels only its own account/region-scoped failed retries without retracting stored metrics.
|
||||
Reset pauses publication, waits for the active sample write, discards queued and failed samples,
|
||||
wipes storage and resumes the same live service. The canonical filter-store monitor serializes
|
||||
each sample write with update/delete; reset hooks release it before the storage-factory wipe to
|
||||
avoid lock inversion. Shutdown writes the queued samples, abandons failed retries and stops the
|
||||
worker (up to five seconds to await executor termination). Storage backends expose synchronous
|
||||
operations without a cancellation deadline; a stuck backend can delay that drain. Runtime
|
||||
cancellation is not a crash-recovery guarantee.
|
||||
|
||||
Samples are readable with `GetMetricStatistics` and `GetMetricData`, and alarms evaluate normally.
|
||||
|
||||
The filter pattern syntax follows the AWS reference:
|
||||
|
||||
- Terms: `ERROR ARGUMENTS` (all present), `?ERROR ?ARGUMENTS` (any present), `ERROR -ARGUMENTS`
|
||||
(exclusion), `"exact phrase"`. Terms are case-sensitive substrings of the message.
|
||||
- Regular expressions between percent signs, `%^[hc]at%`, with the operators and escapes AWS
|
||||
allows. Parentheses and characters outside ASCII are rejected, as on AWS.
|
||||
- JSON patterns: `{ $.eventType = "UpdateTrail" && $.code >= 400 }`, with `=`, `!=`, `<`, `<=`,
|
||||
`>`, `>=`, `IS NULL`, `IS TRUE`, `IS FALSE`, `NOT EXISTS`, `&&`, `||`, parentheses, array indexes,
|
||||
`[*]` and `.*` wildcards, and `$.['a.b']` for a property with a dot in its name. AWS's wildcard
|
||||
quotas apply: one per property selector and three per pattern.
|
||||
- Space-delimited patterns: `[ip, ..., status_code = 4*, bytes]`, with named fields, `...` for any
|
||||
number of fields, conditions on any field, `w1`/`w2` indicators, and `%regex%` values. Text
|
||||
between double quotes or square brackets is one field.
|
||||
|
||||
`PutMetricFilter` applies the rules AWS applies: the log group must exist, the pattern must parse,
|
||||
at most two regular expressions per pattern, exactly one transformation whose `metricValue` is a
|
||||
number or a single-valued field reference, at most three dimensions in total, and 100 metric
|
||||
filters per log group. Scalar JSON metric-value and dimension references need not be mentioned
|
||||
in the filter pattern. Wildcard metric-value references such as `$.values[*]` are rejected;
|
||||
wildcards remain available in the filter pattern itself. Ordinary dimensions are
|
||||
available only for JSON or space-delimited patterns and cannot be combined with a `defaultValue`;
|
||||
system-only dimensions can. The shared group regex quota counts up to five **regex-bearing filters**
|
||||
across metric and subscription filters, independently of the two expressions allowed per pattern.
|
||||
This shared pool is documentation-backed, not a new live mixed-writer claim. `TestMetricFilter`
|
||||
reports one-based event numbers. JSON matches have empty public `extractedValues`; space-delimited
|
||||
matches expose named fields as `$name` and positional fields as `$1`, `$2` and so on.
|
||||
|
||||
`fieldSelectionCriteria` selects which batches a filter processes from the system fields
|
||||
`@aws.account` and `@aws.region`, with `=`, `!=`, `IN`, `NOT IN` and the `AND` and `OR` the API
|
||||
documents, as in `@aws.account IN ["111111111111"]`. Both fields describe the ingested batch rather
|
||||
than the individual event, so one evaluation decides the batch. A criterion that does not parse is
|
||||
rejected when the filter is stored.
|
||||
|
||||
`emitSystemFieldDimensions` adds `@aws.account` and `@aws.region` on matching contributions as
|
||||
described above. They count toward the same combined limit of three dimensions.
|
||||
|
||||
`applyOnTransformedLogs` is stored and returned unchanged. Request acceptance was verified on AWS,
|
||||
including `true` on the tested group without a group-level transformer. Floci does not execute
|
||||
group-level or account-level log transformations, including stored `TRANSFORMER_POLICY`
|
||||
configuration. Accepting the flag therefore does not establish transformed-log processing or
|
||||
full compatibility with an AWS group that has an active transformer.
|
||||
|
||||
See [Metric-filter contract verification](cloudwatch-metric-filters-verification.md) for the
|
||||
scenarios tagged **Verified on Live AWS**, the recorded fixtures and their evidence boundaries.
|
||||
|
||||
### Logs Insights {#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` combining conditions with `and` / `or` — for example `filter level = 'ERROR' and status = 200` | Only the leftmost operator is parsed; the rest of the line becomes the compared value, so nothing 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 compound-filter 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.
|
||||
|
||||
### Reading events past the limit
|
||||
|
||||
`FilterLogEvents` and `GetLogEvents` both page, and they signal the end of the results differently
|
||||
because the AWS APIs do.
|
||||
|
||||
`FilterLogEvents` pages forward only. Its `nextToken` is an `f/<index>` offset into the matched set,
|
||||
so the offset counts matches, not stored events: a request narrowed by `--filter-pattern`,
|
||||
`--start-time` or `--log-stream-names` pages through only what it matched. A missing token starts
|
||||
from the oldest match, and **a response with no `nextToken` means pagination is finished**, so the
|
||||
final page omits it. An unrecognized, non-numeric or negative token returns
|
||||
`InvalidParameterException` (400). `startFromHead` is not supported, so results always run oldest
|
||||
first.
|
||||
|
||||
`GetLogEvents` pages in both directions with `f/<index>` and `b/<index>`, and always returns
|
||||
`nextForwardToken` and `nextBackwardToken`. It signals the end by returning the same token it was
|
||||
given rather than by omitting it, which is what its SDK paginators expect.
|
||||
|
||||
### 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
|
||||
|
||||
```bash
|
||||
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 {#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 |
|
||||
| `PutMetricStream` | Create or update a metric stream definition |
|
||||
| `GetMetricStream` | Read a metric stream definition |
|
||||
| `ListMetricStreams` | List metric streams |
|
||||
| `DeleteMetricStream` | Delete a metric stream |
|
||||
| `StartMetricStreams` | Move metric streams to `running` |
|
||||
| `StopMetricStreams` | Move metric streams to `stopped` |
|
||||
| `PutDashboard` | Create a dashboard, or replace its body when the name is taken (tags apply on create only) |
|
||||
| `GetDashboard` | Read a dashboard body and ARN |
|
||||
| `ListDashboards` | List dashboards, optionally by name prefix |
|
||||
| `DeleteDashboards` | Delete dashboards by name |
|
||||
| `TagResource` | Tag an alarm or dashboard by ARN |
|
||||
| `UntagResource` | Remove tags from an alarm or dashboard |
|
||||
| `ListTagsForResource` | List the tags of an alarm or dashboard |
|
||||
|
||||
Metric streams are stored as definitions with their `running` or `stopped` state. Floci never
|
||||
delivers metrics to the Firehose delivery stream a metric stream names.
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
Reference in New Issue
Block a user