/******************************************************************************* * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2019) * * contact.vitam@culture.gouv.fr * * This software is a computer program whose purpose is to implement a digital archiving back-office system managing * high volumetry securely and efficiently. * * This software is governed by the CeCILL 2.1 license under French law and abiding by the rules of distribution of free * software. You can use, modify and/ or redistribute the software under the terms of the CeCILL 2.1 license as * circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info". * * As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license, * users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the * successive licensors have only limited liability. * * In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or * developing or reproducing the software by the user in light of its specific status of free software, that may mean * that it is complicated to manipulate, and that also therefore means that it is reserved for developers and * experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the * software's suitability as regards their requirements in conditions enabling the security of their systems and/or data * to be ensured and, more generally, to use and operate it in the same conditions as regards security. * * The fact that you are presently reading this means that you have had knowledge of the CeCILL 2.1 license and that you * accept its terms. *******************************************************************************/ package fr.gouv.vitam.common.metrics; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.text.DateFormat; import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.SortedMap; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.codahale.metrics.Clock; import com.codahale.metrics.ConsoleReporter; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricFilter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.ScheduledReporter; import com.codahale.metrics.Snapshot; import com.codahale.metrics.Timer; import fr.gouv.vitam.common.CharsetUtils; import fr.gouv.vitam.common.logging.VitamLogLevel; import fr.gouv.vitam.common.logging.VitamLogger; import fr.gouv.vitam.common.logging.VitamLoggerFactory; /** * A reporter which outputs measurements to a {@link PrintStream}, like {@code System.out}. */ public class LogbackReporter extends ScheduledReporter { // TODO Should this logger really be static ? (same one for ALL the metrics) perhaps not... private static final VitamLogger LOGGER = VitamLoggerFactory.getInstance(LogbackReporter.class); private static final int CONSOLE_WIDTH = 80; private static final AtomicInteger UNIQUE_RANK = new AtomicInteger(0); private final PrintStream output; private final ByteArrayOutputStream byteArrayOutput; private final Locale locale; private final Clock clock; private final DateFormat dateFormat; private final VitamLogger privateLogger; /** * Returns a new {@link Builder} for {@link ConsoleReporter}. * * @param registry the registry to report * @return a {@link Builder} instance for a {@link ConsoleReporter} */ public static Builder forRegistry(MetricRegistry registry) { return new Builder(registry); } /** * A builder for {@link ConsoleReporter} instances. Defaults to using the default locale and time zone, writing to * {@code System.out}, converting rates to events/second, converting durations to milliseconds, and not filtering * metrics. */ public static class Builder { private final MetricRegistry registry; private Locale locale; private Clock clock; private TimeZone timeZone; private TimeUnit rateUnit; private TimeUnit durationUnit; private MetricFilter filter; private VitamLogLevel logLevel; private Builder(MetricRegistry registry) { this.registry = registry; locale = Locale.getDefault(); clock = Clock.defaultClock(); timeZone = TimeZone.getDefault(); rateUnit = TimeUnit.SECONDS; durationUnit = TimeUnit.MILLISECONDS; filter = MetricFilter.ALL; logLevel = VitamLogLevel.INFO; } /** * Format numbers for the given {@link Locale}. * * @param locale a {@link Locale} * @return {@code this} */ public Builder formattedFor(Locale locale) { this.locale = locale; return this; } /** * Use the given {@link Clock} instance for the time. * * @param clock a {@link Clock} instance * @return {@code this} */ public Builder withClock(Clock clock) { this.clock = clock; return this; } /** * Use the given {@link TimeZone} for the time. * * @param timeZone a {@link TimeZone} * @return {@code this} */ public Builder formattedFor(TimeZone timeZone) { this.timeZone = timeZone; return this; } /** * Convert rates to the given time unit. * * @param rateUnit a unit of time * @return {@code this} */ public Builder convertRatesTo(TimeUnit rateUnit) { this.rateUnit = rateUnit; return this; } /** * Convert durations to the given time unit. * * @param durationUnit a unit of time * @return {@code this} */ public Builder convertDurationsTo(TimeUnit durationUnit) { this.durationUnit = durationUnit; return this; } /** * Only report metrics which match the given filter. * * @param filter a {@link MetricFilter} * @return {@code this} */ public Builder filter(MetricFilter filter) { this.filter = filter; return this; } /** * Set the LogBack log level * * @param logLevel {@link VitamLogLevel} * @return {@code this} */ public Builder logLevel(VitamLogLevel logLevel) { this.logLevel = logLevel; return this; } /** * Builds a {@link ConsoleReporter} with the given properties. * * @return a {@link ConsoleReporter} */ public LogbackReporter build() { return new LogbackReporter(registry, locale, clock, timeZone, rateUnit, durationUnit, filter, logLevel); } } private LogbackReporter(MetricRegistry registry, Locale locale, Clock clock, TimeZone timeZone, TimeUnit rateUnit, TimeUnit durationUnit, MetricFilter filter, VitamLogLevel logLevel) { super(registry, "logback-reporter", filter, rateUnit, durationUnit); byteArrayOutput = new ByteArrayOutputStream(); output = new PrintStream(byteArrayOutput); this.locale = locale; this.clock = clock; dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale); dateFormat.setTimeZone(timeZone); privateLogger = VitamLoggerFactory.getInstance("logback-reporter" + UNIQUE_RANK.incrementAndGet()); privateLogger.setLevel(logLevel); } @SuppressWarnings("rawtypes") @Override public void report(SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters, SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters, SortedMap<String, Timer> timers) { byteArrayOutput.reset(); final String dateTime = dateFormat.format(new Date(clock.getTime())); printWithBanner(dateTime, '='); output.println(); if (!gauges.isEmpty()) { printWithBanner("-- Gauges", '-'); for (final Map.Entry<String, Gauge> entry : gauges.entrySet()) { output.println(entry.getKey()); printGauge(entry); } output.println(); } if (!counters.isEmpty()) { printWithBanner("-- Counters", '-'); for (final Map.Entry<String, Counter> entry : counters.entrySet()) { output.println(entry.getKey()); printCounter(entry); } output.println(); } if (!histograms.isEmpty()) { printWithBanner("-- Histograms", '-'); for (final Map.Entry<String, Histogram> entry : histograms.entrySet()) { output.println(entry.getKey()); printHistogram(entry.getValue()); } output.println(); } if (!meters.isEmpty()) { printWithBanner("-- Meters", '-'); for (final Map.Entry<String, Meter> entry : meters.entrySet()) { output.println(entry.getKey()); printMeter(entry.getValue()); } output.println(); } if (!timers.isEmpty()) { printWithBanner("-- Timers", '-'); for (final Map.Entry<String, Timer> entry : timers.entrySet()) { output.println(entry.getKey()); printTimer(entry.getValue()); } output.println(); } output.println(); try { final String msg = byteArrayOutput.toString(CharsetUtils.UTF_8); privateLogger.log(privateLogger.getLevel(), msg); } catch (final UnsupportedEncodingException e) { LOGGER.error(e); } } private void printMeter(Meter meter) { output.printf(locale, " count = %d%n", meter.getCount()); output.printf(locale, " mean rate = %2.2f events/%s%n", convertRate(meter.getMeanRate()), getRateUnit()); output.printf(locale, " 1-minute rate = %2.2f events/%s%n", convertRate(meter.getOneMinuteRate()), getRateUnit()); output.printf(locale, " 5-minute rate = %2.2f events/%s%n", convertRate(meter.getFiveMinuteRate()), getRateUnit()); output.printf(locale, " 15-minute rate = %2.2f events/%s%n", convertRate(meter.getFifteenMinuteRate()), getRateUnit()); } private void printCounter(Map.Entry<String, Counter> entry) { output.printf(locale, " count = %d%n", entry.getValue().getCount()); } @SuppressWarnings("rawtypes") private void printGauge(Map.Entry<String, Gauge> entry) { output.printf(locale, " value = %s%n", entry.getValue().getValue()); } private void printHistogram(Histogram histogram) { output.printf(locale, " count = %d%n", histogram.getCount()); final Snapshot snapshot = histogram.getSnapshot(); output.printf(locale, " min = %d%n", snapshot.getMin()); output.printf(locale, " max = %d%n", snapshot.getMax()); output.printf(locale, " mean = %2.2f%n", snapshot.getMean()); output.printf(locale, " stddev = %2.2f%n", snapshot.getStdDev()); output.printf(locale, " median = %2.2f%n", snapshot.getMedian()); output.printf(locale, " 75%% <= %2.2f%n", snapshot.get75thPercentile()); output.printf(locale, " 95%% <= %2.2f%n", snapshot.get95thPercentile()); output.printf(locale, " 98%% <= %2.2f%n", snapshot.get98thPercentile()); output.printf(locale, " 99%% <= %2.2f%n", snapshot.get99thPercentile()); output.printf(locale, " 99.9%% <= %2.2f%n", snapshot.get999thPercentile()); } private void printTimer(Timer timer) { final Snapshot snapshot = timer.getSnapshot(); output.printf(locale, " count = %d%n", timer.getCount()); output.printf(locale, " mean rate = %2.2f calls/%s%n", convertRate(timer.getMeanRate()), getRateUnit()); output.printf(locale, " 1-minute rate = %2.2f calls/%s%n", convertRate(timer.getOneMinuteRate()), getRateUnit()); output.printf(locale, " 5-minute rate = %2.2f calls/%s%n", convertRate(timer.getFiveMinuteRate()), getRateUnit()); output.printf(locale, " 15-minute rate = %2.2f calls/%s%n", convertRate(timer.getFifteenMinuteRate()), getRateUnit()); output.printf(locale, " min = %2.2f %s%n", convertDuration(snapshot.getMin()), getDurationUnit()); output.printf(locale, " max = %2.2f %s%n", convertDuration(snapshot.getMax()), getDurationUnit()); output.printf(locale, " mean = %2.2f %s%n", convertDuration(snapshot.getMean()), getDurationUnit()); output.printf(locale, " stddev = %2.2f %s%n", convertDuration(snapshot.getStdDev()), getDurationUnit()); output.printf(locale, " median = %2.2f %s%n", convertDuration(snapshot.getMedian()), getDurationUnit()); output.printf(locale, " 75%% <= %2.2f %s%n", convertDuration(snapshot.get75thPercentile()), getDurationUnit()); output.printf(locale, " 95%% <= %2.2f %s%n", convertDuration(snapshot.get95thPercentile()), getDurationUnit()); output.printf(locale, " 98%% <= %2.2f %s%n", convertDuration(snapshot.get98thPercentile()), getDurationUnit()); output.printf(locale, " 99%% <= %2.2f %s%n", convertDuration(snapshot.get99thPercentile()), getDurationUnit()); output.printf(locale, " 99.9%% <= %2.2f %s%n", convertDuration(snapshot.get999thPercentile()), getDurationUnit()); } private void printWithBanner(String s, char c) { output.print(s); output.print(' '); for (int i = 0; i < CONSOLE_WIDTH - s.length() - 1; i++) { output.print(c); } output.println(); } }