aws / aws/aws-cdk

(aws-ecs): L2 support for ECS Action Logs on Cluster

Open
#38,528 4 comments 2 reactions 0 assignees View on GitHub
@aws-cdk/aws-ecs effort/medium feature-request p2
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.