/** * Copyright 2014 Jeffrey Damick, All rights reserved. */ package com.damick.dropwizard.metrics.cloudwatch; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.internal.EC2MetadataClient; import com.amazonaws.regions.Region; import com.amazonaws.regions.RegionUtils; import com.amazonaws.services.cloudwatch.AmazonCloudWatchAsync; import com.amazonaws.services.cloudwatch.AmazonCloudWatchAsyncClient; import com.amazonaws.util.EC2MetadataUtils; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.ScheduledReporter; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; import com.google.common.base.Strings; import io.dropwizard.metrics.BaseReporterFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.validation.constraints.NotNull; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; /** * A factory for {@link CloudWatchReporterFactory} instances. * <p/> * <b>Configuration Parameters:</b> * <table> * <tr> * <td>Name</td> * <td>Default</td> * <td>Description</td> * </tr> * <tr> * <td>namespace</td> * <td>(empty)</td> * <td>The namespace for the metric data.</td> * </tr> * <tr> * <td>globalDimensions</td> * <td>(empty)</td> * <td>An array of strings to use as metric dimensions. For example: env=dev</td> * </tr> * <tr> * <td>awsSecretKey</td> * <td>(empty)</td> * <td>The optional AWS Secret key. (If this and awsAccessKeyId not set DefaultAWSCredentialsProviderChain is used)</td> * </tr> * <tr> * <td>awsAccessKeyId</td> * <td>(empty)</td> * <td>The optional AWS Access key. (If this and awsSecretKey not set DefaultAWSCredentialsProviderChain is used)</td> * </tr> * <tr> * <td>awsClientConfiguration</td> * <td>(empty)</td> * <td>The optional <a href="http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/ClientConfiguration.html">AWS Client Configuration</a>.</td> * </tr> * </table> */ @JsonTypeName("cloudwatch") public class CloudWatchReporterFactory extends BaseReporterFactory { private static final Logger LOGGER = LoggerFactory.getLogger(CloudWatchReporterFactory.class); private static final String DEFAULT_REGION = "us-east-1"; @JsonIgnore private AmazonCloudWatchAsync client; @NotNull private String namespace = ""; @JsonIgnore private String awsSecretKey = null; @JsonIgnore private String awsAccessKeyId = null; @JsonIgnore private String awsRegion = DEFAULT_REGION; @JsonIgnore private String machineDimension; @JsonIgnore private Boolean ec2MetadataAvailable = null; @JsonIgnore private List<String> globalDimensions = new ArrayList<>(); @JsonIgnore private CloudWatchClientConfiguration clientConfig = new CloudWatchClientConfiguration(); @JsonProperty public String getAwsSecretKey() { return awsSecretKey; } @JsonProperty public void setAwsSecretKey(String awsSecretKey) { this.awsSecretKey = awsSecretKey; } @JsonProperty public String getAwsAccessKeyId() { return awsAccessKeyId; } @JsonProperty public void setAwsAccessKeyId(String awsAccessKeyId) { this.awsAccessKeyId = awsAccessKeyId; } @JsonProperty public String getAwsRegion() { return awsRegion; } @JsonProperty public void setAwsRegion(String awsRegion) { this.awsRegion = awsRegion; } @JsonProperty public String getNamespace() { return namespace; } @JsonProperty public void setNamespace(String namespace) { this.namespace = namespace; } @JsonProperty public String getMachineDimension() { return machineDimension; } @JsonProperty public void setMachineDimension(String machineDimension) { this.machineDimension = machineDimension; } @JsonProperty public List<String> getGlobalDimensions() { return globalDimensions; } @JsonProperty public void setGlobalDimensions(List<String> globalDimensions) { this.globalDimensions = globalDimensions; } @JsonProperty public void setAwsClientConfiguration(CloudWatchClientConfiguration clientConfig) { this.clientConfig = clientConfig; } @JsonProperty public CloudWatchClientConfiguration getAwsClientConfiguration() { return clientConfig; } // for testing.. @JsonIgnore public void setClient(AmazonCloudWatchAsync client) { this.client = client; } @Override public ScheduledReporter build(MetricRegistry registry) { if (client == null) { if (!Strings.isNullOrEmpty(awsAccessKeyId) && !Strings.isNullOrEmpty(awsSecretKey)) { client = new AmazonCloudWatchAsyncClient( new BasicAWSCredentials(this.awsAccessKeyId, this.awsSecretKey), clientConfig, Executors.newCachedThreadPool()); } else { client = new AmazonCloudWatchAsyncClient(new DefaultAWSCredentialsProviderChain(), clientConfig); } Region region = region(); client.setRegion(region); LOGGER.info("CloudWatch reporting configure to send to region: {}", region); } globalDimensions.add("machine=" + machineId() + "*"); return new CloudWatchMachineDimensionReporter(registry, this.namespace, globalDimensions, getFilter(), client); } protected String machineId() { String machine = machineDimension; if (machine == null && isEC2MetadataAvailable()) { machine = EC2MetadataUtils.getInstanceId(); } if (Strings.isNullOrEmpty(machine)) { machine = "localhost"; } return machine; } protected Region region() { String az = null; if (isEC2MetadataAvailable()) { az = EC2MetadataUtils.getAvailabilityZone(); } String regionName = awsRegion; if (!Strings.isNullOrEmpty(az)) { regionName = az.substring(0, az.length() - 1); // strip the AZ letter } return RegionUtils.getRegion(regionName); } protected boolean isEC2MetadataAvailable() { if (ec2MetadataAvailable == null) { EC2MetadataClient client = new EC2MetadataClient(); try { client.readResource("/"); ec2MetadataAvailable = true; } catch (IOException e) { LOGGER.error("Not able to connect to EC2 Metadata Service"); // if we have any exception, we'll assume we're not in ec2.. ec2MetadataAvailable = false; } } return ec2MetadataAvailable; } }