Skip to content

Commit

Permalink
feat(appmesh): access log format support for app mesh (#25229)
Browse files Browse the repository at this point in the history
**Implement access logging feature for AppMesh CDK.**
* Access logging feature allows customer to choose between Text and Json format for the virtual nodes and virtual gateways. Customers can also specify patterns supported by Envoy. (https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage) Example CFN template: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-loggingformat.html

### All Submissions:

* [Y] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [N] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [Y] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [Y] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
EdwardXF committed May 11, 2023
1 parent 8854739 commit c4b00be
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"validateOnSynth": false,
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/e22d0f948e4b9a0aac11f68f048f52c09eeb7d8fef79972eb7e6f957111955bb.json",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/66ab683818060e1118fc673956ded609e269963fadf52391a2f080831d022402.json",
"requiresBootstrapStackVersion": 6,
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
"additionalDependencies": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"version": "31.0.0",
"files": {
"e22d0f948e4b9a0aac11f68f048f52c09eeb7d8fef79972eb7e6f957111955bb": {
"66ab683818060e1118fc673956ded609e269963fadf52391a2f080831d022402": {
"source": {
"path": "mesh-stack.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "e22d0f948e4b9a0aac11f68f048f52c09eeb7d8fef79972eb7e6f957111955bb.json",
"objectKey": "66ab683818060e1118fc673956ded609e269963fadf52391a2f080831d022402.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,9 @@
"Logging": {
"AccessLog": {
"File": {
"Format": {
"Text": "test_pattern"
},
"Path": "/dev/stdout"
}
}
Expand Down Expand Up @@ -1178,6 +1181,18 @@
"Logging": {
"AccessLog": {
"File": {
"Format": {
"Json": [
{
"Key": "testKey1",
"Value": "testValue1"
},
{
"Key": "testKey2",
"Value": "testValue2"
}
]
},
"Path": "/dev/stdout"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1604,7 +1604,10 @@
"logging": {
"accessLog": {
"file": {
"path": "/dev/stdout"
"path": "/dev/stdout",
"format": {
"text": "test_pattern"
}
}
}
}
Expand Down Expand Up @@ -1721,7 +1724,19 @@
"logging": {
"accessLog": {
"file": {
"path": "/dev/stdout"
"path": "/dev/stdout",
"format": {
"json": [
{
"key": "testKey1",
"value": "testValue1"
},
{
"key": "testKey2",
"value": "testValue2"
}
]
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const node3 = mesh.addVirtualNode('node3', {
},
},
},
accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'),
accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout', appmesh.LoggingFormat.fromText('test_pattern')),
});

const node4 = mesh.addVirtualNode('node4', {
Expand Down Expand Up @@ -145,7 +145,9 @@ const node4 = mesh.addVirtualNode('node4', {
},
},
},
accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'),
accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout',
appmesh.LoggingFormat.fromJson(
{ testKey1: 'testValue1', testKey2: 'testValue2' })),
});

node4.addBackend(appmesh.Backend.virtualService(
Expand Down
41 changes: 41 additions & 0 deletions packages/aws-cdk-lib/aws-appmesh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,47 @@ const node = new appmesh.VirtualNode(this, 'node', {
cdk.Tags.of(node).add('Environment', 'Dev');
```

Create a `VirtualNode` with the customized access logging format.

```ts
declare const mesh: appmesh.Mesh;
declare const service: cloudmap.Service;
const node = new appmesh.VirtualNode(this, 'node', {
mesh,
serviceDiscovery: appmesh.ServiceDiscovery.cloudMap(service),
listeners: [appmesh.VirtualNodeListener.http({
port: 8080,
healthCheck: appmesh.HealthCheck.http({
healthyThreshold: 3,
interval: cdk.Duration.seconds(5),
path: '/ping',
timeout: cdk.Duration.seconds(2),
unhealthyThreshold: 2,
}),
timeout: {
idle: cdk.Duration.seconds(5),
},
})],
backendDefaults: {
tlsClientPolicy: {
validation: {
trust: appmesh.TlsValidationTrust.file('/keys/local_cert_chain.pem'),
},
},
},
accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout',
appmesh.LoggingFormat.fromJson(
{testKey1: 'testValue1', testKey2: 'testValue2'})),
});
```

By using a key-value pair indexed signature, you can specify json key pairs to customize the log entry pattern. You can also use text format as below. You can only specify one of these 2 formats.

```ts
accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout', appmesh.LoggingFormat.fromText('test_pattern')),
```

For what values and operators you can use for these two formats, please visit the latest envoy documentation. (https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage)
Create a `VirtualNode` with the constructor and add backend virtual service.

```ts
Expand Down
94 changes: 91 additions & 3 deletions packages/aws-cdk-lib/aws-appmesh/lib/shared-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ export abstract class AccessLog {
*
* @default - no file based access logging
*/
public static fromFilePath(filePath: string): AccessLog {
return new FileAccessLog(filePath);
public static fromFilePath(filePath: string, loggingFormat?: LoggingFormat): AccessLog {
return new FileAccessLog(filePath, loggingFormat);
}

/**
Expand All @@ -143,28 +143,116 @@ class FileAccessLog extends AccessLog {
* @default - no file based access logging
*/
public readonly filePath: string;
private readonly virtualNodeLoggingFormat?: CfnVirtualNode.LoggingFormatProperty;
private readonly virtualGatewayLoggingFormat?: CfnVirtualGateway.LoggingFormatProperty;

constructor(filePath: string) {
constructor(filePath: string, loggingFormat?: LoggingFormat) {
super();
this.filePath = filePath;
// For now we have the same setting for Virtual Gateway and Virtual Nodes
this.virtualGatewayLoggingFormat = loggingFormat?.bind().formatConfig;
this.virtualNodeLoggingFormat = loggingFormat?.bind().formatConfig;
}

public bind(_scope: Construct): AccessLogConfig {
return {
virtualNodeAccessLog: {
file: {
path: this.filePath,
format: this.virtualNodeLoggingFormat,
},
},
virtualGatewayAccessLog: {
file: {
path: this.filePath,
format: this.virtualGatewayLoggingFormat,
},
},
};
}
}

/**
* All Properties for Envoy Access Logging Format for mesh endpoints
*/
export interface LoggingFormatConfig {
/**
* CFN configuration for Access Logging Format
*
* @default - no access logging format
*/
readonly formatConfig?: CfnVirtualNode.LoggingFormatProperty;
}

/**
* Configuration for Envoy Access Logging Format for mesh endpoints
*/
export abstract class LoggingFormat {
/**
* Generate logging format from text pattern
*/
public static fromText(text: string): LoggingFormat {
return new TextLoggingFormat(text);
}
/**
* Generate logging format from json key pairs
*/
public static fromJson(jsonLoggingFormat :{[key:string]: string}): LoggingFormat {
if (Object.keys(jsonLoggingFormat).length == 0) {
throw new Error('Json key pairs cannot be empty.');
}

return new JsonLoggingFormat(jsonLoggingFormat);
};

/**
* Called when the Access Log Format is initialized. Can be used to enforce
* mutual exclusivity with future properties
*/
public abstract bind(): LoggingFormatConfig;
}

/**
* Configuration for Json logging format
*/
class JsonLoggingFormat extends LoggingFormat {
/**
* Json pattern for the output logs
*/
private readonly json: Array<CfnVirtualNode.JsonFormatRefProperty>;
constructor(json: {[key:string]: string}) {
super();
this.json = Object.entries(json).map(([key, value]) => ({ key, value }));
}

bind(): LoggingFormatConfig {
return {
formatConfig: {
json: this.json,
},
};
}
}

class TextLoggingFormat extends LoggingFormat {
/**
* Json pattern for the output logs
*/
private readonly text: string;
constructor(text: string) {
super();
this.text = text;
}

public bind(): LoggingFormatConfig {
return {
formatConfig: {
text: this.text,
},
};
}
}

/**
* Represents the properties needed to define backend defaults
*/
Expand Down
Loading

0 comments on commit c4b00be

Please sign in to comment.