(aws-ecs): L2 support for ECS Action Logs on Cluster
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
### Describe the feature
[ECS Action Logs](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/action-logs.html) ([launched July 2026](https://aws.amazon.com/about-aws/whats-new/2026/07/amazon-ecs-action-logs/)) provides visibility into service deployment and daemon lifecycle operations. It captures previously invisible intermediate steps like infrastructure provisioning, resource registration, rollbacks, and failure details.
Currently, enabling Action Logs in CDK requires manually wiring up **three L1 constructs** from `aws-logs` (`CfnDeliverySource`, `CfnDeliveryDestination`, `CfnDelivery`) with explicit dependencies and an undocumented `LogType` value. There is no L2 support on the `ecs.Cluster` construct.
### Use Case
Every team using ECS with CDK that wants deployment visibility currently needs to drop down to L1 constructs and manually wire 3 resources. This is:
- **Error-prone** – The correct `LogType` is `ACTION_LOGS`, not `EcsActionLogs` as the ECS CLI documentation suggests (we verified this produces a `CREATE_FAILED`)
- **Undiscoverable** – Nothing in the `ecs.Cluster` API hints at this capability
- **Boilerplate-heavy** – 3 resources + dependency management for what is conceptually "enable a feature on my cluster"
Here is our current L1 workaround:
```typescript
const deliverySource = new logs.CfnDeliverySource(this, 'Source', {
name: 'my-source',
resourceArn: cluster.clusterArn,
logType: 'ACTION_LOGS',
});
const deliveryDestination = new logs.CfnDeliveryDestination(this, 'Dest', {
name: 'my-dest',
destinationResourceArn: logGroup.logGroupArn,
});
const delivery = new logs.CfnDelivery(this, 'Delivery', {
deliverySourceName: deliverySource.name,
deliveryDestinationArn: deliveryDestination.attrArn,
});
delivery.addDependency(deliverySource);
delivery.addDependency(deliveryDestination);
```
### Proposed Solution
Following established CDK patterns:
- **`FlowLogDestination`** (abstract class + static factories) for mutually exclusive destination choice
- **`addDefaultCloudMapNamespace()`** pattern for dual prop + method support
- **`Names.uniqueResourceName()`** with `maxLength: 60` for CFN name constraints
### User-facing API
```typescript
// Simple: CloudWatch Logs with auto-created log group
const cluster = new ecs.Cluster(this, 'Cluster', {
vpc,
actionLogs: {
destination: ecs.ActionLogsDestination.toCloudWatchLogs(),
},
});
// With existing log group
const cluster = new ecs.Cluster(this, 'Cluster', {
vpc,
actionLogs: {
destination: ecs.ActionLogsDestination.toCloudWatchLogs(myLogGroup),
},
});
// S3 destination
const cluster = new ecs.Cluster(this, 'Cluster', {
vpc,
actionLogs: {
destination: ecs.ActionLogsDestination.toS3(myBucket),
},
});
// Enable on existing cluster (method)
cluster.enableActionLogs({
destination: ecs.ActionLogsDestination.toCloudWatchLogs(),
});
```
### Interface Design
```typescript
export interface ActionLogsConfiguration {
readonly destination: ActionLogsDestination;
}
export abstract class ActionLogsDestination {
public static toCloudWatchLogs(logGroup?: logs.ILogGroup): ActionLogsDestination;
public static toS3(bucket: s3.IBucket, keyPrefix?: string): ActionLogsDestination;
public static toFirehose(deliveryStream: firehose.IDeliveryStream): ActionLogsDestination;
public abstract bind(scope: Construct, cluster: ICluster): ActionLogsDestinationConfig;
}
```
### Design rationale (CDK patterns followed)
| Decision | Precedent in CDK |
|----------|-----------------|
| Abstract class + static factories for destination | `ec2.FlowLogDestination` (same constraint: choose one of CW/S3/Firehose) |
| Constructor prop + method (`enableActionLogs()`) | `addDefaultCloudMapNamespace()` (creates child constructs → both prop and method) |
| `Names.uniqueResourceName({ maxLength: 60 })` for naming | `AsgCapacityProvider` (capacity provider name constraint) |
### Implementation plan
| File | Change |
|------|--------|
| `packages/aws-cdk-lib/aws-ecs/lib/cluster.ts` | Add `ActionLogsConfiguration`, `ActionLogsDestination`, `enableActionLogs()`, constructor wiring |
| `packages/aws-cdk-lib/aws-ecs/test/cluster.test.ts` | Tests for all destination types, auto-created log group, guard against double-enable |
Note: `cluster.ts` currently uses `import type * as logs` – needs value import for `CfnDeliverySource` etc.
### Other Information
### Verified via CloudFormation deployment + end-to-end test
We deployed and tested this in a real AWS account. Here are the key findings:
**Deployed CFN template:**
```yaml
Resources:
EcsCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: action-logs-demo
ActionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /aws/vendedlogs/ecs/action-logs/action-logs-demo
RetentionInDays: 7
DeletionPolicy: Delete
ActionLogsDeliverySource:
Type: AWS::Logs::DeliverySource
DependsOn: EcsCluster
Properties:
Name: ecs-action-logs-demo
ResourceArn: !GetAtt EcsCluster.Arn
LogType: ACTION_LOGS
ActionLogsDeliveryDestination:
Type: AWS::Logs::DeliveryDestination
DependsOn: ActionLogGroup
Properties:
Name: ecs-action-logs-dest-demo
DestinationResourceArn: !GetAtt ActionLogGroup.Arn
ActionLogsDelivery:
Type: AWS::Logs::Delivery
DependsOn:
- ActionLogsDeliverySource
- ActionLogsDeliveryDestination
Properties:
DeliverySourceName: ecs-action-logs-demo
DeliveryDestinationArn: !GetAtt ActionLogsDeliveryDestination.Arn
```
**Verification commands and results:**
```bash
# Stack deployed successfully
$ aws cloudformation deploy --template-file ecs-action-logs-e2e.yaml --stack-name ecs-action-logs-e2e --capabilities CAPABILITY_IAM
# => CREATE_COMPLETE
# Fargate service (nginx) went stable
$ aws ecs wait services-stable --cluster action-logs-demo --services action-logs-demo-svc
# Log streams appeared automatically
$ aws logs describe-log-streams \
--log-group-name /aws/vendedlogs/ecs/action-logs/action-logs-demo \
--query "logStreams[*].logStreamName" --output table
# +--------------------------------------------------+
# | service/action-logs-demo/action-logs-demo-svc |
# +--------------------------------------------------+
# Action Log events received
$ aws logs get-log-events \
--log-group-name /aws/vendedlogs/ecs/action-logs/action-logs-demo \
--log-stream-name "service/action-logs-demo/action-logs-demo-svc" \
--query "events[*].message" --output text
```
**Received events:**
```json
{
"resourceArn": "arn:aws:ecs:us-east-1:XXXXXXXXXXXX:cluster/action-logs-demo",
"actionSourceId": "service/action-logs-demo/action-logs-demo-svc",
"logLevel": "INFO",
"eventTimestamp": 1786362681510,
"detail": {
"statusReason": "Service deployment in progress.",
"status": "IN_PROGRESS",
"eventName": "SERVICE_DEPLOYMENT_IN_PROGRESS"
}
}
```
```json
{
"resourceArn": "arn:aws:ecs:us-east-1:XXXXXXXXXXXX:cluster/action-logs-demo",
"actionSourceId": "service/action-logs-demo/action-logs-demo-svc",
"logLevel": "INFO",
"eventTimestamp": 1786362702550,
"detail": {
"statusReason": "Service revision marked stable. Locked to container image(s): public.ecr.aws/nginx/nginx:latest@sha256:...",
"status": "SUCCEEDED",
"eventName": "SERVICE_REVISION_STABLE"
}
}
```
### Key findings from testing
| Finding | Details |
|---------|---------|
| ✅ Correct `LogType` for CFN | `ACTION_LOGS` (NOT `EcsActionLogs` – that causes `CREATE_FAILED`) |
| ✅ No Resource Policy needed | Vended Logs mechanism handles it automatically |
| ✅ No special IAM permissions needed | No `ecs:AllowVendedLogDeliveryForResource` required in deploying role |
| ✅ Log stream naming | `service/{cluster-name}/{service-name}` |
| ✅ End-to-end delivery confirmed | Events arrive within seconds of deployment |
### Acknowledgements
- [x] I may be able to implement this feature request
- [ ] This feature might incur a breaking change
### AWS CDK Library version (aws-cdk-lib)
v2.263.0
### AWS CDK CLI version
v2.1135.1
### Environment details (OS name and version, etc.)
Linux: CloudShell via Console + Fedora via local Notebook
Contributor guide
Research direction
Start in packages/aws-cdk-lib/aws-ecs/lib/cluster.ts and review the existing cluster wiring, addDefaultCloudMapNamespace(), and related destination patterns such as ec2.FlowLogDestination. Then inspect packages/aws-cdk-lib/aws-ecs/test/cluster.test.ts and add coverage for CloudWatch Logs, S3, Firehose, auto-created log groups, and double-enable protection; done means the proposed APIs synthesize the required delivery resources and the tests pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- cloud, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100