/// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="scope"></param>
        /// <param name="id"></param>
        /// <param name="props"></param>
        public LogAggregator(Construct scope, string id, LogAggregatorProps props)
            : base(scope, id)
        {
            // The Kinesis stream that will receive all lambda logs.
            var kinesisStream = new Amazon.CDK.AWS.Kinesis.Stream(this, "Stream", props.KinesisStreamProps);

            // Forward all records from kinesis to the log shipper.
            props.LogShipper.AddEventSource(new KinesisEventSource(kinesisStream,
                                                                   new KinesisEventSourceProps {
                BatchSize         = props.KinesisBatchSize,
                MaxBatchingWindow = props.KinesisMaxBatchingWindow,
                StartingPosition  = StartingPosition.TRIM_HORIZON,
            }));

            // Create a role (containing a policy) that can be assumed by CloudWatch logs to put records in Kinesis.
            var statement = new PolicyStatement();

            statement.AddActions(new[] { "kinesis:PutRecords", "kinesis:PutRecord" });
            statement.AddResources(new[] { kinesisStream.StreamArn });

            var cloudWatchLogsToKinesisRole = new Role(this, "CloudWatchLogsToKinesis", new RoleProps
            {
                AssumedBy = new ServicePrincipal("logs.amazonaws.com")
            });

            cloudWatchLogsToKinesisRole.AttachInlinePolicy(new Policy(this, "CanPutRecordsInKinesis", new PolicyProps
            {
                Statements = new[] { statement }
            }));

            // This CloudWatch rule will trigger whenever a new LogGroup is created. This assumes
            // that CloudTrail is enabled.
            var createLogGroupEventRule = new Rule(this, "CreateLogGroupEvent", new RuleProps {
                Description  = "Fires whenever CloudTrail detects that a log group is created",
                EventPattern = new EventPattern {
                    Source     = new[] { "aws.logs" },
                    DetailType = new[] { "AWS API Call via CloudTrail" },
                    Detail     = new Dictionary <string, object>
                    {
                        { "eventSource", new string[] { "logs.amazonaws.com" } },
                        { "eventName", new string[] { "CreateLogGroup" } },
                    }
                }
            });

            if (props.CloudWatchLogRetentionInDays.HasValue)
            {
                var setLogGroupExpirationLambda = new Function(this, "SetLogGroupExpiration", new FunctionProps
                {
                    Runtime     = Runtime.NODEJS_10_X,
                    Handler     = "index.handler",
                    Description = $"Sets the log retention policy to {props.CloudWatchLogRetentionInDays} days when a log group is created.",
                    MemorySize  = 128,
                    Environment = new Dictionary <string, string>
                    {
                        { "retention_days", props.CloudWatchLogRetentionInDays.Value.ToString() },
                        { "prefix", props.LogGroupsPrefix },
                    },
                    Code = Code.FromInline(EmbeddedResourceReader.Read("Resources.SetExpiry.js"))
                });

                // This lambda must be able to change the logs retention policy.
                setLogGroupExpirationLambda.AddToRolePolicy(new PolicyStatement(new PolicyStatementProps
                {
                    Effect    = Effect.ALLOW,
                    Actions   = new[] { "logs:PutRetentionPolicy" },
                    Resources = new[] { "*" },
                }));

                // This lambda should be invoked automatically when a log group is created.
                createLogGroupEventRule.AddTarget(new LambdaFunction(setLogGroupExpirationLambda));
            }

            var excludedLogGroups = props.LogShipper == null ? "" : $"/aws/lambda/{props.LogShipper.FunctionName}";

            // This function will be invoked whenever a CloudWatch log group is created. It will
            // subscribe the log group to our Kinesis Data Stream so that all logs end up in the
            // DataStream.
            var subscribeLogGroupsToKinesisLambda = new Function(this, "SubscribeLogGroupsToKinesis", new FunctionProps
            {
                Runtime     = Runtime.NODEJS_10_X,
                Handler     = "index.handler",
                Description = "Subscribe logs to the Kinesis stream",
                MemorySize  = 128,
                Environment = new Dictionary <string, string>
                {
                    { "arn", kinesisStream.StreamArn },
                    { "role_arn", cloudWatchLogsToKinesisRole.RoleArn },
                    { "prefix", props.LogGroupsPrefix },
                    { "excluded_log_groups", excludedLogGroups },
                    { "filter_pattern", props.CloudWatchLogsFilterPattern.ToString() },
                },
                Code = Code.FromInline(EmbeddedResourceReader.Read("Resources.SubscribeLogGroupsToKinesis.js"))
            });

            subscribeLogGroupsToKinesisLambda.AddToRolePolicy(new PolicyStatement(new PolicyStatementProps
            {
                Effect    = Effect.ALLOW,
                Actions   = new [] { "logs:PutSubscriptionFilter" },
                Resources = new[] { "*" },
            }));

            subscribeLogGroupsToKinesisLambda.AddToRolePolicy(new PolicyStatement(new PolicyStatementProps
            {
                Effect    = Effect.ALLOW,
                Actions   = new[] { "iam:PassRole" },
                Resources = new[] { "*" },
            }));

            createLogGroupEventRule.AddTarget(new LambdaFunction(subscribeLogGroupsToKinesisLambda));
        }