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

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

There is a newer version: 1.6.2
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2013-2017 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2013-2017 Jason Mehrens. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.mail.util.logging;

import java.util.*;
import java.util.logging.LogRecord;

/**
 * A plain text formatter that can produce fixed width output. By default this
 * formatter will produce output no greater than 160 characters wide plus the
 * separator and newline characters. Only specified fields support an
 * {@linkplain #toAlternate(java.lang.String) alternate} fixed width format.
 * 

* By default each CompactFormatter 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>.format - the {@linkplain java.util.Formatter * format} string used to transform the output. The format string can be * used to fix the output size. (defaults to %7$#.160s%n)
  • *
* * @author Jason Mehrens * @since JavaMail 1.5.2 */ public class CompactFormatter extends java.util.logging.Formatter { /** * Load any declared classes to workaround GLASSFISH-21258. */ static { loadDeclaredClasses(); } /** * Used to load declared classes encase class loader doesn't allow loading * during JVM termination. This method is used with unit testing. * * @return an array of classes never null. */ private static Class[] loadDeclaredClasses() { return new Class[]{Alternate.class}; } /** * Holds the java.util.Formatter pattern. */ private final String fmt; /** * Creates an instance with a default format pattern. */ public CompactFormatter() { String p = getClass().getName(); this.fmt = initFormat(p); } /** * Creates an instance with the given format pattern. * * @param format the {@linkplain java.util.Formatter pattern} or null to use * the LogManager default. The arguments are described in the * {@linkplain #format(java.util.logging.LogRecord) format} method. */ public CompactFormatter(final String format) { String p = getClass().getName(); this.fmt = format == null ? initFormat(p) : format; } /** * Format the given log record and returns the formatted string. The * {@linkplain java.util.Formatter#format(java.lang.String, java.lang.Object...) * java.util} argument indexes are assigned to the following properties: * *
    *
  1. {@code format} - the {@linkplain java.util.Formatter * java.util.Formatter} format string specified in the * <formatter-name>.format property or the format that was given when * this formatter was created.
  2. *
  3. {@code date} - if the log record supports nanoseconds then a * ZonedDateTime object representing the event time of the log record in the * system time zone. Otherwise, a {@linkplain Date} object representing * {@linkplain LogRecord#getMillis event time} of the log record.
  4. *
  5. {@code source} - a string representing the caller, if available; * otherwise, the logger's name.
  6. *
  7. {@code logger} - the logger's * {@linkplain Class#getSimpleName() simple} * {@linkplain LogRecord#getLoggerName() name}.
  8. *
  9. {@code level} - the * {@linkplain java.util.logging.Level#getLocalizedName log level}.
  10. *
  11. {@code message} - the formatted log message returned from the * {@linkplain #formatMessage(LogRecord)} method.
  12. *
  13. {@code thrown} - a string representing the * {@linkplain LogRecord#getThrown throwable} associated with the log record * and a relevant stack trace element if available; otherwise, an empty * string is used.
  14. *
  15. {@code message|thrown} The message and the thrown properties joined * as one parameter. This parameter supports * {@linkplain #toAlternate(java.lang.String) alternate} form.
  16. *
  17. {@code thrown|message} The thrown and message properties joined as * one parameter. This parameter supports * {@linkplain #toAlternate(java.lang.String) alternate} form.
  18. *
  19. {@code sequence} the * {@linkplain LogRecord#getSequenceNumber() sequence number} if the given * log record.
  20. *
  21. {@code thread id} the {@linkplain LogRecord#getThreadID() thread id} * of the given log record. By default this is formatted as a {@code long} * by an unsigned conversion.
  22. *
  23. {@code error} the throwable * {@linkplain Class#getSimpleName() simple class name} and * {@linkplain #formatError(LogRecord) error message} without any stack * trace.
  24. *
  25. {@code message|error} The message and error properties joined as one * parameter. This parameter supports * {@linkplain #toAlternate(java.lang.String) alternate} form.
  26. *
  27. {@code error|message} The error and message properties joined as one * parameter. This parameter supports * {@linkplain #toAlternate(java.lang.String) alternate} form.
  28. *
  29. {@code backtrace} only the * {@linkplain #formatBackTrace(LogRecord) stack trace} of the given * throwable.
  30. *
  31. {@code bundlename} the resource bundle * {@linkplain LogRecord#getResourceBundleName() name} of the given log * record.
  32. *
  33. {@code key} the {@linkplain LogRecord#getMessage() raw message} * before localization or formatting.
  34. *
* *

* Some example formats:
*

    *
  • {@code com.sun.mail.util.logging.CompactFormatter.format=%7$#.160s%n} *

    * This prints only 160 characters of the message|thrown ({@code 7$}) using * the {@linkplain #toAlternate(java.lang.String) alternate} form. The * separator is not included as part of the total width. *

         * Encoding failed.|NullPointerException: null String.getBytes(:913)
         * 
    * *
  • {@code com.sun.mail.util.logging.CompactFormatter.format=%7$#.20s%n} *

    * This prints only 20 characters of the message|thrown ({@code 7$}) using * the {@linkplain #toAlternate(java.lang.String) alternate} form. This will * perform a weighted truncation of both the message and thrown properties * of the log record. The separator is not included as part of the total * width. *

         * Encoding|NullPointerE
         * 
    * *
  • {@code com.sun.mail.util.logging.CompactFormatter.format=%1$tc %2$s%n%4$s: %5$s%6$s%n} *

    * This prints the timestamp ({@code 1$}) and the source ({@code 2$}) on the * first line. The second line is the log level ({@code 4$}), log message * ({@code 5$}), and the throwable with a relevant stack trace element * ({@code 6$}) if one is available. *

         * Fri Nov 20 07:29:24 CST 2009 MyClass fatal
         * SEVERE: Encoding failed.NullPointerException: null String.getBytes(:913)
         * 
    * *
  • {@code com.sun.mail.util.logging.CompactFormatter.format=%4$s: %12$#.160s%n} *

    * This prints the log level ({@code 4$}) and only 160 characters of the * message|error ({@code 12$}) using the alternate form. *

         * SEVERE: Unable to send notification.|SocketException: Permission denied: connect
         * 
    * *
  • {@code com.sun.mail.util.logging.CompactFormatter.format=[%9$d][%1$tT][%10$d][%2$s] %5$s%n%6$s%n} *

    * This prints the sequence ({@code 9$}), event time ({@code 1$}) as 24 hour * time, thread id ({@code 10$}), source ({@code 2$}), log message * ({@code 5$}), and the throwable with back trace ({@code 6$}). *

         * [125][14:11:42][38][MyClass fatal] Unable to send notification.
         * SocketException: Permission denied: connect SMTPTransport.openServer(:1949)
         * 
    * *
* * @param record the log record to format. * @return the formatted record. * @throws NullPointerException if the given record is null. */ @Override public String format(final LogRecord record) { //LogRecord is mutable so define local vars. ResourceBundle rb = record.getResourceBundle(); Locale l = rb == null ? null : rb.getLocale(); String msg = formatMessage(record); String thrown = formatThrown(record); String err = formatError(record); Object[] params = { formatZonedDateTime(record), formatSource(record), formatLoggerName(record), formatLevel(record), msg, thrown, new Alternate(msg, thrown), new Alternate(thrown, msg), record.getSequenceNumber(), formatThreadID(record), err, new Alternate(msg, err), new Alternate(err, msg), formatBackTrace(record), record.getResourceBundleName(), record.getMessage()}; if (l == null) { //BUG ID 6282094 return String.format(fmt, params); } else { return String.format(l, fmt, params); } } /** * Formats message for the log record. This method removes any fully * qualified throwable class names from the message. * * @param record the log record. * @return the formatted message string. */ @Override public String formatMessage(final LogRecord record) { String msg = super.formatMessage(record); msg = replaceClassName(msg, record.getThrown()); msg = replaceClassName(msg, record.getParameters()); return msg; } /** * Formats the message from the thrown property of the log record. This * method replaces fully qualified throwable class names from the message * cause chain with simple class names. * * @param t the throwable to format or null. * @return the empty string if null was given or the formatted message * string from the throwable which may be null. */ public String formatMessage(final Throwable t) { String r; if (t != null) { final Throwable apply = apply(t); final String m = apply.getLocalizedMessage(); final String s = apply.toString(); final String sn = simpleClassName(apply.getClass()); if (!isNullOrSpaces(m)) { if (s.contains(m)) { if (s.startsWith(apply.getClass().getName()) || s.startsWith(sn)) { r = replaceClassName(m, t); } else { r = replaceClassName(simpleClassName(s), t); } } else { r = replaceClassName(simpleClassName(s) + ": " + m, t); } } else { r = replaceClassName(simpleClassName(s), t); } if (!r.contains(sn)) { r = sn + ": " + r; } } else { r = ""; } return r; } /** * Formats the level property of the given log record. * * @param record the record. * @return the formatted logger name. * @throws NullPointerException if the given record is null. */ public String formatLevel(final LogRecord record) { return record.getLevel().getLocalizedName(); } /** * Formats the source from the given log record. * * @param record the record. * @return the formatted source of the log record. * @throws NullPointerException if the given record is null. */ public String formatSource(final LogRecord record) { String source = record.getSourceClassName(); if (source != null) { if (record.getSourceMethodName() != null) { source = simpleClassName(source) + " " + record.getSourceMethodName(); } else { source = simpleClassName(source); } } else { source = simpleClassName(record.getLoggerName()); } return source; } /** * Formats the logger name property of the given log record. * * @param record the record. * @return the formatted logger name. * @throws NullPointerException if the given record is null. */ public String formatLoggerName(final LogRecord record) { return simpleClassName(record.getLoggerName()); } /** * Formats the thread id property of the given log record. By default this * is formatted as a {@code long} by an unsigned conversion. * * @param record the record. * @return the formatted thread id as a number. * @throws NullPointerException if the given record is null. * @since JavaMail 1.5.4 */ public Number formatThreadID(final LogRecord record) { /** * Thread.getID is defined as long and LogRecord.getThreadID is defined * as int. Convert to unsigned as a means to better map the two types of * thread identifiers. */ return (((long) record.getThreadID()) & 0xffffffffL); } /** * Formats the thrown property of a LogRecord. The returned string will * contain a throwable message with a back trace. * * @param record the record. * @return empty string if nothing was thrown or formatted string. * @throws NullPointerException if the given record is null. * @see #apply(java.lang.Throwable) * @see #formatBackTrace(java.util.logging.LogRecord) */ public String formatThrown(final LogRecord record) { String msg; final Throwable t = record.getThrown(); if (t != null) { String site = formatBackTrace(record); msg = formatMessage(t) + (isNullOrSpaces(site) ? "" : ' ' + site); } else { msg = ""; } return msg; } /** * Formats the thrown property of a LogRecord as an error message. The * returned string will not contain a back trace. * * @param record the record. * @return empty string if nothing was thrown or formatted string. * @throws NullPointerException if the given record is null. * @see Throwable#toString() * @see #apply(java.lang.Throwable) * @see #formatMessage(java.lang.Throwable) * @since JavaMail 1.5.4 */ public String formatError(final LogRecord record) { return formatMessage(record.getThrown()); } /** * Formats the back trace for the given log record. * * @param record the log record to format. * @return the formatted back trace. * @throws NullPointerException if the given record is null. * @see #apply(java.lang.Throwable) * @see #formatThrown(java.util.logging.LogRecord) * @see #ignore(java.lang.StackTraceElement) */ public String formatBackTrace(final LogRecord record) { String site = ""; final Throwable t = record.getThrown(); if (t != null) { final Throwable root = apply(t); StackTraceElement[] trace = root.getStackTrace(); site = findAndFormat(trace); if (isNullOrSpaces(site)) { int limit = 0; for (Throwable c = t; c != null; c = c.getCause()) { StackTraceElement[] ste = c.getStackTrace(); site = findAndFormat(ste); if (!isNullOrSpaces(site)) { break; } else { if (trace.length == 0) { trace = ste; } } //Deal with excessive cause chains //and cyclic throwables. if (++limit == (1 << 16)) { break; //Give up. } } //Punt. if (isNullOrSpaces(site) && trace.length != 0) { site = formatStackTraceElement(trace[0]); } } } return site; } /** * Finds and formats the first stack frame of interest. * * @param trace the fill stack to examine. * @return a String that best describes the call site. * @throws NullPointerException if stack trace element array is null. */ private String findAndFormat(final StackTraceElement[] trace) { String site = ""; for (StackTraceElement s : trace) { if (!ignore(s)) { site = formatStackTraceElement(s); break; } } //Check if all code was compiled with no debugging info. if (isNullOrSpaces(site)) { for (StackTraceElement s : trace) { if (!defaultIgnore(s)) { site = formatStackTraceElement(s); break; } } } return site; } /** * Formats a stack trace element into a simple call site. * * @param s the stack trace element to format. * @return the formatted stack trace element. * @throws NullPointerException if stack trace element is null. * @see #formatThrown(java.util.logging.LogRecord) */ private String formatStackTraceElement(final StackTraceElement s) { String v = simpleClassName(s.getClassName()); String result; if (v != null) { result = s.toString().replace(s.getClassName(), v); } else { result = s.toString(); } //If the class name contains the simple file name then remove file name. v = simpleFileName(s.getFileName()); if (v != null && result.startsWith(v)) { result = result.replace(s.getFileName(), ""); } return result; } /** * Chooses a single throwable from the cause chain that will be formatted. * This implementation chooses the throwable that best describes the chain. * Subclasses can override this method to choose an alternate throwable for * formatting. * * @param t the throwable from the log record. * @return the chosen throwable or null only if the given argument is null. * @see #formatThrown(java.util.logging.LogRecord) */ protected Throwable apply(final Throwable t) { return SeverityComparator.getInstance().apply(t); } /** * Determines if a stack frame should be ignored as the cause of an error. * * @param s the stack trace element. * @return true if this frame should be ignored. * @see #formatThrown(java.util.logging.LogRecord) */ protected boolean ignore(final StackTraceElement s) { return isUnknown(s) || defaultIgnore(s); } /** * Defines the alternate format. This implementation removes all control * characters from the given string. * * @param s any string or null. * @return null if the argument was null otherwise, an alternate string. */ protected String toAlternate(final String s) { return s != null ? s.replaceAll("[\\x00-\\x1F\\x7F]+", "") : null; } /** * Gets the zoned date time from the given log record. * * @param record the current log record. * @return a zoned date time or a legacy date object. * @throws NullPointerException if the given record is null. * @since JavaMail 1.5.6 */ private Comparable formatZonedDateTime(final LogRecord record) { Comparable zdt = LogManagerProperties.getZonedDateTime(record); if (zdt == null) { zdt = new java.util.Date(record.getMillis()); } return zdt; } /** * Determines if a stack frame should be ignored as the cause of an error. * This does not check for unknown line numbers because code can be compiled * without debugging info. * * @param s the stack trace element. * @return true if this frame should be ignored. */ private boolean defaultIgnore(final StackTraceElement s) { return isSynthetic(s) || isStaticUtility(s) || isReflection(s); } /** * Determines if a stack frame is for a static utility class. * * @param s the stack trace element. * @return true if this frame should be ignored. */ private boolean isStaticUtility(final StackTraceElement s) { try { return LogManagerProperties.isStaticUtilityClass(s.getClassName()); } catch (RuntimeException ignore) { } catch (Exception | LinkageError ignore) { } final String cn = s.getClassName(); return (cn.endsWith("s") && !cn.endsWith("es")) || cn.contains("Util") || cn.endsWith("Throwables"); } /** * Determines if a stack trace element is for a synthetic method. * * @param s the stack trace element. * @return true if synthetic. * @throws NullPointerException if stack trace element is null. */ private boolean isSynthetic(final StackTraceElement s) { return s.getMethodName().indexOf('$') > -1; } /** * Determines if a stack trace element has an unknown line number or a * native line number. * * @param s the stack trace element. * @return true if the line number is unknown. * @throws NullPointerException if stack trace element is null. */ private boolean isUnknown(final StackTraceElement s) { return s.getLineNumber() < 0; } /** * Determines if a stack trace element represents a reflection frame. * * @param s the stack trace element. * @return true if the line number is unknown. * @throws NullPointerException if stack trace element is null. */ private boolean isReflection(final StackTraceElement s) { try { return LogManagerProperties.isReflectionClass(s.getClassName()); } catch (RuntimeException ignore) { } catch (Exception | LinkageError ignore) { } return s.getClassName().startsWith("java.lang.reflect.") || s.getClassName().startsWith("sun.reflect."); } /** * Creates the format pattern for this formatter. * * @param p the class name prefix. * @return the java.util.Formatter format string. * @throws NullPointerException if the given class name is null. */ private String initFormat(final String p) { String v = LogManagerProperties.fromLogManager(p.concat(".format")); if (isNullOrSpaces(v)) { v = "%7$#.160s%n"; //160 chars split between message and thrown. } return v; } /** * Searches the given message for all instances fully qualified class name * with simple class name based off of the types contained in the given * parameter array. * * @param msg the message. * @param t the throwable cause chain to search or null. * @return the modified message string. */ private static String replaceClassName(String msg, Throwable t) { if (!isNullOrSpaces(msg)) { int limit = 0; for (Throwable c = t; c != null; c = c.getCause()) { final Class k = c.getClass(); msg = msg.replace(k.getName(), simpleClassName(k)); //Deal with excessive cause chains and cyclic throwables. if (++limit == (1 << 16)) { break; //Give up. } } } return msg; } /** * Searches the given message for all instances fully qualified class name * with simple class name based off of the types contained in the given * parameter array. * * @param msg the message or null. * @param p the parameter array or null. * @return the modified message string. */ private static String replaceClassName(String msg, Object[] p) { if (!isNullOrSpaces(msg) && p != null) { for (Object o : p) { if (o != null) { final Class k = o.getClass(); msg = msg.replace(k.getName(), simpleClassName(k)); } } } return msg; } /** * Gets the simple class name from the given class. This is a workaround for * BUG ID JDK-8057919. * * @param k the class object. * @return the simple class name or null. * @since JavaMail 1.5.3 */ private static String simpleClassName(final Class k) { try { return k.getSimpleName(); } catch (final InternalError JDK8057919) { } return simpleClassName(k.getName()); } /** * Converts a fully qualified class name to a simple class name. If the * leading part of the given string is not a legal class name then the given * string is returned. * * @param name the fully qualified class name prefix or null. * @return the simple class name or given input. */ private static String simpleClassName(String name) { if (name != null) { int cursor = 0; int sign = -1; int dot = -1; for (int c, prev = dot; cursor < name.length(); cursor += Character.charCount(c)) { c = name.codePointAt(cursor); if (!Character.isJavaIdentifierPart(c)) { if (c == ((int) '.')) { if ((dot + 1) != cursor && (dot + 1) != sign) { prev = dot; dot = cursor; } else { return name; } } else { if ((dot + 1) == cursor) { dot = prev; } break; } } else { if (c == ((int) '$')) { sign = cursor; } } } if (dot > -1 && ++dot < cursor && ++sign < cursor) { name = name.substring(sign > dot ? sign : dot); } } return name; } /** * Converts a file name with an extension to a file name without an * extension. * * @param name the full file name or null. * @return the simple file name or null. */ private static String simpleFileName(String name) { if (name != null) { final int index = name.lastIndexOf('.'); name = index > -1 ? name.substring(0, index) : name; } return name; } /** * Determines is the given string is null or spaces. * * @param s the string or null. * @return true if null or spaces. */ private static boolean isNullOrSpaces(final String s) { return s == null || s.trim().length() == 0; } /** * Used to format two arguments as fixed length message. */ private class Alternate implements java.util.Formattable { /** * The left side of the output. */ private final String left; /** * The right side of the output. */ private final String right; /** * Creates an alternate output. * * @param left the left side or null. * @param right the right side or null. */ Alternate(final String left, final String right) { this.left = String.valueOf(left); this.right = String.valueOf(right); } @SuppressWarnings("override") //JDK-6954234 public void formatTo(java.util.Formatter formatter, int flags, int width, int precision) { String l = left; String r = right; if ((flags & java.util.FormattableFlags.UPPERCASE) == java.util.FormattableFlags.UPPERCASE) { l = l.toUpperCase(formatter.locale()); r = r.toUpperCase(formatter.locale()); } if ((flags & java.util.FormattableFlags.ALTERNATE) == java.util.FormattableFlags.ALTERNATE) { l = toAlternate(l); r = toAlternate(r); } if (precision <= 0) { precision = Integer.MAX_VALUE; } int fence = Math.min(l.length(), precision); if (fence > (precision >> 1)) { fence = Math.max(fence - r.length(), fence >> 1); } if (fence > 0) { if (fence > l.length() && Character.isHighSurrogate(l.charAt(fence - 1))) { --fence; } l = l.substring(0, fence); } r = r.substring(0, Math.min(precision - fence, r.length())); if (width > 0) { final int half = width >> 1; if (l.length() < half) { l = pad(flags, l, half); } if (r.length() < half) { r = pad(flags, r, half); } } Object[] empty = Collections.emptySet().toArray(); formatter.format(l, empty); if (l.length() != 0 && r.length() != 0) { formatter.format("|", empty); } formatter.format(r, empty); } /** * Pad the given input string. * * @param flags the formatter flags. * @param s the string to pad. * @param length the final string length. * @return the padded string. */ private String pad(int flags, String s, int length) { final int padding = length - s.length(); final StringBuilder b = new StringBuilder(length); if ((flags & java.util.FormattableFlags.LEFT_JUSTIFY) == java.util.FormattableFlags.LEFT_JUSTIFY) { for (int i = 0; i < padding; ++i) { b.append('\u0020'); } b.append(s); } else { b.append(s); for (int i = 0; i < padding; ++i) { b.append('\u0020'); } } return b.toString(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy