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

ai.platon.pulsar.skeleton.common.metrics.CodahaleSlf4jReporter.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0
Show newest version
package ai.platon.pulsar.skeleton.common.metrics

import com.codahale.metrics.*
import com.codahale.metrics.Histogram
import com.codahale.metrics.Timer
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
import java.util.*
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.function.Supplier

/**
 * A reporter class for logging metrics values to a SLF4J [Logger] periodically, similar to
 * [ConsoleReporter] or [CsvReporter], but using the SLF4J framework instead. It also
 * supports specifying a [Marker] instance that can be used by custom appenders and filters
 * for the bound logging toolkit to further process metrics reports.
 */
class CodahaleSlf4jReporter private constructor(registry: MetricRegistry,
                                                private val loggerProxy: LoggerProxy,
                                                private val marker: Marker?,
                                                private val prefix: String,
                                                rateUnit: TimeUnit,
                                                durationUnit: TimeUnit,
                                                filter: MetricFilter,
                                                executor: ScheduledExecutorService?,
                                                shutdownExecutorOnStop: Boolean,
                                                disabledMetricAttributes: Set
) : ScheduledReporter(registry, "logger-reporter",
        filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop, disabledMetricAttributes) {
    enum class LoggingLevel {
        TRACE, DEBUG, INFO, WARN, ERROR
    }

    /**
     * A builder for [Slf4jReporter] instances. Defaults to logging to `metrics`, not
     * using a marker, converting rates to events/second, converting durations to milliseconds, and
     * not filtering metrics.
     */
    class Builder(private val registry: MetricRegistry) {
        private var logger = LoggerFactory.getLogger("metrics")
        private var loggingLevel = LoggingLevel.INFO
        private var marker: Marker? = null
        private var prefix = ""
        private var rateUnit = TimeUnit.SECONDS
        private var durationUnit = TimeUnit.MILLISECONDS
        private var filter = MetricFilter.ALL
        private var executor: ScheduledExecutorService? = null
        private var shutdownExecutorOnStop = true
        private var disabledMetricAttributes: Set = setOf()

        /**
         * Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.
         * Default value is true.
         * Setting this parameter to false, has the sense in combining with providing external managed executor via [.scheduleOn].
         *
         * @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
         * @return `this`
         */
        fun shutdownExecutorOnStop(shutdownExecutorOnStop: Boolean): Builder {
            this.shutdownExecutorOnStop = shutdownExecutorOnStop
            return this
        }

        /**
         * Specifies the executor to use while scheduling reporting of metrics.
         * Default value is null.
         * Null value leads to executor will be auto created on start.
         *
         * @param executor the executor to use while scheduling reporting of metrics.
         * @return `this`
         */
        fun scheduleOn(executor: ScheduledExecutorService?): Builder {
            this.executor = executor
            return this
        }

        /**
         * Log metrics to the given logger.
         *
         * @param logger an SLF4J [Logger]
         * @return `this`
         */
        fun outputTo(logger: Logger): Builder {
            this.logger = logger
            return this
        }

        /**
         * Mark all logged metrics with the given marker.
         *
         * @param marker an SLF4J [Marker]
         * @return `this`
         */
        fun markWith(marker: Marker?): Builder {
            this.marker = marker
            return this
        }

        /**
         * Prefix all metric names with the given string.
         *
         * @param prefix the prefix for all metric names
         * @return `this`
         */
        fun prefixedWith(prefix: String): Builder {
            this.prefix = prefix
            return this
        }

        /**
         * Convert rates to the given time unit.
         *
         * @param rateUnit a unit of time
         * @return `this`
         */
        fun convertRatesTo(rateUnit: TimeUnit): Builder {
            this.rateUnit = rateUnit
            return this
        }

        /**
         * Convert durations to the given time unit.
         *
         * @param durationUnit a unit of time
         * @return `this`
         */
        fun convertDurationsTo(durationUnit: TimeUnit): Builder {
            this.durationUnit = durationUnit
            return this
        }

        /**
         * Only report metrics which match the given filter.
         *
         * @param filter a [MetricFilter]
         * @return `this`
         */
        fun filter(filter: MetricFilter): Builder {
            this.filter = filter
            return this
        }

        /**
         * Use Logging Level when reporting.
         *
         * @param loggingLevel a (@link Slf4jReporter.LoggingLevel}
         * @return `this`
         */
        fun withLoggingLevel(loggingLevel: LoggingLevel): Builder {
            this.loggingLevel = loggingLevel
            return this
        }

        /**
         * Don't report the passed metric attributes for all metrics (e.g. "p999", "stddev" or "m15").
         * See [MetricAttribute].
         *
         * @param disabledMetricAttributes a set of [MetricAttribute]
         * @return `this`
         */
        fun disabledMetricAttributes(disabledMetricAttributes: Set): Builder {
            this.disabledMetricAttributes = disabledMetricAttributes
            return this
        }

        /**
         * Builds a [Slf4jReporter] with the given properties.
         *
         * @return a [Slf4jReporter]
         */
        fun build(): CodahaleSlf4jReporter {
            val loggerProxy = when (loggingLevel) {
                LoggingLevel.TRACE -> TraceLoggerProxy(logger)
                LoggingLevel.INFO -> InfoLoggerProxy(logger)
                LoggingLevel.WARN -> WarnLoggerProxy(logger)
                LoggingLevel.ERROR -> ErrorLoggerProxy(logger)
                LoggingLevel.DEBUG -> DebugLoggerProxy(logger)
            }

            return CodahaleSlf4jReporter(registry, loggerProxy, marker, prefix, rateUnit, durationUnit, filter, executor,
                    shutdownExecutorOnStop, disabledMetricAttributes)
        }
    }

    override fun report(gauges: SortedMap>,
                        counters: SortedMap,
                        histograms: SortedMap,
                        meters: SortedMap,
                        timers: SortedMap) {
        if (loggerProxy.isEnabled(marker)) {
            val b = StringBuilder()
            for ((key, value) in gauges) {
                logGauge(b, key, value)
            }
            for ((key, value) in counters) {
                logCounter(b, key, value)
            }
            for ((key, value) in histograms) {
                logHistogram(b, key, value)
            }
            for ((key, value) in meters) {
                logMeter(b, key, value)
            }
            for ((key, value) in timers) {
                logTimer(b, key, value)
            }
        }
    }

    private fun logTimer(b: StringBuilder, name: String, timer: Timer) {
        val snapshot = timer.snapshot
        b.setLength(0)
        b.append("[TIMER] ").append(prefix(name)).append(" | ")
        appendCountIfEnabled(b, timer)
        appendLongDurationIfEnabled(b, MetricAttribute.MIN, Supplier { snapshot.min })
        appendLongDurationIfEnabled(b, MetricAttribute.MAX, Supplier { snapshot.max })
        appendDoubleDurationIfEnabled(b, MetricAttribute.MEAN, Supplier { snapshot.mean })
        appendDoubleDurationIfEnabled(b, MetricAttribute.STDDEV, Supplier { snapshot.stdDev })
        appendDoubleDurationIfEnabled(b, MetricAttribute.P50, Supplier { snapshot.median })
        appendDoubleDurationIfEnabled(b, MetricAttribute.P75, Supplier { snapshot.get75thPercentile() })
        appendDoubleDurationIfEnabled(b, MetricAttribute.P95, Supplier { snapshot.get95thPercentile() })
        appendDoubleDurationIfEnabled(b, MetricAttribute.P98, Supplier { snapshot.get98thPercentile() })
        appendDoubleDurationIfEnabled(b, MetricAttribute.P99, Supplier { snapshot.get99thPercentile() })
        appendDoubleDurationIfEnabled(b, MetricAttribute.P999, Supplier { snapshot.get999thPercentile() })
        appendMetered(b, timer)
        append(b, "rate_unit", rateUnit)
        append(b, "duration_unit", durationUnit)
        loggerProxy.log(marker, b.toString())
    }

    private fun logMeter(b: StringBuilder, name: String, meter: Meter) {
        b.setLength(0)
        b.append("[METER] ").append(prefix(name)).append(" | ")
        appendCountIfEnabled(b, meter)
        appendMetered(b, meter)
        append(b, "rate_unit", rateUnit)
        loggerProxy.log(marker, b.toString())
    }

    private fun logHistogram(b: StringBuilder, name: String, histogram: Histogram) {
        val snapshot = histogram.snapshot
        b.setLength(0)
        b.append("[HISTOGRAM] ").append(prefix(name)).append(" | ")
        appendCountIfEnabled(b, histogram)
        appendLongIfEnabled(b, MetricAttribute.MIN, Supplier { snapshot.min })
        appendLongIfEnabled(b, MetricAttribute.MAX, Supplier { snapshot.max })
        appendDoubleIfEnabled(b, MetricAttribute.MEAN, Supplier { snapshot.mean })
        appendDoubleIfEnabled(b, MetricAttribute.STDDEV, Supplier { snapshot.stdDev })
        appendDoubleIfEnabled(b, MetricAttribute.P50, Supplier { snapshot.median })
        appendDoubleIfEnabled(b, MetricAttribute.P75, Supplier { snapshot.get75thPercentile() })
        appendDoubleIfEnabled(b, MetricAttribute.P95, Supplier { snapshot.get95thPercentile() })
        appendDoubleIfEnabled(b, MetricAttribute.P98, Supplier { snapshot.get98thPercentile() })
        appendDoubleIfEnabled(b, MetricAttribute.P99, Supplier { snapshot.get99thPercentile() })
        appendDoubleIfEnabled(b, MetricAttribute.P999, Supplier { snapshot.get999thPercentile() })
        loggerProxy.log(marker, b.toString())
    }

    private fun logCounter(b: StringBuilder, name: String, counter: Counter) {
        b.setLength(0)
        b.append("[COUNTER] ").append(prefix(name)).append(" | ")
        append(b, MetricAttribute.COUNT.code, counter.count)
        loggerProxy.log(marker, b.toString())
    }

    private fun logGauge(b: StringBuilder, name: String, gauge: Gauge<*>) {
        b.setLength(0)
        b.append("[GAUGE] ").append(prefix(name)).append(" | ")
        append(b, "value", gauge.value)
        loggerProxy.log(marker, b.toString())
    }

    private fun appendLongDurationIfEnabled(b: StringBuilder, metricAttribute: MetricAttribute,
                                            durationSupplier: Supplier) {
        if (!disabledMetricAttributes.contains(metricAttribute)) {
            append(b, metricAttribute.code, convertDuration(durationSupplier.get().toDouble()))
        }
    }

    private fun appendDoubleDurationIfEnabled(b: StringBuilder, metricAttribute: MetricAttribute,
                                              durationSupplier: Supplier) {
        if (!disabledMetricAttributes.contains(metricAttribute)) {
            append(b, metricAttribute.code, convertDuration(durationSupplier.get()))
        }
    }

    private fun appendLongIfEnabled(b: StringBuilder, metricAttribute: MetricAttribute,
                                    valueSupplier: Supplier) {
        if (!disabledMetricAttributes.contains(metricAttribute)) {
            append(b, metricAttribute.code, valueSupplier.get())
        }
    }

    private fun appendDoubleIfEnabled(b: StringBuilder, metricAttribute: MetricAttribute,
                                      valueSupplier: Supplier) {
        if (!disabledMetricAttributes.contains(metricAttribute)) {
            append(b, metricAttribute.code, valueSupplier.get())
        }
    }

    private fun appendCountIfEnabled(b: StringBuilder, counting: Counting) {
        if (!disabledMetricAttributes.contains(MetricAttribute.COUNT)) {
            append(b, MetricAttribute.COUNT.code, counting.count)
        }
    }

    private fun appendMetered(b: StringBuilder, meter: Metered) {
        appendRateIfEnabled(b, MetricAttribute.M1_RATE, Supplier { meter.oneMinuteRate })
        appendRateIfEnabled(b, MetricAttribute.M5_RATE, Supplier { meter.fiveMinuteRate })
        appendRateIfEnabled(b, MetricAttribute.M15_RATE, Supplier { meter.fifteenMinuteRate })
        appendRateIfEnabled(b, MetricAttribute.MEAN_RATE, Supplier { meter.meanRate })
    }

    private fun appendRateIfEnabled(b: StringBuilder, metricAttribute: MetricAttribute, rateSupplier: Supplier) {
        if (!disabledMetricAttributes.contains(metricAttribute)) {
            append(b, metricAttribute.code, convertRate(rateSupplier.get()))
        }
    }

    private fun append(b: StringBuilder, key: String, value: Long) {
        b.append(" ").append(key).append('=').append(value)
    }

    private fun append(b: StringBuilder, key: String, value: Double) {
        b.append(" ").append(key).append('=').append(String.format("%.4f", value))
    }

    private fun append(b: StringBuilder, key: String, value: String) {
        b.append(" ").append(key).append('=').append(value)
    }

    private fun append(b: StringBuilder, key: String, value: Any) {
        b.append(" ").append(key).append('=').append(value)
    }

    override fun getRateUnit(): String {
        return "events/" + super.getRateUnit()
    }

    private fun prefix(vararg components: String): String {
        return MetricRegistry.name(prefix, *components)
    }

    /* private class to allow logger configuration */
    internal abstract class LoggerProxy(protected val logger: Logger) {
        abstract fun log(marker: Marker?, format: String?)
        abstract fun isEnabled(marker: Marker?): Boolean

    }

    /* private class to allow logger configuration */
    private class DebugLoggerProxy(logger: Logger) : LoggerProxy(logger) {
        override fun log(marker: Marker?, format: String?) {
            logger.debug(marker, format)
        }

        override fun isEnabled(marker: Marker?): Boolean {
            return logger.isDebugEnabled(marker)
        }
    }

    /* private class to allow logger configuration */
    private class TraceLoggerProxy(logger: Logger) : LoggerProxy(logger) {
        override fun log(marker: Marker?, format: String?) {
            logger.trace(marker, format)
        }

        override fun isEnabled(marker: Marker?): Boolean {
            return logger.isTraceEnabled(marker)
        }
    }

    /* private class to allow logger configuration */
    private class InfoLoggerProxy(logger: Logger) : LoggerProxy(logger) {
        override fun log(marker: Marker?, format: String?) {
            logger.info(marker, format)
        }

        override fun isEnabled(marker: Marker?): Boolean {
            return logger.isInfoEnabled(marker)
        }
    }

    /* private class to allow logger configuration */
    private class WarnLoggerProxy(logger: Logger) : LoggerProxy(logger) {
        override fun log(marker: Marker?, format: String?) {
            logger.warn(marker, format)
        }

        override fun isEnabled(marker: Marker?): Boolean {
            return logger.isWarnEnabled(marker)
        }
    }

    /* private class to allow logger configuration */
    private class ErrorLoggerProxy(logger: Logger) : LoggerProxy(logger) {
        override fun log(marker: Marker?, format: String?) {
            logger.error(marker, format)
        }

        override fun isEnabled(marker: Marker?): Boolean {
            return logger.isErrorEnabled(marker)
        }
    }

    companion object {
        /**
         * Returns a new [Builder] for [Slf4jReporter].
         *
         * @param registry the registry to report
         * @return a [Builder] instance for a [Slf4jReporter]
         */
        fun forRegistry(registry: MetricRegistry): Builder {
            return Builder(registry)
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy