Skip to content

Commit 6c882e0

Browse files
authored
feat(logs): add support for fieldIndexPolicies in log group L2 Construct (#33416)
### Issue # (if applicable) #33366 Closes #33366 ### Reason for this change Field Indexing for CloudWatch Logs (CWL) was launched in Nov 2024. A lot of CWL customers are asking for indexing support in L2 construct. This feature will enable that property under FieldIndexPolicies as a JSON object in the LogGroup construct. ### Description of changes The change here is just populating the `fieldIndexPolicies` property of the LogGroup CFN with the list of fields provided by the user. The format of this property will be like this: ``` const fieldIndexPolicy = new FieldIndexPolicy({ fields: ['Operation', 'RequestId'], }); new LogGroup(this, 'LogGroupLambda', { dataProtectionPolicy: dataProtectionPolicy, fieldIndexPolicies: [fieldIndexPolicy], }); ``` ### Describe any new or updated permissions being added No new permissions have been added. ### Description of how you validated changes Added unit tests. Will add integ tests after getting a confirmation from the CDK team on the implementation. ### Checklist - [X] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent ba2dfd1 commit 6c882e0

File tree

10 files changed

+167
-5
lines changed

10 files changed

+167
-5
lines changed

packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/aws-cdk-log-group-integ.assets.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/aws-cdk-log-group-integ.template.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@
112112
]
113113
}
114114
},
115+
"FieldIndexPolicies": [
116+
{
117+
"Fields": [
118+
"Operation",
119+
"RequestId"
120+
]
121+
}
122+
],
115123
"RetentionInDays": 731
116124
},
117125
"UpdateReplacePolicy": "Retain",

packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/manifest.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/tree.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Bucket } from 'aws-cdk-lib/aws-s3';
22
import { App, Stack, StackProps } from 'aws-cdk-lib';
33
import { IntegTest } from '@aws-cdk/integ-tests-alpha';
4-
import { LogGroup, DataProtectionPolicy, DataIdentifier, CustomDataIdentifier } from 'aws-cdk-lib/aws-logs';
4+
import { LogGroup, DataProtectionPolicy, DataIdentifier, CustomDataIdentifier, FieldIndexPolicy } from 'aws-cdk-lib/aws-logs';
55

66
class LogGroupIntegStack extends Stack {
77
constructor(scope: App, id: string, props?: StackProps) {
@@ -19,8 +19,13 @@ class LogGroupIntegStack extends Stack {
1919
s3BucketAuditDestination: bucket,
2020
});
2121

22+
const fieldIndexPolicy = new FieldIndexPolicy({
23+
fields: ['Operation', 'RequestId'],
24+
});
25+
2226
new LogGroup(this, 'LogGroupLambda', {
2327
dataProtectionPolicy: dataProtectionPolicy,
28+
fieldIndexPolicies: [fieldIndexPolicy],
2429
});
2530
}
2631
}

packages/aws-cdk-lib/aws-logs/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,29 @@ new logs.LogGroup(this, 'LogGroupLambda', {
441441
});
442442
```
443443

444+
## Field Index Policies
445+
446+
Creates or updates a field index policy for the specified log group. You can use field index policies to create field indexes on fields found in log events in the log group. Creating field indexes lowers the costs for CloudWatch Logs Insights queries that reference those field indexes, because these queries attempt to skip the processing of log events that are known to not match the indexed field. Good fields to index are fields that you often need to query for and fields that have high cardinality of values.
447+
448+
For more information, see [Create field indexes to improve query performance and reduce costs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatchLogs-Field-Indexing.html).
449+
450+
Only log groups in the Standard log class support field index policies.
451+
Currently, this array supports only one field index policy object.
452+
453+
Example:
454+
455+
```ts
456+
457+
const fieldIndexPolicy = new logs.FieldIndexPolicy({
458+
fields: ['Operation', 'RequestId'],
459+
});
460+
461+
new logs.LogGroup(this, 'LogGroup', {
462+
logGroupName: 'cdkIntegLogGroup',
463+
fieldIndexPolicies: [fieldIndexPolicy],
464+
});
465+
```
466+
444467
## Notes
445468

446469
Be aware that Log Group ARNs will always have the string `:*` appended to
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Construct } from 'constructs';
2+
3+
/**
4+
* Creates a field index policy for CloudWatch Logs log groups.
5+
*/
6+
export class FieldIndexPolicy {
7+
private readonly fieldIndexPolicyProps: FieldIndexPolicyProps;
8+
9+
constructor(props: FieldIndexPolicyProps) {
10+
if (props.fields.length > 20) {
11+
throw new Error('A maximum of 20 fields can be indexed per log group');
12+
}
13+
this.fieldIndexPolicyProps = props;
14+
}
15+
16+
/**
17+
* @internal
18+
*/
19+
public _bind(_scope: Construct) {
20+
return { Fields: this.fieldIndexPolicyProps.fields };
21+
}
22+
}
23+
24+
/**
25+
* Properties for creating field index policies
26+
*/
27+
export interface FieldIndexPolicyProps {
28+
/**
29+
* List of fields to index in log events.
30+
*
31+
* @default no fields
32+
*/
33+
readonly fields: string[];
34+
}

packages/aws-cdk-lib/aws-logs/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from './log-retention';
88
export * from './policy';
99
export * from './query-definition';
1010
export * from './data-protection-policy';
11+
export * from './field-index-policy';
1112

1213
// AWS::Logs CloudFormation Resources:
1314
export * from './logs.generated';

packages/aws-cdk-lib/aws-logs/lib/log-group.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Construct } from 'constructs';
22
import { DataProtectionPolicy } from './data-protection-policy';
3+
import { FieldIndexPolicy } from './field-index-policy';
34
import { LogStream } from './log-stream';
45
import { CfnLogGroup } from './logs.generated';
56
import { MetricFilter } from './metric-filter';
@@ -506,6 +507,13 @@ export interface LogGroupProps {
506507
*/
507508
readonly dataProtectionPolicy?: DataProtectionPolicy;
508509

510+
/**
511+
* Field Index Policies for this log group.
512+
*
513+
* @default - no field index policies for this log group.
514+
*/
515+
readonly fieldIndexPolicies?: FieldIndexPolicy[];
516+
509517
/**
510518
* How long, in days, the log contents will be retained.
511519
*
@@ -630,6 +638,12 @@ export class LogGroup extends LogGroupBase {
630638
}
631639

632640
const dataProtectionPolicy = props.dataProtectionPolicy?._bind(this);
641+
const fieldIndexPolicies: any[] = [];
642+
if (props.fieldIndexPolicies) {
643+
props.fieldIndexPolicies.forEach((fieldIndexPolicy) => {
644+
fieldIndexPolicies.push(fieldIndexPolicy._bind(this));
645+
});
646+
}
633647

634648
const resource = new CfnLogGroup(this, 'Resource', {
635649
kmsKeyId: props.encryptionKey?.keyArn,
@@ -643,6 +657,7 @@ export class LogGroup extends LogGroupBase {
643657
Statement: dataProtectionPolicy?.statement,
644658
Configuration: dataProtectionPolicy?.configuration,
645659
} : undefined,
660+
...(props.fieldIndexPolicies && { fieldIndexPolicies: fieldIndexPolicies }),
646661
});
647662

648663
resource.applyRemovalPolicy(props.removalPolicy);

packages/aws-cdk-lib/aws-logs/test/loggroup.test.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as iam from '../../aws-iam';
44
import * as kms from '../../aws-kms';
55
import { Bucket } from '../../aws-s3';
66
import { App, CfnParameter, Fn, RemovalPolicy, Stack } from '../../core';
7-
import { LogGroup, RetentionDays, LogGroupClass, DataProtectionPolicy, DataIdentifier, CustomDataIdentifier, ILogGroup, ILogSubscriptionDestination, FilterPattern } from '../lib';
7+
import { LogGroup, RetentionDays, LogGroupClass, DataProtectionPolicy, DataIdentifier, CustomDataIdentifier, ILogGroup, ILogSubscriptionDestination, FilterPattern, FieldIndexPolicy } from '../lib';
88

99
describe('log group', () => {
1010
test('set kms key when provided', () => {
@@ -921,6 +921,66 @@ describe('log group', () => {
921921
});
922922
});
923923

924+
test('set field index policy with four fields indexed', () => {
925+
// GIVEN
926+
const stack = new Stack();
927+
928+
const fieldIndexPolicy = new FieldIndexPolicy({
929+
fields: ['Operation', 'RequestId', 'timestamp', 'message'],
930+
});
931+
932+
// WHEN
933+
const logGroupName = 'test-field-index-log-group';
934+
new LogGroup(stack, 'LogGroup', {
935+
logGroupName: logGroupName,
936+
fieldIndexPolicies: [fieldIndexPolicy],
937+
});
938+
939+
// THEN
940+
Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', {
941+
LogGroupName: logGroupName,
942+
FieldIndexPolicies: [{
943+
Fields: [
944+
'Operation',
945+
'RequestId',
946+
'timestamp',
947+
'message',
948+
],
949+
}],
950+
});
951+
});
952+
953+
test('set more than 20 field indexes in a field index policy', () => {
954+
let message;
955+
try {
956+
// GIVEN
957+
const stack = new Stack();
958+
const fieldIndexPolicy = new FieldIndexPolicy({
959+
fields: createMoreThan20FieldIndexes(),
960+
});
961+
962+
// WHEN
963+
const logGroupName = 'test-field-multiple-field-index-policies';
964+
new LogGroup(stack, 'LogGroup', {
965+
logGroupName: logGroupName,
966+
fieldIndexPolicies: [fieldIndexPolicy],
967+
});
968+
969+
// THEN
970+
Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', {
971+
LogGroupName: logGroupName,
972+
FieldIndexPolicies: [{
973+
Fields: ['abc'],
974+
}],
975+
});
976+
} catch (e) {
977+
message = (e as Error).message;
978+
}
979+
980+
expect(message).toBeDefined();
981+
expect(message).toEqual('A maximum of 20 fields can be indexed per log group');
982+
});
983+
924984
describe('subscription filter', () => {
925985
test('add subscription filter with custom name', () => {
926986
// GIVEN
@@ -953,6 +1013,14 @@ function dataDrivenTests(cases: string[], body: (suffix: string) => void): void
9531013
}
9541014
}
9551015

1016+
function createMoreThan20FieldIndexes(): string[] {
1017+
let arr: string[] = [];
1018+
for (let i = 0; i < 23; i++) {
1019+
arr.push('abc' + i.toString());
1020+
}
1021+
return arr;
1022+
}
1023+
9561024
class FakeDestination implements ILogSubscriptionDestination {
9571025
public bind(_scope: Construct, _sourceLogGroup: ILogGroup) {
9581026
return {

0 commit comments

Comments
 (0)