Extensible MicrometerMetricsCaptor and polishing

- change `loadCaptor` to return any existing captor
- make builders and facades protected to allow subclassing
- change `AbstractMeter.getMeter()` to use generics
- add test case demonstrating meter name changes
- docs

* Polishing - PR Comments, and rebase
This commit is contained in:
Gary Russell
2018-10-22 19:58:54 -04:00
committed by Artem Bilan
parent 1d1abf257d
commit eed5fe7a88
4 changed files with 173 additions and 24 deletions

View File

@@ -48,7 +48,7 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
public static final String MICROMETER_CAPTOR_NAME = "integrationMicrometerMetricsCaptor";
private final MeterRegistry meterRegistry;
protected final MeterRegistry meterRegistry; // NOSONAR
public MicrometerMetricsCaptor(MeterRegistry meterRegistry) {
Assert.notNull(meterRegistry, "meterRegistry cannot be null");
@@ -81,7 +81,8 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
}
/**
* Add a MicrometerMetricsCaptor to the context if there's a MeterRegistry.
* Add a MicrometerMetricsCaptor to the context if there's a MeterRegistry; if
* there's already a {@link MetricsCaptor} bean, return that.
* @param applicationContext the application context.
* @return the instance.
*/
@@ -93,13 +94,12 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
((GenericApplicationContext) applicationContext).registerBean(MICROMETER_CAPTOR_NAME,
MicrometerMetricsCaptor.class,
() -> new MicrometerMetricsCaptor(registry));
return applicationContext.getBean(MicrometerMetricsCaptor.class);
}
return applicationContext.getBean(MICROMETER_CAPTOR_NAME, MetricsCaptor.class);
}
catch (NoSuchBeanDefinitionException e) {
return null;
}
return null;
}
private class MicroSample implements SampleFacade {
@@ -110,16 +110,17 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
this.sample = sample;
}
@SuppressWarnings("unchecked")
@Override
public void stop(TimerFacade timer) {
this.sample.stop(((MicroTimer) timer).timer);
this.sample.stop(((AbstractMeter<Timer>) timer).getMeter());
}
}
private static class MicroTimerBuilder implements TimerBuilder {
protected static class MicroTimerBuilder implements TimerBuilder {
private final MeterRegistry meterRegistry;
protected final MeterRegistry meterRegistry; // NOSONAR
private final Timer.Builder builder;
@@ -147,7 +148,7 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
}
private static abstract class AbstractMeter implements MeterFacade {
protected static abstract class AbstractMeter<M extends Meter> implements MeterFacade {
protected final MeterRegistry meterRegistry; // NOSONAR
@@ -159,7 +160,7 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
* Get the meter.
* @return the meter.
*/
protected abstract Meter getMeter();
protected abstract M getMeter();
@SuppressWarnings("unchecked")
@Override
@@ -173,17 +174,18 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
}
}
private static class MicroTimer extends AbstractMeter implements TimerFacade {
protected static class MicroTimer extends AbstractMeter<Timer> implements TimerFacade {
private final Timer timer;
MicroTimer(Timer timer, MeterRegistry meterRegistry) {
protected MicroTimer(Timer timer, MeterRegistry meterRegistry) {
super(meterRegistry);
this.timer = timer;
}
@Override
protected Meter getMeter() {
protected Timer getMeter() {
return this.timer;
}
@@ -194,13 +196,13 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
}
private static class MicroCounterBuilder implements CounterBuilder {
protected static class MicroCounterBuilder implements CounterBuilder {
private final MeterRegistry meterRegistry;
protected final MeterRegistry meterRegistry; // NOSONAR
private final Counter.Builder builder;
MicroCounterBuilder(MeterRegistry meterRegistry, String name) {
protected MicroCounterBuilder(MeterRegistry meterRegistry, String name) {
this.meterRegistry = meterRegistry;
this.builder = Counter.builder(name);
}
@@ -224,17 +226,17 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
}
private static class MicroCounter extends AbstractMeter implements CounterFacade {
protected static class MicroCounter extends AbstractMeter<Counter> implements CounterFacade {
private final Counter counter;
MicroCounter(Counter counter, MeterRegistry meterRegistry) {
protected MicroCounter(Counter counter, MeterRegistry meterRegistry) {
super(meterRegistry);
this.counter = counter;
}
@Override
protected Meter getMeter() {
protected Counter getMeter() {
return this.counter;
}
@@ -245,13 +247,13 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
}
private static class MicroGaugeBuilder implements GaugeBuilder {
protected static class MicroGaugeBuilder implements GaugeBuilder {
private final MeterRegistry meterRegistry;
protected final MeterRegistry meterRegistry; // NOSONAR
private final Gauge.Builder<Object> builder;
MicroGaugeBuilder(MeterRegistry meterRegistry, String name, Object obj, ToDoubleFunction<Object> f) {
protected MicroGaugeBuilder(MeterRegistry meterRegistry, String name, Object obj, ToDoubleFunction<Object> f) {
this.meterRegistry = meterRegistry;
this.builder = Gauge.builder(name, obj, f);
}
@@ -275,17 +277,17 @@ public class MicrometerMetricsCaptor implements MetricsCaptor {
}
private static class MicroGauge extends AbstractMeter implements GaugeFacade {
protected static class MicroGauge extends AbstractMeter<Gauge> implements GaugeFacade {
private final Gauge gauge;
MicroGauge(Gauge gauge, MeterRegistry meterRegistry) {
protected MicroGauge(Gauge gauge, MeterRegistry meterRegistry) {
super(meterRegistry);
this.gauge = gauge;
}
@Override
protected Meter getMeter() {
protected Gauge getMeter() {
return this.gauge;
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.support.management.micrometer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.config.EnableIntegrationManagement;
import org.springframework.integration.support.management.metrics.MetricsCaptor;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.context.junit4.SpringRunner;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.search.MeterNotFoundException;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
/**
* @author Gary Russell
*
* @since 5.1
*
*/
@RunWith(SpringRunner.class)
public class MicrometerCustomMetricsTests {
@Autowired
private ConfigurableApplicationContext context;
@Autowired
private MeterRegistry meterRegistry;
@Autowired
private QueueChannel queue;
@Test
public void testSend() throws Exception {
GenericMessage<String> message = new GenericMessage<>("foo");
this.queue.send(message);
this.queue.receive();
MeterRegistry registry = this.meterRegistry;
assertThat(registry.get("spring.integration.channels").gauge().value()).isEqualTo(3);
assertThat(registry.get("myTimer")
.tag("standardTimerName", "spring.integration.send")
.tag("name", "queue")
.tag("result", "success")
.timer().count()).isEqualTo(1);
assertThat(registry.get("myCounter")
.tag("standardCounterName", "spring.integration.receive")
.tag("name", "queue")
.tag("result", "success")
.counter().count()).isEqualTo(1);
// Test meter removal
this.context.close();
try {
registry.get("myTimer").timers();
fail("Expected MeterNotFoundException");
}
catch (MeterNotFoundException e) {
assertThat(e).hasMessageContaining("No meter with name 'myTimer' was found");
}
try {
registry.get("myCounter").counters();
fail("Expected MeterNotFoundException");
}
catch (MeterNotFoundException e) {
assertThat(e).hasMessageContaining("No meter with name 'myCounter' was found");
}
}
@Configuration
@EnableIntegration
@EnableIntegrationManagement
public static class Config {
@Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
@Bean
public QueueChannel queue() {
return new QueueChannel();
}
@Bean(name = MicrometerMetricsCaptor.MICROMETER_CAPTOR_NAME)
public MetricsCaptor captor() {
return new CustomMetricsCaptor(meterRegistry());
}
}
static class CustomMetricsCaptor extends MicrometerMetricsCaptor {
CustomMetricsCaptor(MeterRegistry meterRegistry) {
super(meterRegistry);
}
@Override
public TimerBuilder timerBuilder(String name) {
return super.timerBuilder("myTimer")
.tag("standardTimerName", name);
}
@Override
public CounterBuilder counterBuilder(String name) {
return super.counterBuilder("myCounter")
.tag("standardCounterName", name);
}
}
}

View File

@@ -157,6 +157,9 @@ In addition, there are three `Gauge` Meters:
* `spring.integration.handlers`: The number of `MessageHandlers` in the application.
* `spring.integration.sources`: The number of `MessageSources` in the application.
It is possible to customize the names and tags of `Meters` created by integration components by providing a subclass of `MicrometerMetricsCaptor`.
The https://github.com/spring-projects/spring-integration/blob/master/spring-integration-core/src/test/java/org/springframework/integration/support/management/micrometer/MicrometerCustomMetricsTests.java[MicrometerCustomMetricsTests] test case shows a simple example of how to do that.
You can also further customize the meters by overloading the `build()` methods on builder subclasses.
[[mgmt-channel-features]]
==== `MessageChannel` Metric Features

View File

@@ -200,3 +200,9 @@ Object name key values are now quoted if they contain any characters other than
e.g. `org.springframework.integration:type=MessageChannel,` `name="input:foo.myGroup.errors"`.
This has the side effect that previously "allowed" names, with such characters, will now be quoted.
e.g. `org.springframework.integration:type=MessageChannel,` `name="input#foo.myGroup.errors"`.
[[x51.-micrometer]]
=== Micrometer Support Changes
It is now simpler to customize the standard Micrometer meters created by the framework.
See <<micrometer-integration>> for more information.