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

com.sun.mail.util.logging.CollectorFormatter Maven / Gradle / Ivy

/*
 * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2013, 2019 Jason Mehrens. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.mail.util.logging;

import static com.sun.mail.util.logging.LogManagerProperties.fromLogManager;
import java.lang.reflect.UndeclaredThrowableException;
import java.text.MessageFormat;
import java.util.Comparator;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.LogRecord;

/**
 * A LogRecord formatter that takes a sequence of LogRecords and combines them
 * into a single summary result. Formating of the head, LogRecord, and tail are
 * delegated to the wrapped formatter.
 *
 * 

* By default each CollectorFormatter is initialized using the * following LogManager configuration properties where * <formatter-name> refers to the fully qualified class name * or the fully qualified derived class name of the formatter. If properties * are not defined, or contain invalid values, then the specified default values * are used. *

    *
  • <formatter-name>.comparator name of a * {@linkplain java.util.Comparator} class used to choose the collected * LogRecord. If a comparator is specified then the max * LogRecord is chosen. If comparator is set to the string literal * null, then the last record is chosen. (defaults to * {@linkplain SeverityComparator}) * *
  • <formatter-name>.comparator.reverse a boolean * true to collect the min LogRecord or * false to collect the max LogRecord. * (defaults to false) * *
  • <formatter-name>.format the * {@linkplain java.text.MessageFormat MessageFormat} string used to format the * collected summary statistics. The arguments are explained in detail in the * {@linkplain #getTail(java.util.logging.Handler) getTail} documentation. * (defaults to {0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} * more}\n) * *
  • <formatter-name>.formatter name of a Formatter class * used to format the collected LogRecord. * (defaults to {@linkplain CompactFormatter}) * *
* * @author Jason Mehrens * @since JavaMail 1.5.2 */ public class CollectorFormatter extends Formatter { /** * Avoid depending on JMX runtime bean to get the start time. */ private static final long INIT_TIME = System.currentTimeMillis(); /** * The message format string used as the formatted output. */ private final String fmt; /** * The formatter used to format the chosen log record. */ private final Formatter formatter; /** * The comparator used to pick the log record to format. */ private final Comparator comparator; /** * The last accepted record. Synchronized access is preferred over volatile * for this class. */ private LogRecord last; /** * The number of log records that have been formatted. */ private long count; /** * The number of log produced containing at least one log record. * Only incremented when this formatter is reset. */ private long generation = 1L; /** * The number of log records that have been formatted with a thrown object. */ private long thrown; /** * The eldest log record time or eldest time possible for this instance. */ private long minMillis = INIT_TIME; /** * The newest log record time. */ private long maxMillis = Long.MIN_VALUE; /** * Creates the formatter using the LogManager defaults. * * @throws SecurityException if a security manager exists and the caller * does not have LoggingPermission("control"). * @throws UndeclaredThrowableException if there are problems loading from * the LogManager. */ public CollectorFormatter() { final String p = getClass().getName(); this.fmt = initFormat(p); this.formatter = initFormatter(p); this.comparator = initComparator(p); } /** * Creates the formatter using the given format. * * @param format the message format or null to use the LogManager default. * @throws SecurityException if a security manager exists and the caller * does not have LoggingPermission("control"). * @throws UndeclaredThrowableException if there are problems loading from * the LogManager. */ public CollectorFormatter(String format) { final String p = getClass().getName(); this.fmt = format == null ? initFormat(p) : format; this.formatter = initFormatter(p); this.comparator = initComparator(p); } /** * Creates the formatter using the given values. * * @param format the format string or null to use the LogManager default. * @param f the formatter used on the collected log record or null to * specify no formatter. * @param c the comparator used to determine which log record to format or * null to specify no comparator. * @throws SecurityException if a security manager exists and the caller * does not have LoggingPermission("control"). * @throws UndeclaredThrowableException if there are problems loading from * the LogManager. */ public CollectorFormatter(String format, Formatter f, Comparator c) { final String p = getClass().getName(); this.fmt = format == null ? initFormat(p) : format; this.formatter = f; this.comparator = c; } /** * Accumulates log records which will be used to produce the final output. * The output is generated using the {@link #getTail} method which also * resets this formatter back to its original state. * * @param record the record to store. * @return an empty string. * @throws NullPointerException if the given record is null. */ @Override public String format(final LogRecord record) { if (record == null) { throw new NullPointerException(); } boolean accepted; do { final LogRecord peek = peek(); //The self compare of the first record acts like a type check. LogRecord update = apply(peek != null ? peek : record, record); if (peek != update) { //Not identical. update.getSourceMethodName(); //Infer caller, null check. accepted = acceptAndUpdate(peek, update); } else { accepted = accept(peek, record); } } while (!accepted); return ""; } /** * Formats the collected LogRecord and summary statistics. The collected * results are reset after calling this method. The * {@linkplain java.text.MessageFormat java.text} argument indexes are * assigned to the following properties: * *
    *
  1. {@code head} the * {@linkplain Formatter#getHead(java.util.logging.Handler) head} string * returned from the target formatter and * {@linkplain #finish(java.lang.String) finished} by this formatter. *
  2. {@code formatted} the current log record * {@linkplain Formatter#format(java.util.logging.LogRecord) formatted} by * the target formatter and {@linkplain #finish(java.lang.String) finished} * by this formatter. If the formatter is null then record is formatted by * this {@linkplain #formatMessage(java.util.logging.LogRecord) formatter}. *
  3. {@code tail} the * {@linkplain Formatter#getTail(java.util.logging.Handler) tail} string * returned from the target formatter and * {@linkplain #finish(java.lang.String) finished} by this formatter. *
  4. {@code count} the total number of log records * {@linkplain #format consumed} by this formatter. *
  5. {@code remaining} the count minus one. *
  6. {@code thrown} the total number of log records * {@linkplain #format consumed} by this formatter with an assigned * {@linkplain java.util.logging.LogRecord#getThrown throwable}. *
  7. {@code normal messages} the count minus the thrown. *
  8. {@code minMillis} the eldest log record * {@linkplain java.util.logging.LogRecord#getMillis event time} * {@linkplain #format consumed} by this formatter. If the count is zero * then this is set to the previous max or approximate start time if there * was no previous max. By default this parameter is defined as a number. * The format type and format style rules from the * {@linkplain java.text.MessageFormat} should be used to convert this from * milliseconds to a date or time. *
  9. {@code maxMillis} the most recent log record * {@linkplain java.util.logging.LogRecord#getMillis event time} * {@linkplain #format consumed} by this formatter. If the count is zero * then this is set to the {@linkplain System#currentTimeMillis() current time}. * By default this parameter is defined as a number. The format type and * format style rules from the {@linkplain java.text.MessageFormat} should * be used to convert this from milliseconds to a date or time. *
  10. {@code elapsed} the elapsed time in milliseconds between the * {@code maxMillis} and {@code minMillis}. *
  11. {@code startTime} the approximate start time in milliseconds. By * default this parameter is defined as a number. The format type and format * style rules from the {@linkplain java.text.MessageFormat} should be used * to convert this from milliseconds to a date or time. *
  12. {@code currentTime} the * {@linkplain System#currentTimeMillis() current time} in milliseconds. By * default this parameter is defined as a number. The format type and format * style rules from the {@linkplain java.text.MessageFormat} should be used * to convert this from milliseconds to a date or time. *
  13. {@code uptime} the elapsed time in milliseconds between the * {@code currentTime} and {@code startTime}. *
  14. {@code generation} the number times this method produced output with * at least one {@linkplain #format consumed} log record. This can be used * to track the number of complete reports this formatter has produced. *
* *

* Some example formats:
*

    *
  • {@code com.sun.mail.util.logging.CollectorFormatter.format={0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n} *

    * This prints the head ({@code {0}}), format ({@code {1}}), and tail * ({@code {2}}) from the target formatter followed by the number of * remaining ({@code {4}}) log records consumed by this formatter if there * are any remaining records. *

         * Encoding failed.|NullPointerException: null String.getBytes(:913)... 3 more
         * 
    *
  • {@code com.sun.mail.util.logging.CollectorFormatter.format=These {3} messages occurred between\n{7,date,EEE, MMM dd HH:mm:ss:S ZZZ yyyy} and {8,time,EEE, MMM dd HH:mm:ss:S ZZZ yyyy}\n} *

    * This prints the count ({@code {3}}) followed by the date and time of the * eldest log record ({@code {7}}) and the date and time of the most recent * log record ({@code {8}}). *

         * These 292 messages occurred between
         * Tue, Jul 21 14:11:42:449 -0500 2009 and Fri, Nov 20 07:29:24:0 -0600 2009
         * 
    *
  • {@code com.sun.mail.util.logging.CollectorFormatter.format=These {3} messages occurred between {9,choice,86400000#{7,date} {7,time} and {8,time}|86400000<{7,date} and {8,date}}\n} *

    * This prints the count ({@code {3}}) and then chooses the format based on * the elapsed time ({@code {9}}). If the elapsed time is less than one day * then the eldest log record ({@code {7}}) date and time is formatted * followed by just the time of the most recent log record ({@code {8}}. * Otherwise, the just the date of the eldest log record ({@code {7}}) and * just the date of most recent log record ({@code {8}} is formatted. *

         * These 73 messages occurred between Jul 21, 2009 2:11:42 PM and 2:13:32 PM
         *
         * These 116 messages occurred between Jul 21, 2009 and Aug 20, 2009
         * 
    *
  • {@code com.sun.mail.util.logging.CollectorFormatter.format={13} alert reports since {10,date}.\n} *

    * This prints the generation ({@code {13}}) followed by the start time * ({@code {10}}) formatted as a date. *

         * 4,320 alert reports since Jul 21, 2012.
         * 
    *
* * @param h the handler or null. * @return the output string. */ @Override public String getTail(final Handler h) { super.getTail(h); //Be forward compatible with super.getHead. return formatRecord(h, true); } /** * Formats the collected LogRecord and summary statistics. The LogRecord and * summary statistics are not changed by calling this method. * * @return the current record formatted or the default toString. * @see #getTail(java.util.logging.Handler) */ @Override public String toString() { String result; try { result = formatRecord((Handler) null, false); } catch (final RuntimeException ignore) { result = super.toString(); } return result; } /** * Used to choose the collected LogRecord. This implementation returns the * greater of two LogRecords. * * @param t the current record. * @param u the record that could replace the current. * @return the greater of the given log records. * @throws NullPointerException may occur if either record is null. */ protected LogRecord apply(final LogRecord t, final LogRecord u) { if (t == null || u == null) { throw new NullPointerException(); } if (comparator != null) { return comparator.compare(t, u) >= 0 ? t : u; } else { return u; } } /** * Updates the summary statistics only if the expected record matches the * last record. The update record is not stored. * * @param e the LogRecord that is expected. * @param u the LogRecord used to collect statistics. * @return true if the last record was the expected record. * @throws NullPointerException if the update record is null. */ private synchronized boolean accept(final LogRecord e, final LogRecord u) { /** * LogRecord methods must be called before the check of the last stored * record to guard against subclasses of LogRecord that might attempt to * reset the state by triggering a call to getTail. */ final long millis = u.getMillis(); //Null check. final Throwable ex = u.getThrown(); if (last == e) { //Only if the exact same reference. if (++count != 1L) { minMillis = Math.min(minMillis, millis); } else { //Show single records as instant and not a time period. minMillis = millis; } maxMillis = Math.max(maxMillis, millis); if (ex != null) { ++thrown; } return true; } else { return false; } } /** * Resets all of the collected summary statistics including the LogRecord. * @param min the current min milliseconds. */ private synchronized void reset(final long min) { if (last != null) { last = null; ++generation; } count = 0L; thrown = 0L; minMillis = min; maxMillis = Long.MIN_VALUE; } /** * Formats the given record with the head and tail. * * @param h the Handler or null. * @param reset true if the summary statistics and LogRecord should be reset * back to initial values. * @return the formatted string. * @see #getTail(java.util.logging.Handler) */ private String formatRecord(final Handler h, final boolean reset) { final LogRecord record; final long c; final long t; final long g; long msl; long msh; long now; synchronized (this) { record = last; c = count; g = generation; t = thrown; msl = minMillis; msh = maxMillis; now = System.currentTimeMillis(); if (c == 0L) { msh = now; } if (reset) { //BUG ID 6351685 reset(msh); } } final String head; final String msg; final String tail; final Formatter f = this.formatter; if (f != null) { synchronized (f) { head = f.getHead(h); msg = record != null ? f.format(record) : ""; tail = f.getTail(h); } } else { head = ""; msg = record != null ? formatMessage(record) : ""; tail = ""; } Locale l = null; if (record != null) { ResourceBundle rb = record.getResourceBundle(); l = rb == null ? null : rb.getLocale(); } final MessageFormat mf; if (l == null) { //BUG ID 8039165 mf = new MessageFormat(fmt); } else { mf = new MessageFormat(fmt, l); } /** * These arguments are described in the getTail documentation. */ return mf.format(new Object[]{finish(head), finish(msg), finish(tail), c, (c - 1L), t, (c - t), msl, msh, (msh - msl), INIT_TIME, now, (now - INIT_TIME), g}); } /** * Applied to the head, format, and tail returned by the target formatter. * This implementation trims all input strings. * * @param s the string to transform. * @return the transformed string. * @throws NullPointerException if the given string is null. */ protected String finish(String s) { return s.trim(); } /** * Peek at the current log record. * * @return null or the current log record. */ private synchronized LogRecord peek() { return this.last; } /** * Updates the summary statistics and stores given LogRecord if the expected * record matches the current record. * * @param e the expected record. * @param u the update record. * @return true if the update was performed. * @throws NullPointerException if the update record is null. */ private synchronized boolean acceptAndUpdate(LogRecord e, LogRecord u) { if (accept(e, u)) { this.last = u; return true; } else { return false; } } /** * Gets the message format string from the LogManager or creates the default * message format string. * * @param p the class name prefix. * @return the format string. * @throws NullPointerException if the given argument is null. */ private String initFormat(final String p) { String v = fromLogManager(p.concat(".format")); if (v == null || v.length() == 0) { v = "{0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n"; } return v; } /** * Gets and creates the formatter from the LogManager or creates the default * formatter. * * @param p the class name prefix. * @return the formatter. * @throws NullPointerException if the given argument is null. * @throws UndeclaredThrowableException if the formatter can not be created. */ private Formatter initFormatter(final String p) { Formatter f; String v = fromLogManager(p.concat(".formatter")); if (v != null && v.length() != 0) { if (!"null".equalsIgnoreCase(v)) { try { f = LogManagerProperties.newFormatter(v); } catch (final RuntimeException re) { throw re; } catch (final Exception e) { throw new UndeclaredThrowableException(e); } } else { f = null; } } else { //Don't force the byte code verifier to load the formatter. f = Formatter.class.cast(new CompactFormatter()); } return f; } /** * Gets and creates the comparator from the LogManager or returns the * default comparator. * * @param p the class name prefix. * @return the comparator or null. * @throws IllegalArgumentException if it was specified that the comparator * should be reversed but no initial comparator was specified. * @throws NullPointerException if the given argument is null. * @throws UndeclaredThrowableException if the comparator can not be * created. */ @SuppressWarnings("unchecked") private Comparator initComparator(final String p) { Comparator c; final String name = fromLogManager(p.concat(".comparator")); final String reverse = fromLogManager(p.concat(".comparator.reverse")); try { if (name != null && name.length() != 0) { if (!"null".equalsIgnoreCase(name)) { c = LogManagerProperties.newComparator(name); if (Boolean.parseBoolean(reverse)) { assert c != null; c = LogManagerProperties.reverseOrder(c); } } else { if (reverse != null) { throw new IllegalArgumentException( "No comparator to reverse."); } else { c = null; //No ordering. } } } else { if (reverse != null) { throw new IllegalArgumentException( "No comparator to reverse."); } else { //Don't force the byte code verifier to load the comparator. c = Comparator.class.cast(SeverityComparator.getInstance()); } } } catch (final RuntimeException re) { throw re; //Avoid catch all. } catch (final Exception e) { throw new UndeclaredThrowableException(e); } return c; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy