Skip to content

Commit 54fc5dd

Browse files
add metrics configuration so that histogram (bucket) is enabled for http.client metrics from webclient (http_client_requests_seconds_bucket).
Co-authored-by: Jan Schawo <[email protected]>
1 parent 2110a25 commit 54fc5dd

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package de.otto.babbage.core.config
2+
3+
import io.micrometer.common.KeyValue
4+
import io.micrometer.common.KeyValues
5+
import org.springframework.boot.actuate.metrics.http.Outcome
6+
import org.springframework.web.reactive.function.client.ClientRequest
7+
import org.springframework.web.reactive.function.client.ClientRequestObservationContext
8+
import org.springframework.web.reactive.function.client.DefaultClientRequestObservationConvention
9+
10+
class ClientRequestMetricsTagContributor : DefaultClientRequestObservationConvention() {
11+
12+
override fun getLowCardinalityKeyValues(context: ClientRequestObservationContext): KeyValues {
13+
return KeyValues.of(
14+
KeyValue.of("method", context.request?.method()?.name() ?: "undefined"),
15+
KeyValue.of("uri", extractFirstPath(context.request)),
16+
KeyValue.of("client.name", context.request?.url()?.host ?: "none"),
17+
KeyValue.of("status", context.response?.statusCode()?.value()?.toString() ?: "undefined"),
18+
KeyValue.of("outcome", context.response?.statusCode()?.value()?.let { Outcome.forStatus(it).toString() } ?: Outcome.UNKNOWN.toString())
19+
)
20+
}
21+
22+
private fun extractFirstPath(request: ClientRequest?): String {
23+
return if (request != null) {
24+
"/" + request.url().path.split("/").firstOrNull { it.isNotBlank() }
25+
} else {
26+
"undefined"
27+
}
28+
}
29+
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package de.otto.babbage.core.config
2+
3+
import io.micrometer.core.instrument.Meter
4+
import io.micrometer.core.instrument.MeterRegistry
5+
import io.micrometer.core.instrument.config.MeterFilter
6+
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig
7+
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer
8+
import org.springframework.context.annotation.Bean
9+
import org.springframework.context.annotation.Configuration
10+
11+
@Configuration
12+
class MetricConfiguration {
13+
14+
@Bean
15+
fun clientRequestMetricsTagContributor(): ClientRequestMetricsTagContributor {
16+
return ClientRequestMetricsTagContributor()
17+
}
18+
19+
@Bean
20+
fun meterRegistryCustomizer(): MeterRegistryCustomizer<MeterRegistry> =
21+
MeterRegistryCustomizer<MeterRegistry> { registry ->
22+
registry.config()
23+
.meterFilter(object : MeterFilter {
24+
override fun configure(id: Meter.Id, config: DistributionStatisticConfig): DistributionStatisticConfig {
25+
return if (id.name.startsWith("http.client")) {
26+
config.merge(DistributionStatisticConfig.builder().percentilesHistogram(true).build())
27+
} else {
28+
config
29+
}
30+
}
31+
32+
})
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package de.otto.babbage.core.config
2+
3+
import io.kotest.matchers.collections.shouldContain
4+
import io.micrometer.common.KeyValue
5+
import org.junit.jupiter.api.Test
6+
import org.mockito.Mockito
7+
import org.springframework.http.HttpMethod
8+
import org.springframework.http.HttpStatusCode
9+
import org.springframework.web.reactive.function.client.ClientRequest
10+
import org.springframework.web.reactive.function.client.ClientRequestObservationContext
11+
import org.springframework.web.reactive.function.client.ClientResponse
12+
import java.net.URI
13+
14+
internal class ClientRequestMetricsTagContributorTest {
15+
16+
@Test
17+
fun `should create key value pairs for web client request`() {
18+
// given
19+
val url = "http://www.otto.de/first/second/third"
20+
val clientRequest = ClientRequest.create(HttpMethod.GET, URI.create(url))
21+
val clientResponse = ClientResponse.create(HttpStatusCode.valueOf(200)).build()
22+
val observationContext = ClientRequestObservationContext(clientRequest)
23+
observationContext.setResponse(clientResponse)
24+
25+
// when
26+
val result = ClientRequestMetricsTagContributor().getLowCardinalityKeyValues(observationContext)
27+
28+
// then
29+
result shouldContain KeyValue.of("method", "GET")
30+
result shouldContain KeyValue.of("uri", "/first")
31+
result shouldContain KeyValue.of("client.name", "www.otto.de")
32+
result shouldContain KeyValue.of("status", "200")
33+
result shouldContain KeyValue.of("outcome", "SUCCESS")
34+
}
35+
36+
@Test
37+
fun `should create fallback key value pairs for empty request`() {
38+
// given
39+
val mockedContext = Mockito.mock(ClientRequestObservationContext::class.java)
40+
41+
// when
42+
val result = ClientRequestMetricsTagContributor().getLowCardinalityKeyValues(mockedContext)
43+
44+
// then
45+
result shouldContain KeyValue.of("method", "undefined")
46+
result shouldContain KeyValue.of("uri", "undefined")
47+
result shouldContain KeyValue.of("client.name", "none")
48+
result shouldContain KeyValue.of("status", "undefined")
49+
result shouldContain KeyValue.of("outcome", "UNKNOWN")
50+
}
51+
}

0 commit comments

Comments
 (0)