diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetricsProperties.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetricsProperties.java index b4eb240c8..04c9fe086 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetricsProperties.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetricsProperties.java @@ -20,6 +20,8 @@ import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.BeanExpressionResolver; import org.springframework.boot.actuate.metrics.export.MetricExportProperties; import org.springframework.boot.actuate.metrics.export.TriggerProperties; import org.springframework.boot.bind.RelaxedNames; @@ -28,8 +30,10 @@ import org.springframework.cloud.stream.metrics.config.BinderMetricsAutoConfigur import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.PropertySource; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.PatternMatchUtils; @@ -49,14 +53,15 @@ public class ApplicationMetricsProperties private String[] properties; - private MetricExportProperties export; + private final MetricExportProperties metricExportProperties; public TriggerProperties getTrigger() { - return export.findTrigger(BinderMetricsAutoConfiguration.APPLICATION_METRICS_EXPORTER_TRIGGER_NAME); + return metricExportProperties.findTrigger(BinderMetricsAutoConfiguration.APPLICATION_METRICS_EXPORTER_TRIGGER_NAME); } - public ApplicationMetricsProperties(MetricExportProperties export) { - this.export = export; + public ApplicationMetricsProperties(MetricExportProperties metricExportProperties) { + Assert.notNull(metricExportProperties, "'metricsExportProperties' cannot be null"); + this.metricExportProperties = metricExportProperties; } public String getPrefix() { @@ -114,20 +119,31 @@ public class ApplicationMetricsProperties */ @Override public void onApplicationEvent(ContextRefreshedEvent event) { - ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) event - .getSource(); + ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) event.getSource(); + ConfigurableEnvironment environment = ctx.getEnvironment(); + BeanExpressionResolver beanExpressionResolver = ctx.getBeanFactory().getBeanExpressionResolver(); + BeanExpressionContext expressionContext = new BeanExpressionContext(ctx.getBeanFactory(), null); if (!ObjectUtils.isEmpty(this.properties)) { - for (PropertySource source : ctx.getEnvironment().getPropertySources()) { + for (PropertySource source : environment.getPropertySources()) { if (source instanceof EnumerablePropertySource) { EnumerablePropertySource e = (EnumerablePropertySource) source; for (String propertyName : e.getPropertyNames()) { RelaxedNames relaxedNames = new RelaxedNames(propertyName); relaxedLoop: for (String relaxedPropertyName : relaxedNames) { if (isMatch(relaxedPropertyName, this.properties, null)) { + Object value = source.getProperty(propertyName); + String stringValue = ObjectUtils.nullSafeToString(value); + Object exportedValue = null; + if (value != null) { + exportedValue = stringValue.startsWith("#{") + ? beanExpressionResolver.evaluate( + environment.resolvePlaceholders(stringValue), expressionContext) + : environment.resolvePlaceholders(stringValue); + } this.exportProperties.put( RelaxedPropertiesUtils .findCanonicalFormat(relaxedNames), - source.getProperty(propertyName)); + exportedValue); break relaxedLoop; } } diff --git a/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java b/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java index b6560ccaf..7be45120a 100644 --- a/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java +++ b/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java @@ -20,7 +20,7 @@ import java.util.Collection; import java.util.concurrent.TimeUnit; import com.fasterxml.jackson.databind.ObjectMapper; - +import org.assertj.core.api.Assertions; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -204,6 +204,44 @@ public class ApplicationMetricsExporterTests { applicationContext.close(); } + @Test + public void propertiesWithPlaceholdersAndExpressions() throws Exception { + ConfigurableApplicationContext applicationContext = SpringApplication.run( + BinderExporterApplication.class, + "--server.port=0", + "--spring.jmx.enabled=false", + "--PLATFORM_APP_NAME=123-name-foo", + "--PLATFORM_APP_ID=123-id-bar", + "--spring.cloud.application.guid=${PLATFORM_APP_NAME}.${PLATFORM_APP_ID}", + "--spring.cloud.application.guid.expression=#{'${PLATFORM_APP_NAME}' + '..' + '${PLATFORM_APP_ID}'}", + "--spring.cloud.application.guid.default.prop=${app.name.not.found:time-source}", + "--spring.cloud.application.guid.default.env=${APP_NAME_NOT_FOUND:time-source}", + "--spring.metrics.export.delay-millis=500", + "--spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS + ".destination=foo", + "--spring.metrics.export.includes=integration**", + "--spring.cloud.stream.metrics.properties=spring**"); + Emitter emitterSource = applicationContext.getBean(Emitter.class); + MessageCollector collector = applicationContext.getBean(MessageCollector.class); + Message message = collector.forChannel(emitterSource.applicationMetrics()).poll(1000, + TimeUnit.MILLISECONDS); + Assert.assertNotNull(message); + ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); + ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), ApplicationMetrics.class); + Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", + applicationMetrics.getMetrics())); + Assertions.assertThat(applicationMetrics.getProperties().get("spring.cloud.application.guid")) + .isEqualTo("123-name-foo.123-id-bar"); + Assertions.assertThat(applicationMetrics.getProperties().get("spring.cloud.application.guid.expression")) + .isEqualTo("123-name-foo..123-id-bar"); + Assertions.assertThat(applicationMetrics.getProperties().get("spring.cloud.application.guid.default.prop")) + .isEqualTo("time-source"); + Assertions.assertThat(applicationMetrics.getProperties().get("spring.cloud.application.guid.default.env")) + .isEqualTo("time-source"); + Assert.assertFalse(CollectionUtils.isEmpty(applicationMetrics.getProperties())); + Assert.assertTrue(applicationMetrics.getProperties().get("spring.test.env.syntax").equals("testing")); + applicationContext.close(); + } + @Test public void overrideAppName() throws Exception { ConfigurableApplicationContext applicationContext = SpringApplication.run(