/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2013-2016 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013-2016 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://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/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 packager/legal/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. *

* * @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 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 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:
*

* * @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 removes any fully qualified throwable class names from the message * cause chain. * * @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) { return t != null ? replaceClassName(apply(t).getMessage(), t) : ""; } /** * 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 = formatToString(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) { Throwable t = record.getThrown(); if (t != null) { return formatToString(t); } else { return ""; } } /** * Gets the simple class name of the reduced throwable and message of * the reduced throwable. * * @param t the given throwable. * @return the formatted throwable. * @since JavaMail 1.5.4 * @throws NullPointerException if given throwable is null. */ private String formatToString(Throwable t) { return simpleClassName(apply(t).getClass()) + ": " + formatMessage(t); } /** * 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(LogRecord record) { String site = ""; final Throwable t = record.getThrown(); if (t != null) { final Throwable root = apply(t); site = findAndFormat(root.getStackTrace()); if (isNullOrSpaces(site)) { int limit = 0; for (Throwable c = t; c != null; c = c.getCause()) { site = findAndFormat(c.getStackTrace()); if (!isNullOrSpaces(site)) { break; } //Deal with excessive cause chains //and cyclic throwables. if (++limit == (1 << 16)) { break; //Give up. } } } } 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(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(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(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 ignore) { } catch (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 ignore) { } catch (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. * * @param name the fully qualified class name or null. * @return the simple class name or null. */ private static String simpleClassName(String name) { if (name != null) { final int index = name.lastIndexOf('.'); name = index > -1 ? name.substring(index + 1) : name; } 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(); } } }