All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graylog.integrations.aws.service.CloudWatchService Maven / Gradle / Ivy

There is a newer version: 6.1.4
Show newest version
/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * .
 */
package org.graylog.integrations.aws.service;

import com.github.rholder.retry.Attempt;
import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.RetryListener;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import org.graylog.integrations.aws.AWSClientBuilderUtil;
import org.graylog.integrations.aws.resources.requests.AWSRequest;
import org.graylog.integrations.aws.resources.requests.CreateLogSubscriptionRequest;
import org.graylog.integrations.aws.resources.responses.CreateLogSubscriptionResponse;
import org.graylog.integrations.aws.resources.responses.LogGroupsResponse;
import org.graylog2.shared.utilities.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient;
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClientBuilder;
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogGroupsRequest;
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogGroupsResponse;
import software.amazon.awssdk.services.cloudwatchlogs.model.Distribution;
import software.amazon.awssdk.services.cloudwatchlogs.model.InvalidParameterException;
import software.amazon.awssdk.services.cloudwatchlogs.model.PutSubscriptionFilterRequest;
import software.amazon.awssdk.services.cloudwatchlogs.paginators.DescribeLogGroupsIterable;

import javax.inject.Inject;
import javax.ws.rs.BadRequestException;
import java.util.ArrayList;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

public class CloudWatchService {

    private static final Logger LOG = LoggerFactory.getLogger(CloudWatchService.class);
    private static final int SUBSCRIPTION_RETRY_DELAY = 1000;
    private static final int SUBSCRIPTION_RETRY_MAX_ATTEMPTS = 120;
    private static final TimeUnit SUBSCRIPTION_RETRY_DELAY_UNIT = TimeUnit.MILLISECONDS;

    private final CloudWatchLogsClientBuilder logsClientBuilder;
    private final AWSClientBuilderUtil awsClientBuilderUtil;

    @Inject
    public CloudWatchService(CloudWatchLogsClientBuilder logsClientBuilder,
                             AWSClientBuilderUtil awsClientBuilderUtil) {
        this.logsClientBuilder = logsClientBuilder;
        this.awsClientBuilderUtil = awsClientBuilderUtil;
    }

    public LogGroupsResponse getLogGroupNames(AWSRequest request) {

        final CloudWatchLogsClient cloudWatchLogsClient = awsClientBuilderUtil.buildClient(logsClientBuilder, request);
        final DescribeLogGroupsRequest describeLogGroupsRequest = DescribeLogGroupsRequest.builder().build();
        final DescribeLogGroupsIterable responses = cloudWatchLogsClient.describeLogGroupsPaginator(describeLogGroupsRequest);

        final ArrayList groupNameList = new ArrayList<>();
        for (DescribeLogGroupsResponse response : responses) {
            for (int c = 0; c < response.logGroups().size(); c++) {
                groupNameList.add(response.logGroups().get(c).logGroupName());
            }
        }
        LOG.debug("Log groups queried: [{}]", groupNameList);

        if (groupNameList.isEmpty()) {
            throw new BadRequestException(String.format(Locale.ROOT, "No CloudWatch log groups were found in the [%s] region.", request.region()));
        }

        return LogGroupsResponse.create(groupNameList, groupNameList.size());
    }

    public CreateLogSubscriptionResponse addSubscriptionFilter(CreateLogSubscriptionRequest request) {
        CloudWatchLogsClient cloudWatch = awsClientBuilderUtil.buildClient(logsClientBuilder, request);
        final PutSubscriptionFilterRequest putSubscriptionFilterRequest =
                PutSubscriptionFilterRequest.builder()
                        .logGroupName(request.logGroupName())
                        .filterName(request.filterName())
                        .filterPattern(request.filterPattern())
                        .destinationArn(request.destinationStreamArn())
                        .roleArn(request.roleArn())
                        .distribution(Distribution.BY_LOG_STREAM)
                        .build();
        try {
            final Retryer retryer = RetryerBuilder.newBuilder()
                    .retryIfExceptionOfType(InvalidParameterException.class)
                    .withWaitStrategy(WaitStrategies.fixedWait(SUBSCRIPTION_RETRY_DELAY, SUBSCRIPTION_RETRY_DELAY_UNIT))
                    .withStopStrategy(StopStrategies.stopAfterAttempt(SUBSCRIPTION_RETRY_MAX_ATTEMPTS))
                    .withRetryListener(new RetryListener() {
                        @Override
                        public  void onRetry(Attempt attempt) {
                            if (attempt.hasException()) {
                                LOG.info("Failed to create log group subscription on retry attempt [{}]. " +
                                        "This is probably normal and indicates that the specified IAM role is not ready yet due to IAM eventual consistency." +
                                        "Retrying now. Exception [{}]", attempt.getAttemptNumber(), attempt.getExceptionCause().getMessage());
                            } else if (attempt.hasException() && attempt.getAttemptNumber() == SUBSCRIPTION_RETRY_MAX_ATTEMPTS) {
                                LOG.error("Failed to put subscription after [{}] attempts. Giving up. Exception [{}]", attempt.getAttemptNumber(), attempt.getExceptionCause());
                            } else if (attempt.getAttemptNumber() > 1) {
                                LOG.info("Retry of CloudWatch log group [{}] subscription was finally successful on attempt [{}].",
                                        request.logGroupName(), attempt.getAttemptNumber());
                            }
                        }
                    })
                    .build();
            try {
                retryer.call(() -> {
                    cloudWatch.putSubscriptionFilter(putSubscriptionFilterRequest);
                    return null;
                });

                String explanation = String.format(Locale.ROOT, "Success. The subscription filter [%s] was added for the CloudWatch log group [%s].",
                        request.filterName(), request.logGroupName());
                return CreateLogSubscriptionResponse.create(explanation);
            } catch (RetryException e) {
                throw new RuntimeException(String.format(Locale.ROOT, "Failed to create the CloudWatch subscription after [%d] attempts. Exception [%s]",
                        e.getNumberOfFailedAttempts(), e.getCause()), e.getCause()); // e.getCause() returns the actual AWS exception to the UI.
            }
        } catch (Exception e) {
            final String specificError = ExceptionUtils.formatMessageCause(e);
            final String responseMessage = String.format(Locale.ROOT, "Attempt to add subscription [%s] to Cloudwatch log group " +
                            "[%s] failed due to the following exception: [%s]",
                    request.filterName(),
                    request.logGroupName(), specificError);
            LOG.error(responseMessage);
            throw new BadRequestException(responseMessage, e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy