- Lambda stack exports lambda ARN
- App stack defines log group
- App stack defines subscription to the log group for lambda
So, by design, many app stacks share the same lambda stack to handle log events.
App Stack LogGroup
ServiceLogGroup:
Type: "AWS::Logs::LogGroup"
DeletionPolicy: Retain
Properties:
LogGroupName:
Fn::Sub: ${AWS::StackName}/ecs/Service/MyService
RetentionInDays: 15
App Stack Subscription
# CloudwatchLogsFilterArn is the output of Lambda stack
ServiceLogSubscription:
Type: "AWS::Logs::SubscriptionFilter"
DependsOn:
- ServiceLogGroup
Properties:
DestinationArn:
Fn::ImportValue: CloudwatchLogsFilterArn
FilterPattern: "{ ( $.category = \"my.pkg.ACTIVITY\" || $.category = \"my.pkg.METRICS\") && $.publish IS TRUE }"
LogGroupName:
Fn::Sub: ${AWS::StackName}/ecs/Service/MyService
CloudWatch log event
{
"awslogs": {
"data": <Base64 encoded and GZipped String>
}
}
Decoded message
// see http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html?shortFooter=true#LambdaFunctionExample
{
"owner": "111111111111",
"logGroup": "CloudTrail",
"logStream": "111111111111_CloudTrail_us-east-1",
"subscriptionFilters": [
"Destination"
],
"messageType": "DATA_MESSAGE",
"logEvents": [
{
"id": "31953106606966983378809025079804211143289615424298221568",
"timestamp": 1432826855000,
"message": "<App specific JSON>"
},
...
]
}
Lambda decodes message
public Void handleRequest(Object input, Context context) {
byte[] bytes = Base64.getDecoder().decode(data);
try (GZIPInputStream zi = new GZIPInputStream(new ByteArrayInputStream(bytes))) {
String result = IOUtils.toString(zi);
message = MAPPER.readValue(result, AwsCloudWatchLogMessage.class); // POJO
for (AwsCloudWatchLogEvent e : message.getLogEvents()) {
String appLogEventJson = e.getMessage();
ObjectNode json = MAPPER.readValue(logEvent.getMessage(), ObjectNode.class);
Optional<JsonNode> xyz = Optional.ofNullable(json.get("xyz"));
...
}
} catch (IOException e) {
//...
}