Skip to content

Commit

Permalink
support prometheus export with micrometer
Browse files Browse the repository at this point in the history
  • Loading branch information
tillkuhn committed Feb 8, 2024
1 parent b1eae65 commit c41b349
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 20 deletions.
1 change: 1 addition & 0 deletions kotlin/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ build
out/
.scannerwork/
cookie.txt
prom.txt
30 changes: 30 additions & 0 deletions kotlin/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,33 @@ $ curl -sS http://localhost:8080/actuator/health
== Gradle Version Catalog

* https://blog.jdriven.com/2022/11/gradle-goodness-defining-plugin-versions-using-version-catalog/

== Metrics / Monitoring

* https://github.com/kirshiyin89/springboot-monitoring-demo/tree/feature/monitoring-with-custom-endpoints-and-security[How To Secure Custom Spring Boot Actuator Endpoints and Add Prometheus Metrics]
* https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html[Part V. Spring Boot Actuator: Production-ready features - Metrics / Micrometer]
* https://stackoverflow.com/questions/48451381/spring-boot-actuator-micrometer-metrics-disable-some[Spring Boot Actuator/Micrometer Metrics Disable Some]
* https://medium.com/javarevisited/unlocking-precision-metrics-in-spring-boot-with-micrometer-a-comprehensive-guide-6d72d6eaaf00[Unlocking Precision Metrics in Spring Boot with Micrometer: A Comprehensive Guide]
* https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.security[Spring Boot Actuator Endpoint Security]
* https://www.callicoder.com/spring-boot-actuator-metrics-monitoring-dashboard-prometheus-grafana/[Spring Boot Actuator metrics monitoring with Prometheus and Grafana]

.Example for filtered /actuator/metrics endpoint
----
# HELP jvm_memory_max_bytes The maximum amount of memory in bytes that can be used for memory management
# TYPE jvm_memory_max_bytes gauge
jvm_memory_max_bytes{app="angkor-api",area="nonheap",id="CodeCache",} 5.0331648E7
jvm_memory_max_bytes{app="angkor-api",area="nonheap",id="Metaspace",} -1.0
jvm_memory_max_bytes{app="angkor-api",area="nonheap",id="Compressed Class Space",} 1.073741824E9
jvm_memory_max_bytes{app="angkor-api",area="heap",id="G1 Eden Space",} -1.0
jvm_memory_max_bytes{app="angkor-api",area="heap",id="G1 Survivor Space",} -1.0
jvm_memory_max_bytes{app="angkor-api",area="heap",id="G1 Old Gen",} 8.589934592E9
# HELP process_cpu_usage The "recent cpu usage" for the Java Virtual Machine process
# TYPE process_cpu_usage gauge
process_cpu_usage{app="angkor-api",} 0.0
# HELP jdbc_connections_active Current number of active connections that have been allocated from the data source.
# TYPE jdbc_connections_active gauge
jdbc_connections_active{app="angkor-api",name="dataSource",} 0.0
# HELP process_start_time_seconds Start time of the process since unix epoch.
# TYPE process_start_time_seconds gauge
process_start_time_seconds{app="angkor-api",} 1.707417503734E9
----
5 changes: 4 additions & 1 deletion kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,14 @@ dependencies {
implementation(libs.bundles.rome)
implementation(libs.bucket4j)


// Persistence (Postgres, JPA, Hibernate)
implementation(libs.postgresql)
implementation(libs.bundles.flyway)
implementation(libs.hypersistence.utils.hibernate)

// Monitoring / Micrometer
implementation(libs.micrometer.prometheus)


// Jackson JSON Parsing Dependencies
// For Gradle users, if you use the Spring Boot Gradle plugin you can omit the version number to adopt
Expand Down
2 changes: 2 additions & 0 deletions kotlin/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ wiremock = { module = "com.github.tomakehurst:wiremock-standalone", version = "3
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-all-open = { module = "org.jetbrains.kotlin:kotlin-allopen", version.ref = "kotlin" }

micrometer-prometheus = { module = "io.micrometer:micrometer-registry-prometheus", version = "1.12.2" }


[bundles]
# Nice ... we can bundle libs and use like "libs.bundles.flyway"
Expand Down
68 changes: 68 additions & 0 deletions kotlin/src/main/kotlin/net/timafe/angkor/config/MetricsConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package net.timafe.angkor.config

import io.micrometer.core.instrument.Meter
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.config.MeterFilter
import io.micrometer.core.instrument.config.MeterFilterReply
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration


@Configuration
class MetricsConfig {

companion object {
val FilterNames = setOf(
"hikaricp.connections.max",
"hikaricp.connections.active",
"hikaricp.connections.acquire",
"hikaricp.connections",
"jvm.memory.max",
"jvm.memory.committed",
"jvm.memory.used",
"process.start.time",
"process.uptime",
"system.cpu.usage",
"tomcat.sessions.active.current",
"tomcat.sessions.active.max",
"tomcat.sessions.created"
)
}

// add common tags
@Bean
fun metricsCommonTags(): MeterRegistryCustomizer<MeterRegistry> {
return MeterRegistryCustomizer<MeterRegistry> { registry: MeterRegistry ->
registry.config().commonTags("app", "angkor-api")
}
}

/**
* Reduce metrics https://stackoverflow.com/a/54097060/4292075
* https://github.com/micrometer-metrics/micrometer-docs/blob/main/src/docs/concepts/meter-filters.adoc#deny-or-accept-meters
* Example Names (it uses dots, not the prometheus style underscore notation)
* http.server.requests.active
* spring.security.filter.active *
*/

@Bean
fun meterFilter(): MeterFilter {
return object : MeterFilter {
override fun accept(id: Meter.Id): MeterFilterReply {
//listOf("jdbc.connections.active", "jvm.memory.max","process.cpu.usage","process.start").forEach {
FilterNames.forEach {
if (id.name.startsWith(it)) {
return MeterFilterReply.NEUTRAL
}
}
// println(id.name)
// if (id.getName().startsWith("jvm.")) {
// return MeterFilterReply.DENY
// }
return MeterFilterReply.DENY // deny all other
}
}
}

}
6 changes: 6 additions & 0 deletions kotlin/src/main/kotlin/net/timafe/angkor/config/WebConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.http.converter.StringHttpMessageConverter
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.EnableWebMvc
Expand Down Expand Up @@ -38,9 +39,14 @@ class WebConfig(private val objectMapper: ObjectMapper) : WebMvcConfigurer{
#
* https://stackoverflow.com/a/55958912/4292075
* https://stackoverflow.com/a/49405364/4292075
*
* Similar issue with actuator/prometheus endpoint if there's no
* HttpMessageConverter registered that could handle the "text/plain" MediaType, only
* https://stackoverflow.com/a/52085616/4292075
*/
override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>?>) {
converters.add(MappingJackson2HttpMessageConverter(objectMapper))
converters.add( StringHttpMessageConverter())
// addDefaultHttpMessageConverters(converters)
super.configureMessageConverters(converters)
}
Expand Down
20 changes: 2 additions & 18 deletions kotlin/src/main/kotlin/net/timafe/angkor/web/MetricsController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode
import jakarta.servlet.http.HttpServletRequest
import net.timafe.angkor.config.AppProperties
import net.timafe.angkor.config.Constants
import net.timafe.angkor.config.MetricsConfig.Companion.FilterNames
import net.timafe.angkor.domain.dto.MetricDetails
import net.timafe.angkor.service.MetricsService
import net.timafe.angkor.web.vm.BooleanResult
Expand Down Expand Up @@ -31,23 +32,6 @@ class MetricsController(

private val log: Logger = LoggerFactory.getLogger(this.javaClass)

companion object {
val filterNames = setOf(
"hikaricp.connections.max",
"hikaricp.connections.active",
"hikaricp.connections.acquire",
"hikaricp.connections",
"jvm.memory.max",
"jvm.memory.committed",
"jvm.memory.used",
"process.start.time",
"process.uptime",
"system.cpu.usage",
"tomcat.sessions.active.current",
"tomcat.sessions.active.max",
"tomcat.sessions.created"
)
}

@GetMapping("${Constants.API_LATEST}/stats")
fun entityStats(): Map<String, Long> {
Expand All @@ -64,7 +48,7 @@ class MetricsController(
metrics.add(MetricDetails("kotlin.version", "Kotlin Version", KotlinVersion.CURRENT.toString(), null))
metrics.add(MetricDetails("app.version", "App Version (API)", appProperties.version, null))
metrics.addAll(metricsEndpoint.listNames().names
.filter { filterNames.contains(it) }
.filter { FilterNames.contains(it) }
.map {
val resp: MetricsEndpoint.MetricDescriptor = metricsEndpoint.metric(it, null)
MetricDetails(resp.name, resp.description, resp.measurements[0].value, resp.baseUnit)
Expand Down
6 changes: 5 additions & 1 deletion kotlin/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,15 @@ management:
endpoints:
web:
exposure:
include: info,health,metrics
include: info,health,metrics,prometheus
endpoint:
# https://www.callicoder.com/spring-boot-actuator/#displaying-detailed-health-information
health:
show-details: always
prometheus:
metrics:
export:
enabled: true # new key, metrics.prometheus ... is deprecated

server:
error:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class MetricsControllerTest(private val controller: MetricsController) {

fun `test admin status`() {
val stats = controller.metrics()
// Size should all custom metrics registered in MetricsController
// plus those that are not ignored in net.timafe.angkor.config.MetricsConfig
Assertions.assertThat(stats.size).isGreaterThan(15)
}

Expand Down

0 comments on commit c41b349

Please sign in to comment.