Skip to content

Commit 2edc354

Browse files
Add auto-configuration for LocalResponseCache metrics
Fixes gh-3722 Signed-off-by: LivingLikeKrillin <143606756+LivingLikeKrillin@users.noreply.github.com>
1 parent 877b160 commit 2edc354

File tree

3 files changed

+185
-0
lines changed

3 files changed

+185
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2013-present 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+
package org.springframework.cloud.gateway.config;
18+
19+
import java.util.Collections;
20+
21+
import com.github.benmanes.caffeine.cache.Caffeine;
22+
import io.micrometer.core.instrument.MeterRegistry;
23+
import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics;
24+
25+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
29+
import org.springframework.boot.micrometer.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration;
30+
import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration;
31+
import org.springframework.cache.caffeine.CaffeineCacheManager;
32+
import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheGatewayFilterFactory.CacheMetricsListener;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Configuration;
35+
36+
/**
37+
* Auto-configuration for LocalResponseCache metrics. Registers Caffeine cache metrics
38+
* with the {@link MeterRegistry} when both the cache infrastructure and Micrometer are
39+
* available.
40+
*
41+
* @author LivingLikeKrillin
42+
*/
43+
@Configuration(proxyBeanMethods = false)
44+
@ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class, MeterRegistry.class, MetricsAutoConfiguration.class })
45+
@ConditionalOnBean(MeterRegistry.class)
46+
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".metrics.enabled", matchIfMissing = true)
47+
@AutoConfigureAfter({ LocalResponseCacheAutoConfiguration.class, MetricsAutoConfiguration.class,
48+
CompositeMeterRegistryAutoConfiguration.class })
49+
public class LocalResponseCacheMetricsAutoConfiguration {
50+
51+
@Bean
52+
CacheMetricsListener localResponseCacheMetricsListener(MeterRegistry meterRegistry) {
53+
return (cache, cacheName) -> CaffeineCacheMetrics.monitor(meterRegistry, cache, cacheName,
54+
Collections.emptyList());
55+
}
56+
57+
}

spring-cloud-gateway-server-webflux/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfigurat
1010
org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration
1111
org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration
1212
org.springframework.cloud.gateway.config.LocalResponseCacheAutoConfiguration
13+
org.springframework.cloud.gateway.config.LocalResponseCacheMetricsAutoConfiguration
1314
org.springframework.cloud.gateway.config.GatewayTracingAutoConfiguration
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2013-present 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+
package org.springframework.cloud.gateway.config;
18+
19+
import java.time.Duration;
20+
21+
import com.github.benmanes.caffeine.cache.Caffeine;
22+
import io.micrometer.core.instrument.MeterRegistry;
23+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
24+
import org.junit.jupiter.api.Test;
25+
26+
import org.springframework.boot.autoconfigure.AutoConfigurations;
27+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
28+
import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheGatewayFilterFactory.CacheMetricsListener;
29+
import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties;
30+
import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheUtils;
31+
import org.springframework.context.annotation.Bean;
32+
import org.springframework.context.annotation.Configuration;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
36+
/**
37+
* Tests for {@link LocalResponseCacheMetricsAutoConfiguration}.
38+
*
39+
* @author LivingLikeKrillin
40+
*/
41+
class LocalResponseCacheMetricsAutoConfigurationTests {
42+
43+
@Test
44+
void metricsListenerCreatedWhenMeterRegistryPresent() {
45+
new ApplicationContextRunner()
46+
.withConfiguration(AutoConfigurations.of(LocalResponseCacheAutoConfiguration.class,
47+
LocalResponseCacheMetricsAutoConfiguration.class))
48+
.withUserConfiguration(MeterRegistryConfig.class)
49+
.withPropertyValues(GatewayProperties.PREFIX + ".filter.local-response-cache.enabled=true")
50+
.run(context -> {
51+
assertThat(context).hasSingleBean(CacheMetricsListener.class);
52+
});
53+
}
54+
55+
@Test
56+
void metricsListenerNotCreatedWhenMeterRegistryAbsent() {
57+
new ApplicationContextRunner()
58+
.withConfiguration(AutoConfigurations.of(LocalResponseCacheAutoConfiguration.class,
59+
LocalResponseCacheMetricsAutoConfiguration.class))
60+
.withPropertyValues(GatewayProperties.PREFIX + ".filter.local-response-cache.enabled=true")
61+
.run(context -> {
62+
assertThat(context).doesNotHaveBean(CacheMetricsListener.class);
63+
});
64+
}
65+
66+
@Test
67+
void metricsListenerNotCreatedWhenMetricsDisabled() {
68+
new ApplicationContextRunner()
69+
.withConfiguration(AutoConfigurations.of(LocalResponseCacheAutoConfiguration.class,
70+
LocalResponseCacheMetricsAutoConfiguration.class))
71+
.withUserConfiguration(MeterRegistryConfig.class)
72+
.withPropertyValues(GatewayProperties.PREFIX + ".filter.local-response-cache.enabled=true",
73+
GatewayProperties.PREFIX + ".metrics.enabled=false")
74+
.run(context -> {
75+
assertThat(context).doesNotHaveBean(CacheMetricsListener.class);
76+
});
77+
}
78+
79+
@Test
80+
void caffeineRecordStatsEnabled() {
81+
LocalResponseCacheProperties properties = new LocalResponseCacheProperties();
82+
properties.setTimeToLive(Duration.ofMinutes(5));
83+
Caffeine<Object, Object> caffeine = LocalResponseCacheUtils.createCaffeine(properties);
84+
com.github.benmanes.caffeine.cache.Cache<Object, Object> cache = caffeine.build();
85+
86+
cache.put("key", "value");
87+
cache.getIfPresent("key");
88+
cache.getIfPresent("missing");
89+
90+
assertThat(cache.stats().hitCount()).isEqualTo(1);
91+
assertThat(cache.stats().missCount()).isEqualTo(1);
92+
}
93+
94+
@Test
95+
void cacheMetricsListenerBindsToMeterRegistry() {
96+
SimpleMeterRegistry registry = new SimpleMeterRegistry();
97+
LocalResponseCacheProperties properties = new LocalResponseCacheProperties();
98+
properties.setTimeToLive(Duration.ofMinutes(5));
99+
Caffeine<Object, Object> caffeine = LocalResponseCacheUtils.createCaffeine(properties);
100+
com.github.benmanes.caffeine.cache.Cache<Object, Object> cache = caffeine.build();
101+
102+
new ApplicationContextRunner()
103+
.withConfiguration(AutoConfigurations.of(LocalResponseCacheMetricsAutoConfiguration.class))
104+
.withBean(MeterRegistry.class, () -> registry)
105+
.run(context -> {
106+
CacheMetricsListener listener = context.getBean(CacheMetricsListener.class);
107+
listener.onCacheCreated(cache, "test-cache");
108+
109+
cache.put("key", "value");
110+
cache.getIfPresent("key");
111+
112+
assertThat(registry.find("cache.gets").tag("result", "hit").functionCounter()).isNotNull();
113+
assertThat(registry.find("cache.size").tag("cache", "test-cache").gauge()).isNotNull();
114+
});
115+
}
116+
117+
@Configuration(proxyBeanMethods = false)
118+
static class MeterRegistryConfig {
119+
120+
@Bean
121+
MeterRegistry meterRegistry() {
122+
return new SimpleMeterRegistry();
123+
}
124+
125+
}
126+
127+
}

0 commit comments

Comments
 (0)