Helio
07/10/2025, 7:51 AMktor.http.server.requests.upper_99
. But I need to add an extra property on top of this metric that says if the request took longer than 1s, add a new property saying that failed
otherwise met
.
Questions:
1. Is it possible to add new metric on top of this existing metric? Otherwise, would the code below be the most appropriate way to do it?
a. I know that after every endpoint execution Ktor logs the result, such as. 200 OK: GET - /healthcheck in 35ms
. Is it possible to extract the value instead of calculating it?
get("/healthcheck") {
val startTime = System.currentTimeMillis()
println(call.request.uri)
call.respondText(Constants.HEALTHCHECK_RESPONSE, ContentType.Text.Plain)
val endTime = System.currentTimeMillis() // Capture the end time
val duration = endTime - startTime // Calculate the duration
if (duration < 1000L) {
//Send Success metric
}
}
2. What would be the most appropriate way to get the name of the path. call.request.call.route.parent
? Is this the most accurate way? Assuming I don't want to get the value of the "queryParameter" and how the endpoint is defined instead.
Thanks for your help.Aleksei Tirman [JB]
07/10/2025, 8:13 AMHelio
07/10/2025, 8:37 AMDropwizardMetrics
Plugin. I'm using micrometer
.
For example:
install(MicrometerMetrics) {
registry = StatsdMeterRegistry.registry
}
object StatsdMeterRegistry {
val registry = statsdMeterRegistry { configurationParameter ->
when (configurationParameter) {
"statsd.host" -> STATSD_HOST
"statsd.port" -> STATSD_PORT
else -> null
}
}.apply {
config().commonTags(*MICROMETER_COMMON_TAGS.toTypedArray())
}
//Here I can create my custom Metrics if I wish.
}
private fun statsdMeterRegistry(setup: (String) -> String?) = StatsdMeterRegistry(StatsdConfig(setup), Clock.SYSTEM)
2. Yes, I would like to be able to capture the fullEndpoint Path, in a similar way that Ktor does. For example. Notice the arrow, that I dont' want to use the queryParameter value, I would like to keep the definition of the route.Helio
07/10/2025, 8:42 AMStatsdMeterRegistry
, where I create the createEndpointLatencyCounter
object StatsdMeterRegistry {
val registry = statsdMeterRegistry { configurationParameter ->
when (configurationParameter) {
"statsd.host" -> STATSD_HOST
"statsd.port" -> STATSD_PORT
else -> null
}
}.apply {
config().commonTags(*MICROMETER_COMMON_TAGS.toTypedArray())
}
fun createEndpointLatencyCounter(endpoint: RoutingNode?, status: String): Counter {
return Counter.builder("artifactory-permission.endpoint.latency")
.description("Endpoint latency metric")
.tag("endpoint", endpoint) //This can be null according to RouteNode.
.tag("status", status)
.register(registry)
}
}
private fun statsdMeterRegistry(setup: (String) -> String?) = StatsdMeterRegistry(StatsdConfig(setup), Clock.SYSTEM)
Here is an example of the Route
, attempting to send Metric.
put("/validate") {
var result = (mapOf <String, Boolean>())
val start = System.currentTimeMillis()
runCatching {
val requestPayload = call.receive<ArtifactoryPermissionValidatePostRequestModel>()
call.logger.debug("Received validation requestPayload: $requestPayload", )
result = ArtifactoryPermissionService.validateUploadPermission(requestPayload)
}.onSuccess {
val duration = System.currentTimeMillis() - start
if (duration < 1000L) {
StatsdMeterRegistry.createEndpointLatencyCounter(call.request.call.route.parent, "met").increment()
} else {
StatsdMeterRegistry.createEndpointLatencyCounter(call.request.call.route.parent, "failed").increment()
}
call.respond(HttpStatusCode.OK, result)
}.onFailure {
handlePostValidationFailure(it)
}.getOrThrow()
}
Aleksei Tirman [JB]
07/10/2025, 1:30 PMfun RoutingNode?.representation(): String {
val shouldInclude = fun(r: RoutingNode): Boolean {
return r is RoutingRoot || r.selector is PathSegmentConstantRouteSelector
}
if (this == null) return ""
val routes = mutableListOf<RoutingNode>()
if (shouldInclude(this)) {
routes.add(this)
}
var parent: RoutingNode? = this.parent
while (parent != null) {
if (shouldInclude(parent)) {
routes.addFirst(parent)
}
parent = parent.parent
}
return routes.joinToString(separator = "/") { route ->
if (route is RoutingRoot) {
""
} else {
when (val selector = route.selector) {
is PathSegmentConstantRouteSelector -> selector.value
else -> ""
}
}
}
}
Helio
07/10/2025, 11:34 PM200 OK: GET - /healthcheck in 35ms
. I will see how we can do that then.Aleksei Tirman [JB]
07/11/2025, 8:24 AMApplicationCall.processingTimeMillis
method in the ResponseSent
hook.Helio
07/11/2025, 9:45 AM