Commit 01f7805c authored by Phillip Webb's avatar Phillip Webb

Merge branch '2.0.x'

parents ff35d141 b1399db9
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.actuate.metrics; package org.springframework.boot.actuate.metrics;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
...@@ -81,15 +82,15 @@ public class MetricsEndpoint { ...@@ -81,15 +82,15 @@ public class MetricsEndpoint {
public MetricResponse metric(@Selector String requiredMetricName, public MetricResponse metric(@Selector String requiredMetricName,
@Nullable List<String> tag) { @Nullable List<String> tag) {
List<Tag> tags = parseTags(tag); List<Tag> tags = parseTags(tag);
List<Meter> meters = new ArrayList<>(); Collection<Meter> meters = findFirstMatchingMeters(this.registry,
collectMeters(meters, this.registry, requiredMetricName, tags); requiredMetricName, tags);
if (meters.isEmpty()) { if (meters.isEmpty()) {
return null; return null;
} }
Map<Statistic, Double> samples = getSamples(meters); Map<Statistic, Double> samples = getSamples(meters);
Map<String, Set<String>> availableTags = getAvailableTags(meters); Map<String, Set<String>> availableTags = getAvailableTags(meters);
tags.forEach((t) -> availableTags.remove(t.getKey())); tags.forEach((t) -> availableTags.remove(t.getKey()));
Meter.Id meterId = meters.get(0).getId(); Meter.Id meterId = meters.iterator().next().getId();
return new MetricResponse(requiredMetricName, meterId.getDescription(), return new MetricResponse(requiredMetricName, meterId.getDescription(),
meterId.getBaseUnit(), asList(samples, Sample::new), meterId.getBaseUnit(), asList(samples, Sample::new),
asList(availableTags, AvailableTag::new)); asList(availableTags, AvailableTag::new));
...@@ -112,18 +113,23 @@ public class MetricsEndpoint { ...@@ -112,18 +113,23 @@ public class MetricsEndpoint {
return Tag.of(parts[0], parts[1]); return Tag.of(parts[0], parts[1]);
} }
private void collectMeters(List<Meter> meters, MeterRegistry registry, String name, private Collection<Meter> findFirstMatchingMeters(MeterRegistry registry, String name,
Iterable<Tag> tags) { Iterable<Tag> tags) {
if (registry instanceof CompositeMeterRegistry) { if (registry instanceof CompositeMeterRegistry) {
((CompositeMeterRegistry) registry).getRegistries() return findFirstMatchingMeters((CompositeMeterRegistry) registry, name, tags);
.forEach((member) -> collectMeters(meters, member, name, tags));
}
else {
meters.addAll(registry.find(name).tags(tags).meters());
} }
return registry.find(name).tags(tags).meters();
}
private Collection<Meter> findFirstMatchingMeters(CompositeMeterRegistry composite,
String name, Iterable<Tag> tags) {
return composite.getRegistries().stream()
.map((registry) -> findFirstMatchingMeters(registry, name, tags))
.filter((matching) -> !matching.isEmpty()).findFirst()
.orElse(Collections.emptyList());
} }
private Map<Statistic, Double> getSamples(List<Meter> meters) { private Map<Statistic, Double> getSamples(Collection<Meter> meters) {
Map<Statistic, Double> samples = new LinkedHashMap<>(); Map<Statistic, Double> samples = new LinkedHashMap<>();
meters.forEach((meter) -> mergeMeasurements(samples, meter)); meters.forEach((meter) -> mergeMeasurements(samples, meter));
return samples; return samples;
...@@ -138,7 +144,7 @@ public class MetricsEndpoint { ...@@ -138,7 +144,7 @@ public class MetricsEndpoint {
return Statistic.MAX.equals(statistic) ? Double::max : Double::sum; return Statistic.MAX.equals(statistic) ? Double::max : Double::sum;
} }
private Map<String, Set<String>> getAvailableTags(List<Meter> meters) { private Map<String, Set<String>> getAvailableTags(Collection<Meter> meters) {
Map<String, Set<String>> availableTags = new HashMap<>(); Map<String, Set<String>> availableTags = new HashMap<>();
meters.forEach((meter) -> mergeAvailableTags(availableTags, meter)); meters.forEach((meter) -> mergeAvailableTags(availableTags, meter));
return availableTags; return availableTags;
......
...@@ -91,6 +91,42 @@ public class MetricsEndpointTests { ...@@ -91,6 +91,42 @@ public class MetricsEndpointTests {
assertThat(getCount(response)).hasValue(4.0); assertThat(getCount(response)).hasValue(4.0);
} }
@Test
public void findFirstMatchingMetersFromNestedRegistries() {
CompositeMeterRegistry composite = new CompositeMeterRegistry();
SimpleMeterRegistry firstLevel0 = new SimpleMeterRegistry();
CompositeMeterRegistry firstLevel1 = new CompositeMeterRegistry();
SimpleMeterRegistry secondLevel = new SimpleMeterRegistry();
composite.add(firstLevel0);
composite.add(firstLevel1);
firstLevel1.add(secondLevel);
secondLevel.counter("cache", "result", "hit", "host", "1").increment(2);
secondLevel.counter("cache", "result", "miss", "host", "1").increment(2);
secondLevel.counter("cache", "result", "hit", "host", "2").increment(2);
MetricsEndpoint endpoint = new MetricsEndpoint(composite);
MetricsEndpoint.MetricResponse response = endpoint.metric("cache",
Collections.emptyList());
assertThat(response.getName()).isEqualTo("cache");
assertThat(availableTagKeys(response)).containsExactly("result", "host");
assertThat(getCount(response)).hasValue(6.0);
response = endpoint.metric("cache", Collections.singletonList("result:hit"));
assertThat(availableTagKeys(response)).containsExactly("host");
assertThat(getCount(response)).hasValue(4.0);
}
@Test
public void matchingMeterNotFoundInNestedRegistries() {
CompositeMeterRegistry composite = new CompositeMeterRegistry();
CompositeMeterRegistry firstLevel = new CompositeMeterRegistry();
SimpleMeterRegistry secondLevel = new SimpleMeterRegistry();
composite.add(firstLevel);
firstLevel.add(secondLevel);
MetricsEndpoint endpoint = new MetricsEndpoint(composite);
MetricsEndpoint.MetricResponse response = endpoint.metric("invalid.metric.name",
Collections.emptyList());
assertThat(response).isNull();
}
@Test @Test
public void metricTagValuesAreDeduplicated() { public void metricTagValuesAreDeduplicated() {
this.registry.counter("cache", "host", "1", "region", "east", "result", "hit"); this.registry.counter("cache", "host", "1", "region", "east", "result", "hit");
......
...@@ -652,6 +652,21 @@ details. ...@@ -652,6 +652,21 @@ details.
[[boot-features-encrypting-properties]]
=== Encrypting Properties
Spring Boot does not provide any built in support for encrypting property values, however,
it does provide the hook points necessary to modify values contained in the Spring
`Environment`. The `EnvironmentPostProcessor` interface allows you to manipulate the
`Environment` before the application starts. See <<howto-customize-the-environment-or-application-context>>
for details.
If you're looking for a secure way to store credentials and passwords, the
https://cloud.spring.io/spring-cloud-vault/[Spring Cloud Vault] project provides
support for storing externalized configuration in
https://www.vaultproject.io/[HashiCorp Vault].
[[boot-features-external-config-yaml]] [[boot-features-external-config-yaml]]
=== Using YAML Instead of Properties === Using YAML Instead of Properties
http://yaml.org[YAML] is a superset of JSON and, as such, is a convenient format for http://yaml.org[YAML] is a superset of JSON and, as such, is a convenient format for
...@@ -6959,6 +6974,10 @@ that the driver exits after each test and that a new instance is injected. If yo ...@@ -6959,6 +6974,10 @@ that the driver exits after each test and that a new instance is injected. If yo
not want this behavior, you can add `@Scope("singleton")` to your `WebDriver` `@Bean` not want this behavior, you can add `@Scope("singleton")` to your `WebDriver` `@Bean`
definition. definition.
WARNING: The `webDriver` scope created by Spring Boot will replace any user defined scope
of the same name. If you define your own `webDriver` scope you may find it stops working
when you use `@WebMvcTest`.
If you have Spring Security on the classpath, `@WebMvcTest` will also scan `WebSecurityConfigurer` If you have Spring Security on the classpath, `@WebMvcTest` will also scan `WebSecurityConfigurer`
beans. Instead of disabling security completely for such tests, you can use Spring Security's test support. beans. Instead of disabling security completely for such tests, you can use Spring Security's test support.
More details on how to use Spring Security's `MockMvc` support can be found in More details on how to use Spring Security's `MockMvc` support can be found in
......
...@@ -749,16 +749,23 @@ listings for Maven and Gradle: ...@@ -749,16 +749,23 @@ listings for Maven and Gradle:
.Gradle .Gradle
[source,groovy,indent=0,subs="attributes"] [source,groovy,indent=0,subs="attributes"]
---- ----
configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
}
dependencies { dependencies {
compile("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-devtools")
} }
---- ----
NOTE: Developer tools are automatically disabled when running a fully packaged NOTE: Developer tools are automatically disabled when running a fully packaged
application. If your application is launched from `java -jar` or if it is started from a application. If your application is launched from `java -jar` or if it is started from a
special classloader, then it is considered a "`production application`". Flagging the special classloader, then it is considered a "`production application`". Flagging the
dependency as optional in Maven or using `compileOnly` in Gradle is a best practice that dependency as optional in Maven or using a custom`developmentOnly` configuration in
prevents devtools from being transitively applied to other modules that use your project. Gradle (as shown above) is a best practice that prevents devtools from being transitively
applied to other modules that use your project.
TIP: Repackaged archives do not contain devtools by default. If you want to use a TIP: Repackaged archives do not contain devtools by default. If you want to use a
<<using-boot-devtools-remote,certain remote devtools feature>>, you need to disable the <<using-boot-devtools-remote,certain remote devtools feature>>, you need to disable the
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment