Skip to content

Commit 225ec81

Browse files
Add Cloudwatch metrics auto-configuration with AWS SDK v2 (#237)
Co-authored-by: Maciej Walkowiak <[email protected]>
1 parent 69ad838 commit 225ec81

File tree

13 files changed

+504
-12
lines changed

13 files changed

+504
-12
lines changed

spring-cloud-aws-autoconfigure/pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
<groupId>org.springframework.boot</groupId>
1919
<artifactId>spring-boot-autoconfigure</artifactId>
2020
</dependency>
21+
<dependency>
22+
<groupId>org.springframework.boot</groupId>
23+
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
24+
<optional>true</optional>
25+
</dependency>
26+
2127
<dependency>
2228
<groupId>io.awspring.cloud</groupId>
2329
<artifactId>spring-cloud-aws-core</artifactId>
@@ -104,6 +110,11 @@
104110
<artifactId>junit-jupiter</artifactId>
105111
<scope>test</scope>
106112
</dependency>
113+
<dependency>
114+
<groupId>io.micrometer</groupId>
115+
<artifactId>micrometer-registry-cloudwatch2</artifactId>
116+
<optional>true</optional>
117+
</dependency>
107118
<!-- AWS SDK v1 is required by testcontainers-localstack -->
108119
<dependency>
109120
<groupId>com.amazonaws</groupId>

spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientBuilderConfigurer.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.springframework.util.StringUtils;
2424
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
2525
import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
26-
import software.amazon.awssdk.awscore.client.builder.AwsSyncClientBuilder;
2726
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
2827
import software.amazon.awssdk.regions.Region;
2928
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
@@ -48,14 +47,14 @@ public class AwsClientBuilderConfigurer {
4847
this.clientOverrideConfiguration = new SpringCloudClientConfiguration().clientOverrideConfiguration();
4948
}
5049

51-
public <T extends AwsClientBuilder<?, ?> & AwsSyncClientBuilder<?, ?>> T configure(T builder) {
50+
public <T extends AwsClientBuilder<?, ?>> T configure(T builder) {
5251
return configure(builder, null, null);
5352
}
5453

55-
public <T extends AwsClientBuilder<?, ?> & AwsSyncClientBuilder<?, ?>> T configure(T builder,
56-
@Nullable AwsClientProperties clientProperties, @Nullable AwsClientCustomizer<T> customizer) {
54+
public <T extends AwsClientBuilder<?, ?>> T configure(T builder, @Nullable AwsClientProperties clientProperties,
55+
@Nullable AwsClientCustomizer<T> customizer) {
5756
Assert.notNull(builder, "builder is required");
58-
Assert.notNull(builder, "clientProperties are required");
57+
Assert.notNull(clientProperties, "clientProperties are required");
5958

6059
builder.credentialsProvider(this.credentialsProvider).region(resolveRegion(clientProperties))
6160
.overrideConfiguration(this.clientOverrideConfiguration);

spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientCustomizer.java

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@
1616
package io.awspring.cloud.autoconfigure.core;
1717

1818
import org.springframework.lang.Nullable;
19+
import software.amazon.awssdk.awscore.client.builder.AwsAsyncClientBuilder;
1920
import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
2021
import software.amazon.awssdk.awscore.client.builder.AwsSyncClientBuilder;
2122
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
2223
import software.amazon.awssdk.http.SdkHttpClient;
24+
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
2325

2426
/**
2527
* @author Matej Nedić
2628
* @since 3.0.0
2729
*/
28-
public interface AwsClientCustomizer<T extends AwsClientBuilder<?, ?> & AwsSyncClientBuilder<?, ?>> {
30+
public interface AwsClientCustomizer<T> {
2931

3032
@Nullable
3133
default ClientOverrideConfiguration overrideConfiguration() {
@@ -42,16 +44,38 @@ default SdkHttpClient.Builder<?> httpClientBuilder() {
4244
return null;
4345
}
4446

45-
static <V extends software.amazon.awssdk.awscore.client.builder.AwsClientBuilder<?, ?> & AwsSyncClientBuilder<?, ?>> void apply(
46-
AwsClientCustomizer<V> configurer, V builder) {
47+
@Nullable
48+
default SdkAsyncHttpClient asyncHttpClient() {
49+
return null;
50+
}
51+
52+
@Nullable
53+
default SdkAsyncHttpClient.Builder<?> asyncHttpClientBuilder() {
54+
return null;
55+
}
56+
57+
static <V extends AwsClientBuilder<?, ?>> void apply(AwsClientCustomizer<V> configurer, V builder) {
4758
if (configurer.overrideConfiguration() != null) {
4859
builder.overrideConfiguration(configurer.overrideConfiguration());
4960
}
50-
if (configurer.httpClient() != null) {
51-
builder.httpClient(configurer.httpClient());
61+
62+
if (builder instanceof AwsSyncClientBuilder) {
63+
AwsSyncClientBuilder syncClientBuilder = (AwsSyncClientBuilder) builder;
64+
if (configurer.httpClient() != null) {
65+
syncClientBuilder.httpClient(configurer.httpClient());
66+
}
67+
if (configurer.httpClientBuilder() != null) {
68+
syncClientBuilder.httpClientBuilder(configurer.httpClientBuilder());
69+
}
5270
}
53-
if (configurer.httpClientBuilder() != null) {
54-
builder.httpClientBuilder(configurer.httpClientBuilder());
71+
else if (builder instanceof AwsAsyncClientBuilder) {
72+
AwsAsyncClientBuilder asyncClientBuilder = (AwsAsyncClientBuilder) builder;
73+
if (configurer.asyncHttpClient() != null) {
74+
asyncClientBuilder.httpClient(configurer.asyncHttpClient());
75+
}
76+
if (configurer.httpClientBuilder() != null) {
77+
asyncClientBuilder.httpClientBuilder(configurer.asyncHttpClientBuilder());
78+
}
5579
}
5680
}
5781
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2013-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.awspring.cloud.autoconfigure.metrics;
17+
18+
import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer;
19+
import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer;
20+
import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration;
21+
import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration;
22+
import io.micrometer.cloudwatch2.CloudWatchConfig;
23+
import io.micrometer.cloudwatch2.CloudWatchMeterRegistry;
24+
import io.micrometer.core.instrument.Clock;
25+
import org.springframework.beans.factory.ObjectProvider;
26+
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
27+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
28+
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
29+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
30+
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
32+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
33+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
34+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
35+
import org.springframework.context.annotation.Bean;
36+
import org.springframework.context.annotation.Configuration;
37+
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
38+
import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient;
39+
import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClientBuilder;
40+
41+
/**
42+
* Configuration for exporting metrics to CloudWatch.
43+
*
44+
* @author Jon Schneider
45+
* @author Dawid Kublik
46+
* @author Jan Sauer
47+
* @author Eddú Meléndez
48+
* @since 2.0.0
49+
*/
50+
@Configuration(proxyBeanMethods = false)
51+
@AutoConfigureBefore({ CompositeMeterRegistryAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class })
52+
@AutoConfigureAfter({ CredentialsProviderAutoConfiguration.class, RegionProviderAutoConfiguration.class,
53+
MetricsAutoConfiguration.class })
54+
@EnableConfigurationProperties({ CloudWatchRegistryProperties.class, CloudWatchProperties.class })
55+
@ConditionalOnProperty(prefix = "management.metrics.export.cloudwatch", name = "namespace")
56+
@ConditionalOnClass({ CloudWatchAsyncClient.class, CloudWatchMeterRegistry.class, AwsRegionProvider.class })
57+
public class CloudWatchExportAutoConfiguration {
58+
59+
@Bean
60+
@ConditionalOnProperty(value = "spring.cloud.aws.cloudwatch.enabled", matchIfMissing = true)
61+
public CloudWatchMeterRegistry cloudWatchMeterRegistry(CloudWatchConfig config, Clock clock,
62+
CloudWatchAsyncClient client) {
63+
return new CloudWatchMeterRegistry(config, clock, client);
64+
}
65+
66+
@Bean
67+
@ConditionalOnMissingBean
68+
public CloudWatchAsyncClient cloudWatchAsyncClient(CloudWatchProperties properties,
69+
AwsClientBuilderConfigurer awsClientBuilderConfigurer,
70+
ObjectProvider<AwsClientCustomizer<CloudWatchAsyncClientBuilder>> configurer) {
71+
return awsClientBuilderConfigurer
72+
.configure(CloudWatchAsyncClient.builder(), properties, configurer.getIfAvailable()).build();
73+
}
74+
75+
@Bean
76+
@ConditionalOnMissingBean
77+
public CloudWatchConfig cloudWatchConfig(CloudWatchRegistryProperties cloudWatchProperties) {
78+
return new CloudWatchPropertiesConfigAdapter(cloudWatchProperties);
79+
}
80+
81+
@Bean
82+
@ConditionalOnMissingBean
83+
public Clock micrometerClock() {
84+
return Clock.SYSTEM;
85+
}
86+
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2013-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.awspring.cloud.autoconfigure.metrics;
17+
18+
import io.awspring.cloud.autoconfigure.AwsClientProperties;
19+
import org.springframework.boot.context.properties.ConfigurationProperties;
20+
21+
/**
22+
* {@link ConfigurationProperties} for configuring CloudWatch client.
23+
*
24+
* @author Jon Schneider
25+
* @author Dawid Kublik
26+
* @author Bernardo Martins
27+
* @author Eddú Meléndez
28+
* @since 2.0.0
29+
*/
30+
@ConfigurationProperties(prefix = "spring.cloud.aws.cloudwatch")
31+
public class CloudWatchProperties extends AwsClientProperties {
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2013-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.awspring.cloud.autoconfigure.metrics;
17+
18+
import io.micrometer.cloudwatch2.CloudWatchConfig;
19+
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter;
20+
21+
/**
22+
* Adapter to convert {@link CloudWatchRegistryProperties} to a {@link CloudWatchConfig}.
23+
*
24+
* @author Jon Schneider
25+
* @author Dawid Kublik
26+
* @since 2.0.0
27+
*/
28+
class CloudWatchPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter<CloudWatchRegistryProperties>
29+
implements CloudWatchConfig {
30+
31+
CloudWatchPropertiesConfigAdapter(CloudWatchRegistryProperties properties) {
32+
super(properties);
33+
}
34+
35+
@Override
36+
public String namespace() {
37+
return get(CloudWatchRegistryProperties::getNamespace, CloudWatchConfig.super::namespace);
38+
}
39+
40+
@Override
41+
public int batchSize() {
42+
return get(CloudWatchRegistryProperties::getBatchSize, CloudWatchConfig.super::batchSize);
43+
}
44+
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2013-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.awspring.cloud.autoconfigure.metrics;
17+
18+
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryProperties;
19+
import org.springframework.boot.context.properties.ConfigurationProperties;
20+
21+
/**
22+
* {@link ConfigurationProperties} for configuring CloudWatch metrics export.
23+
*
24+
* @author Jon Schneider
25+
* @author Dawid Kublik
26+
* @author Bernardo Martins
27+
* @author Eddú Meléndez
28+
* @since 2.0.0
29+
*/
30+
@ConfigurationProperties(prefix = "management.metrics.export.cloudwatch")
31+
public class CloudWatchRegistryProperties extends StepRegistryProperties {
32+
33+
private static final int DEFAULT_BATCH_SIZE = 20;
34+
35+
/**
36+
* The namespace which will be used when sending metrics to CloudWatch. This property is needed and must not be
37+
* null.
38+
*/
39+
private String namespace = "";
40+
41+
public CloudWatchRegistryProperties() {
42+
setBatchSize(DEFAULT_BATCH_SIZE);
43+
}
44+
45+
public String getNamespace() {
46+
return this.namespace;
47+
}
48+
49+
public void setNamespace(String namespace) {
50+
this.namespace = namespace;
51+
}
52+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2013-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Auto-configuration for CloudWatch integration.
19+
*/
20+
@org.springframework.lang.NonNullApi
21+
@org.springframework.lang.NonNullFields
22+
package io.awspring.cloud.autoconfigure.metrics;

spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
22
io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration,\
33
io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration,\
44
io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration,\
5+
io.awspring.cloud.autoconfigure.metrics.CloudWatchExportAutoConfiguration,\
56
io.awspring.cloud.autoconfigure.ses.SesAutoConfiguration,\
67
io.awspring.cloud.autoconfigure.s3.S3TransferManagerAutoConfiguration,\
78
io.awspring.cloud.autoconfigure.s3.S3AutoConfiguration,\

spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ConfiguredAwsClient.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
2626
import software.amazon.awssdk.core.client.config.SdkClientOption;
2727
import software.amazon.awssdk.http.SdkHttpClient;
28+
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
29+
import software.amazon.awssdk.regions.Region;
2830
import software.amazon.awssdk.utils.AttributeMap;
2931

3032
public class ConfiguredAwsClient {
@@ -46,6 +48,10 @@ public boolean isEndpointOverridden() {
4648
return clientConfigurationAttributes.get(SdkClientOption.ENDPOINT_OVERRIDDEN);
4749
}
4850

51+
public Region getRegion() {
52+
return clientConfigurationAttributes.get(AwsClientOption.AWS_REGION);
53+
}
54+
4955
public Duration getApiCallTimeout() {
5056
return clientConfigurationAttributes.get(SdkClientOption.API_CALL_TIMEOUT);
5157
}
@@ -66,4 +72,8 @@ public DefaultsMode getDefaultsMode() {
6672
return clientConfigurationAttributes.get(AwsClientOption.DEFAULTS_MODE);
6773
}
6874

75+
public SdkAsyncHttpClient getAsyncHttpClient() {
76+
return clientConfigurationAttributes.get(SdkClientOption.ASYNC_HTTP_CLIENT);
77+
}
78+
6979
}

0 commit comments

Comments
 (0)