All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.javalin.plugin.metrics.MicrometerPlugin.kt Maven / Gradle / Ivy

/*
 * Javalin - https://javalin.io
 * Copyright 2020 David Åse
 * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
 */

package io.javalin.plugin.metrics

import io.javalin.Javalin
import io.javalin.core.plugin.Plugin
import io.javalin.core.util.OptionalDependency
import io.javalin.core.util.Util
import io.javalin.http.Context
import io.javalin.http.ExceptionHandler
import io.javalin.http.HandlerType
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Metrics
import io.micrometer.core.instrument.Tag
import io.micrometer.core.instrument.Tags
import io.micrometer.core.instrument.binder.http.DefaultHttpServletRequestTagsProvider
import io.micrometer.core.instrument.binder.jetty.JettyConnectionMetrics
import io.micrometer.core.instrument.binder.jetty.JettyServerThreadPoolMetrics
import io.micrometer.core.instrument.binder.jetty.TimedHandler
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class MicrometerPlugin @JvmOverloads constructor(
    private val registry: MeterRegistry = Metrics.globalRegistry,
    private val tags: Iterable = Tags.empty(),
    private val tagExceptionName: Boolean = false,
    private val tagRedirectPaths: Boolean = false,
    private val tagNotFoundMappedPaths: Boolean = false
) : Plugin {
    override fun apply(app: Javalin) {
        Util.ensureDependencyPresent(OptionalDependency.MICROMETER)
        app.jettyServer()?.server()?.let { server ->
            if (tagExceptionName) {
                app.exception(Exception::class.java, EXCEPTION_HANDLER)
            }

            server.insertHandler(TimedHandler(registry, tags, object : DefaultHttpServletRequestTagsProvider() {
                override fun getTags(request: HttpServletRequest, response: HttpServletResponse): Iterable {
                    val exceptionName = if (tagExceptionName) {
                        response.getHeader(EXCEPTION_HEADER)
                    } else {
                        "Unknown"
                    }
                    val pathInfo = request.pathInfo.removePrefix(app._conf.contextPath).prefixIfNot("/")
                    response.setHeader(EXCEPTION_HEADER, null)
                    val handlerType = HandlerType.valueOf(request.method)
                    val uri = app.javalinServlet().matcher.findEntries(handlerType, pathInfo).asSequence()
                        .map { it.path }
                        .map { if (it == "/" || it.isBlank()) "root" else it }
                        .map { if (!tagRedirectPaths && response.status in 300..399) "REDIRECTION" else it }
                        .map { if (!tagNotFoundMappedPaths && response.status == 404) "NOT_FOUND" else it }
                        .firstOrNull() ?: "NOT_FOUND"
                    return Tags.concat(
                        super.getTags(request, response),
                        "uri", uri,
                        "exception", exceptionName ?: "None"
                    )
                }
            }))

            JettyServerThreadPoolMetrics(server.threadPool, tags).bindTo(registry)
            app.events {
                it.serverStarted {
                    JettyConnectionMetrics.addToAllConnectors(server, registry, tags)
                }
            }
        }
    }

    companion object {
        private const val EXCEPTION_HEADER = "__micrometer_exception_name"

        var EXCEPTION_HANDLER = ExceptionHandler { e: Exception, ctx: Context ->
            val simpleName = e.javaClass.simpleName
            ctx.header(EXCEPTION_HEADER, simpleName.ifBlank { e.javaClass.name })
            ctx.status(500)
        }
    }

    private fun String.prefixIfNot(prefix: String) = if (this.startsWith(prefix)) this else "$prefix$this"
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy