Skip to content

Commit 06666f4

Browse files
authored
feat(ecs-service-extensions): Enable default logging to CloudWatch for extensions (under feature flag) (#17817)
---- This PR adds a default `awslogs` log driver to the application container. We first check if any other observability extensions (e.g. `FirelensExtension`) have already been added to the service. It will add the log driver only if no such loggers have been enabled. *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent bb67345 commit 06666f4

14 files changed

+893
-130
lines changed

packages/@aws-cdk-containers/ecs-service-extensions/README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,43 @@ nameDescription.add(new Container({
109109
Every `ServiceDescription` requires at minimum that you add a `Container` extension
110110
which defines the main application (essential) container to run for the service.
111111

112-
After that, you can optionally enable additional features for the service using the `ServiceDescription.add()` method:
112+
### Logging using `awslogs` log driver
113+
114+
If no observability extensions have been configured for a service, the ECS Service Extensions configures an `awslogs` log driver for the application container of the service to send the container logs to CloudWatch Logs.
115+
116+
You can either provide a log group to the `Container` extension or one will be created for you by the CDK.
117+
118+
Following is an example of an application with an `awslogs` log driver configured for the application container:
119+
120+
```ts
121+
const environment = new Environment(stack, 'production');
122+
123+
const nameDescription = new ServiceDescription();
124+
nameDescription.add(new Container({
125+
cpu: 1024,
126+
memoryMiB: 2048,
127+
trafficPort: 80,
128+
image: ContainerImage.fromRegistry('nathanpeck/name'),
129+
environment: {
130+
PORT: '80',
131+
},
132+
logGroup: new awslogs.LogGroup(stack, 'MyLogGroup'),
133+
}));
134+
```
135+
136+
If a log group is not provided, no observability extensions have been created, and the `ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER` feature flag is enabled, then logging will be configured by default and a log group will be created for you.
137+
138+
The `ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER` feature flag is enabled by default in any CDK apps that are created with CDK v1.140.0 or v2.8.0 and later.
139+
140+
To enable default logging for previous versions, ensure that the `ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER` flag within the application stack context is set to true, like so:
141+
142+
```ts
143+
stack.node.setContext(cxapi.ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER, true);
144+
```
145+
146+
Alternatively, you can also set the feature flag in the `cdk.json` file. For more information, refer the [docs](https://docs.aws.amazon.com/cdk/v2/guide/featureflags.html).
147+
148+
After adding the `Container` extension, you can optionally enable additional features for the service using the `ServiceDescription.add()` method:
113149

114150
```ts
115151
nameDescription.add(new AppMeshExtension({ mesh }));

packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/container.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import * as ecs from '@aws-cdk/aws-ecs';
2+
import * as awslogs from '@aws-cdk/aws-logs';
3+
import * as cdk from '@aws-cdk/core';
4+
import * as cxapi from '@aws-cdk/cx-api';
25
import { Service } from '../service';
36
import { ServiceExtension } from './extension-interfaces';
47

@@ -38,6 +41,13 @@ export interface ContainerExtensionProps {
3841
readonly environment?: {
3942
[key: string]: string,
4043
}
44+
45+
/**
46+
* The log group into which application container logs should be routed.
47+
*
48+
* @default - A log group is automatically created for you if the `ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER` feature flag is set.
49+
*/
50+
readonly logGroup?: awslogs.ILogGroup;
4151
}
4252

4353
/**
@@ -51,6 +61,11 @@ export class Container extends ServiceExtension {
5161
*/
5262
public readonly trafficPort: number;
5363

64+
/**
65+
* The log group into which application container logs should be routed.
66+
*/
67+
public logGroup?: awslogs.ILogGroup;
68+
5469
/**
5570
* The settings for the container.
5671
*/
@@ -60,11 +75,12 @@ export class Container extends ServiceExtension {
6075
super('service-container');
6176
this.props = props;
6277
this.trafficPort = props.trafficPort;
78+
this.logGroup = props.logGroup;
6379
}
6480

65-
// @ts-ignore - Ignore unused params that are required for abstract class extend
6681
public prehook(service: Service, scope: Construct) {
6782
this.parentService = service;
83+
this.scope = scope;
6884
}
6985

7086
// This hook sets the overall task resource requirements to the
@@ -93,6 +109,31 @@ export class Container extends ServiceExtension {
93109
containerProps = hookProvider.mutateContainerDefinition(containerProps);
94110
});
95111

112+
// If no observability extensions have been added to the service description then we can configure the `awslogs` log driver
113+
if (!containerProps.logging) {
114+
// Create a log group for the service if one is not provided by the user (only if feature flag is set)
115+
if (!this.logGroup && this.parentService.node.tryGetContext(cxapi.ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER)) {
116+
this.logGroup = new awslogs.LogGroup(this.scope, `${this.parentService.id}-logs`, {
117+
logGroupName: `${this.parentService.id}-logs`,
118+
removalPolicy: cdk.RemovalPolicy.DESTROY,
119+
retention: awslogs.RetentionDays.ONE_MONTH,
120+
});
121+
}
122+
123+
if (this.logGroup) {
124+
containerProps = {
125+
...containerProps,
126+
logging: new ecs.AwsLogDriver({
127+
streamPrefix: this.parentService.id,
128+
logGroup: this.logGroup,
129+
}),
130+
};
131+
}
132+
} else {
133+
if (this.logGroup) {
134+
throw Error(`Log configuration already specified. You cannot provide a log group for the application container of service '${this.parentService.id}' while also adding log configuration separately using service extensions.`);
135+
}
136+
}
96137
this.container = taskDefinition.addContainer('app', containerProps);
97138

98139
// Create a port mapping for the container

packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export interface ServiceProps {
5454

5555
/**
5656
* The options for configuring the auto scaling target.
57+
*
58+
* @default none
5759
*/
5860
readonly autoScaleTaskCount?: AutoScalingOptions;
5961
}
@@ -196,7 +198,6 @@ export class Service extends Construct {
196198
// Ensure that the task definition supports both EC2 and Fargate
197199
compatibility: ecs.Compatibility.EC2_AND_FARGATE,
198200
} as ecs.TaskDefinitionProps;
199-
200201
for (const extensions in this.serviceDescription.extensions) {
201202
if (this.serviceDescription.extensions[extensions]) {
202203
taskDefProps = this.serviceDescription.extensions[extensions].modifyTaskDefinitionProps(taskDefProps);

packages/@aws-cdk-containers/ecs-service-extensions/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"@aws-cdk/aws-sqs": "0.0.0",
7171
"@aws-cdk/core": "0.0.0",
7272
"@aws-cdk/custom-resources": "0.0.0",
73+
"@aws-cdk/cx-api": "0.0.0",
7374
"@aws-cdk/region-info": "0.0.0",
7475
"constructs": "^3.3.69"
7576
},
@@ -98,6 +99,7 @@
9899
"@aws-cdk/aws-sqs": "0.0.0",
99100
"@aws-cdk/core": "0.0.0",
100101
"@aws-cdk/custom-resources": "0.0.0",
102+
"@aws-cdk/cx-api": "0.0.0",
101103
"@aws-cdk/region-info": "0.0.0",
102104
"constructs": "^3.3.69"
103105
},

packages/@aws-cdk-containers/ecs-service-extensions/test/appmesh.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ describe('appmesh', () => {
3333
});
3434

3535
// THEN
36-
3736
// Ensure that task has an App Mesh sidecar
3837
expect(stack).toHaveResource('AWS::ECS::TaskDefinition', {
3938
ContainerDefinitions: [

packages/@aws-cdk-containers/ecs-service-extensions/test/cloudwatch-agent.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ describe('cloudwatch agent', () => {
2727
});
2828

2929
// THEN
30-
3130
expect(stack).toHaveResource('AWS::ECS::TaskDefinition', {
3231
ContainerDefinitions: [
3332
{

0 commit comments

Comments
 (0)