/* * JBoss, Home of Professional Open Source. * Copyright 2009, Red Hat Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package com.redhat.gss.formatters; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.ArrayDeque; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.Locale; import java.util.Map; import org.jboss.logmanager.ExtLogRecord; import java.util.logging.Level; import java.util.logging.Formatter; import java.util.logging.LogRecord; import java.security.PrivilegedAction; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import static java.lang.Math.min; import static java.lang.Math.max; import static java.lang.System.getSecurityManager; import static java.lang.Thread.currentThread; import static java.security.AccessController.doPrivileged; import java.io.PrintWriter; import java.util.regex.Pattern; import org.jboss.logmanager.NDC; import org.jboss.logmanager.formatters.FormatStep; /** * Formatter utility methods. */ public final class Formatters { public static final String THREAD_ID = "id"; private static final boolean DEFAULT_TRUNCATE_BEGINNING = false; private static final String NEW_LINE = String.format("%n"); private static final Pattern PRECISION_INT_PATTERN = Pattern.compile("\\d+"); private Formatters() { } private static final Formatter NULL_FORMATTER = new Formatter() { public String format(final LogRecord record) { return ""; } }; /** * Get the null formatter, which outputs nothing. * * @return the null formatter */ public static Formatter nullFormatter() { return NULL_FORMATTER; } /** * Create a format step which simply emits the given string. * * @param string the string to emit * @return a format step */ public static FormatStep textFormatStep(final String string) { return new FormatStep() { public void render(final StringBuilder builder, final ExtLogRecord record) { builder.append(string); } public int estimateLength() { return string.length(); } }; } /** * Apply up to {@code count} trailing segments of the given string to the given {@code builder}. * * @param count the maximum number of segments to include * @param subject the subject string * @return the substring */ private static String applySegments(final int count, final String subject) { if (count == 0) { return subject; } int idx = subject.length() + 1; for (int i = 0; i < count; i ++) { idx = subject.lastIndexOf('.', idx - 1); if (idx == -1) { return subject; } } return subject.substring(idx + 1); } /** * Apply up to {@code precision} trailing segments of the given string to the given {@code builder}. If the * precision contains non-integer values * * @param precision the precision used to * @param subject the subject string * * @return the substring */ private static String applySegments(final String precision, final String subject) { if (precision == null) { return subject; } // Check for dots if (PRECISION_INT_PATTERN.matcher(precision).matches()) { return applySegments(Integer.parseInt(precision), subject); } // %c{1.} would be o.j.l.f.FormatStringParser // %c{1.~} would be o.~.~.~.FormatStringParser // %c{.} ....FormatStringParser final Map<Integer, Segment> segments = parsePatternSegments(precision); final Deque<String> categorySegments = parseCategorySegments(subject); final StringBuilder result = new StringBuilder(); Segment segment = null; int index = 0; while (true) { index++; if (segments.containsKey(index)) { segment = segments.get(index); } final String s = categorySegments.poll(); // Always print the last part of the category segments if (categorySegments.peek() == null) { result.append(s); break; } if (segment == null) { result.append(s).append('.'); } else { if (segment.len > 0) { if (segment.len > s.length()) { result.append(s); } else { result.append(s.substring(0, segment.len)); } } if (segment.text != null) { result.append(segment.text); } result.append('.'); } } return result.toString(); } private abstract static class JustifyingFormatStep implements FormatStep { private final boolean leftJustify; private final boolean truncateBeginning; private final int minimumWidth; private final int maximumWidth; protected JustifyingFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { if (maximumWidth != 0 && minimumWidth > maximumWidth) { throw new IllegalArgumentException("Specified minimum width may not be greater than the specified maximum width"); } if (maximumWidth < 0 || minimumWidth < 0) { throw new IllegalArgumentException("Minimum and maximum widths must not be less than zero"); } this.leftJustify = leftJustify; this.truncateBeginning = truncateBeginning; this.minimumWidth = minimumWidth; this.maximumWidth = maximumWidth == 0 ? Integer.MAX_VALUE : maximumWidth; } public void render(final StringBuilder builder, final ExtLogRecord record) { final int minimumWidth = this.minimumWidth; final int maximumWidth = this.maximumWidth; final boolean leftJustify = this.leftJustify; if (leftJustify) { // no copy necessary for left justification final int oldLen = builder.length(); renderRaw(builder, record); final int newLen = builder.length(); // if we exceeded the max width, chop it off final int writtenLen = newLen - oldLen; final int overflow = writtenLen - maximumWidth; if (overflow > 0) { if (truncateBeginning) { builder.delete(oldLen, overflow + 1); } builder.setLength(newLen - overflow); } else { final int spaces = minimumWidth - writtenLen; for (int i = 0; i < spaces; i ++) { builder.append(' '); } } } else { // only copy the data if we're right justified final StringBuilder subBuilder = new StringBuilder(); renderRaw(subBuilder, record); final int len = subBuilder.length(); if (len > maximumWidth) { if (truncateBeginning) { final int overflow = len - maximumWidth; subBuilder.delete(0, overflow); } subBuilder.setLength(maximumWidth); } else if (len < minimumWidth) { // right justify int spaces = minimumWidth - len; for (int i = 0; i < spaces; i ++) { builder.append(' '); } } builder.append(subBuilder); } } public int estimateLength() { final int maximumWidth = this.maximumWidth; final int minimumWidth = this.minimumWidth; if (maximumWidth != 0) { return min(maximumWidth, minimumWidth * 3); } else { return max(32, minimumWidth); } } public abstract void renderRaw(final StringBuilder builder, final ExtLogRecord record); } private abstract static class SegmentedFormatStep extends JustifyingFormatStep { private final int count; private final String precision; protected SegmentedFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth, final int count) { super(leftJustify, minimumWidth, truncateBeginning, maximumWidth); this.count = count; precision = null; } protected SegmentedFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth, final String precision) { super(leftJustify, minimumWidth, truncateBeginning, maximumWidth); this.count = 0; this.precision = precision; } public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { if (precision == null) { builder.append(applySegments(count, getSegmentedSubject(record))); } else { builder.append(applySegments(precision, getSegmentedSubject(record))); } } public abstract String getSegmentedSubject(final ExtLogRecord record); } /** * Create a format step which emits the logger name with the given justification rules. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @param precision the argument used for the logger name, may be {@code null} or contain dots to format the logger name * @return the format */ public static FormatStep loggerNameFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth, final String precision) { return loggerNameFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth, precision); } /** * Create a format step which emits the logger name with the given justification rules. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @param precision the argument used for the logger name, may be {@code null} or contain dots to format the * logger name * * @return the format */ public static FormatStep loggerNameFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth, final String precision) { return new SegmentedFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth, precision) { public String getSegmentedSubject(final ExtLogRecord record) { return record.getLoggerName(); } }; } /** * Create a format step which emits the source class name with the given justification rules (NOTE: call stack * introspection introduces a significant performance penalty). * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @param precision the argument used for the class name, may be {@code null} or contain dots to format the class name * @return the format step */ public static FormatStep classNameFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth, final String precision) { return classNameFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth, precision); } /** * Create a format step which emits the source class name with the given justification rules (NOTE: call stack * introspection introduces a significant performance penalty). * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @param precision the argument used for the class name, may be {@code null} or contain dots to format the * class name * * @return the format step */ public static FormatStep classNameFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth, final String precision) { return new SegmentedFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth, precision) { public String getSegmentedSubject(final ExtLogRecord record) { return record.getSourceClassName(); } }; } /** * Create a format step which emits the date of the log record with the given justification rules. * * @param timeZone the time zone to format to * @param formatString the date format string * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep dateFormatStep(final TimeZone timeZone, final String formatString, final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return dateFormatStep(timeZone, formatString, leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the date of the log record with the given justification rules. * * @param timeZone the time zone to format to * @param formatString the date format string * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * * @return the format step */ public static FormatStep dateFormatStep(final TimeZone timeZone, final String formatString, final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { private final ThreadLocal<SimpleDateFormat> holder = new ThreadLocal<SimpleDateFormat>() { protected SimpleDateFormat initialValue() { final SimpleDateFormat dateFormat = new SimpleDateFormat(formatString == null ? "yyyy-MM-dd HH:mm:ss,SSS" : formatString); dateFormat.setTimeZone(timeZone); return dateFormat; } }; public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(holder.get().format(new Date(record.getMillis()))); } }; } /** * Create a format step which emits the date of the log record with the given justification rules. * * @param formatString the date format string * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep dateFormatStep(final String formatString, final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return dateFormatStep(TimeZone.getDefault(), formatString, leftJustify, minimumWidth, maximumWidth); } /** * Create a format step which emits the source file name with the given justification rules (NOTE: call stack * introspection introduces a significant performance penalty). * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep fileNameFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return fileNameFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the source file name with the given justification rules (NOTE: call stack * introspection introduces a significant performance penalty). * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep fileNameFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(record.getSourceFileName()); } }; } /** * Create a format step which emits the complete source location information with the given justification rules * (NOTE: call stack introspection introduces a significant performance penalty). * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep locationInformationFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return locationInformationFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the complete source location information with the given justification rules * (NOTE: call stack introspection introduces a significant performance penalty). * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep locationInformationFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { final String fileName = record.getSourceFileName(); final int lineNumber = record.getSourceLineNumber(); final String className = record.getSourceClassName(); final String methodName = record.getSourceMethodName(); builder.append(className).append('.').append(methodName); builder.append('(').append(fileName); if (lineNumber != -1) { builder.append(':').append(lineNumber); } builder.append(')'); } }; } /** * Create a format step which emits the source file line number with the given justification rules (NOTE: call stack * introspection introduces a significant performance penalty). * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep lineNumberFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return lineNumberFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the source file line number with the given justification rules (NOTE: call stack * introspection introduces a significant performance penalty). * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep lineNumberFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(record.getSourceLineNumber()); } }; } /** * Create a format step which emits the formatted log message text with the given justification rules. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep messageFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return messageFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the formatted log message text with the given justification rules. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep messageFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(record.getFormattedMessage()); final Throwable t = record.getThrown(); if (t != null) { builder.append(": "); t.printStackTrace(new PrintWriter(new StringBuilderWriter(builder))); } } }; } /** * Create a format step which emits the formatted log message text (simple version, no exception traces) with the given justification rules. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep simpleMessageFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return simpleMessageFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the formatted log message text (simple version, no exception traces) with the given justification rules. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep simpleMessageFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(record.getFormattedMessage()); } }; } /** * Create a format step which emits the stack trace of an exception with the given justification rules. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @param extended {@code true} if the stack trace should attempt to include extended JAR version information * @return the format step */ public static FormatStep exceptionFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth, final boolean extended) { return exceptionFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth, extended); } /** * Create a format step which emits the stack trace of an exception with the given justification rules. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @param extended {@code true} if the stack trace should attempt to include extended JAR version information * @return the format step */ public static FormatStep exceptionFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth, final boolean extended) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { doPrivileged(new PrivilegedAction<Void>() { public Void run() { final Throwable t = record.getThrown(); if (t != null) { builder.append(": ").append(t).append(NEW_LINE); final StackTraceElement[] stackTrace = t.getStackTrace(); final Map<String, String> cache = extended ? new HashMap<String, String>() : null; if (extended) { for (StackTraceElement element : stackTrace) { renderExtended(builder, element, cache); } } else { for (StackTraceElement element : stackTrace) { renderTrivial(builder, element); } } final Throwable cause = t.getCause(); if (cause != null) { renderCause(builder, t, cause, cache, extended); } } return null; } }); } private void renderTrivial(final StringBuilder builder, final StackTraceElement element) { builder.append("\tat ").append(element).append(NEW_LINE); } private void renderExtended(final StringBuilder builder, final StackTraceElement element, final Map<String, String> cache) { builder.append("\tat ").append(element); final String className = element.getClassName(); final String cached; if ((cached = cache.get(className)) != null) { builder.append(cached).append(NEW_LINE); return; } final int dotIdx = className.lastIndexOf('.'); if (dotIdx == -1) { return; } final String packageName = className.substring(0, dotIdx); // try to guess the real Class object final Class<?> exceptionClass = guessClass(className); // now try to guess the real Package object Package exceptionPackage = null; if (exceptionClass != null) { exceptionPackage = exceptionClass.getPackage(); } if (exceptionPackage == null) try { exceptionPackage = Package.getPackage(packageName); } catch (Throwable t) { // ignore } // now try to extract the version from the Package String packageVersion = null; if (exceptionPackage != null) { try { packageVersion = exceptionPackage.getImplementationVersion(); } catch (Throwable t) { // ignore } if (packageVersion == null) try { packageVersion = exceptionPackage.getSpecificationVersion(); } catch (Throwable t) { // ignore } } // now try to find the originating resource of the class URL resource = null; final SecurityManager sm = getSecurityManager(); final String classResourceName = className.replace('.', '/') + ".class"; if (exceptionClass != null) { try { if (sm == null) { final ProtectionDomain protectionDomain = exceptionClass.getProtectionDomain(); if (protectionDomain != null) { final CodeSource codeSource = protectionDomain.getCodeSource(); if (codeSource != null) { resource = codeSource.getLocation(); } } } else { resource = doPrivileged(new PrivilegedAction<URL>() { public URL run() { final ProtectionDomain protectionDomain = exceptionClass.getProtectionDomain(); if (protectionDomain != null) { final CodeSource codeSource = protectionDomain.getCodeSource(); if (codeSource != null) { return codeSource.getLocation(); } } return null; } }); } } catch (Throwable t) { // ignore } if (resource == null) try { final ClassLoader exceptionClassLoader = exceptionClass.getClassLoader(); if (sm == null) { resource = exceptionClassLoader == null ? ClassLoader.getSystemResource(classResourceName) : exceptionClassLoader.getResource(classResourceName); } else { resource = doPrivileged(new PrivilegedAction<URL>() { public URL run() { return exceptionClassLoader == null ? ClassLoader.getSystemResource(classResourceName) : exceptionClassLoader.getResource(classResourceName); } }); } } catch (Throwable t) { // ignore } } // now try to extract the JAR name from the resource URL String jarName = getJarName(resource, classResourceName); // finally, render the mess boolean started = false; final StringBuilder tagBuilder = new StringBuilder(); if (jarName != null) { started = true; tagBuilder.append(" [").append(jarName).append(':'); } if (packageVersion != null) { if (! started) { tagBuilder.append(" [:"); started = true; } tagBuilder.append(packageVersion); } if (started) { tagBuilder.append(']'); final String tag = tagBuilder.toString(); cache.put(className, tag); builder.append(tag); } else { cache.put(className, ""); } builder.append(NEW_LINE); } private Class<?> guessClass(final String name) { try { try { final ClassLoader tccl = currentThread().getContextClassLoader(); if (tccl != null) return Class.forName(name, false, tccl); } catch (ClassNotFoundException e) { // ok, try something else... } try { return Class.forName(name); } catch (ClassNotFoundException e) { // ok, try something else... } return Class.forName(name, false, null); } catch (Throwable t) { return null; } } private void renderCause(final StringBuilder builder, final Throwable t, final Throwable cause, final Map<String, String> cache, final boolean extended) { final StackTraceElement[] causeStack = cause.getStackTrace(); final StackTraceElement[] currentStack = t.getStackTrace(); int m = causeStack.length - 1; int n = currentStack.length - 1; // Walk the stacks backwards from the end, until we find an element that is different while (m >= 0 && n >= 0 && causeStack[m].equals(currentStack[n])) { m--; n--; } int framesInCommon = causeStack.length - 1 - m; builder.append("Caused by: ").append(cause).append(NEW_LINE); if (extended) { for (int i=0; i <= m; i++) { renderExtended(builder, causeStack[i], cache); } } else { for (int i=0; i <= m; i++) { renderTrivial(builder, causeStack[i]); } } if (framesInCommon != 0) { builder.append("\t... ").append(framesInCommon).append(" more").append(NEW_LINE); } // Recurse if we have a cause final Throwable ourCause = cause.getCause(); if (ourCause != null) { renderCause(builder, cause, ourCause, cache, extended); } } }; } static String getJarName(URL resource, String classResourceName) { if (resource == null) { return null; } final String path = resource.getPath(); final String protocol = resource.getProtocol(); if ("jar".equals(protocol)) { // the last path segment before "!/" should be the JAR name final int sepIdx = path.lastIndexOf("!/"); if (sepIdx != -1) { // hit! final String firstPart = path.substring(0, sepIdx); // now find the last file separator before the JAR separator final int lsIdx = Math.max(firstPart.lastIndexOf('/'), firstPart.lastIndexOf('\\')); return firstPart.substring(lsIdx + 1); } } else if ("module".equals(protocol)) { return resource.getPath(); } // OK, that would have been too easy. Next let's just grab the last piece before the class name for (int endIdx = path.lastIndexOf(classResourceName); endIdx >= 0; endIdx--) { char ch = path.charAt(endIdx); if (ch == '/' || ch == '\\' || ch == '?') { String firstPart = path.substring(0, endIdx); int lsIdx = Math.max(firstPart.lastIndexOf('/'), firstPart.lastIndexOf('\\')); return firstPart.substring(lsIdx + 1); } } // OK, just use the last segment final int endIdx = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); return path.substring(endIdx + 1); } /** * Create a format step which emits the log message resource key (if any) with the given justification rules. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep resourceKeyFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return resourceKeyFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the log message resource key (if any) with the given justification rules. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep resourceKeyFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { final String key = record.getResourceKey(); if (key != null) builder.append(key); } }; } /** * Create a format step which emits the source method name with the given justification rules (NOTE: call stack * introspection introduces a significant performance penalty). * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep methodNameFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return methodNameFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the source method name with the given justification rules (NOTE: call stack * introspection introduces a significant performance penalty). * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep methodNameFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(record.getSourceMethodName()); } }; } private static final String separatorString; static { separatorString = doPrivileged(new PrivilegedAction<String>() { public String run() { return System.getProperty("line.separator"); } }); } /** * Create a format step which emits the host name with the given justification rules * * A singleton is used so the hostname is obtained only 1 time. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep hostNameFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(FormatGetHostName.getInstance().getHostname()); } }; } /** * Create a format step which emits the platform line separator. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep lineSeparatorFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return lineSeparatorFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the platform line separator. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep lineSeparatorFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(separatorString); } }; } /** * Create a format step which emits the log level name. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep levelFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return levelFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the log level name. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep levelFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { final Level level = record.getLevel(); builder.append(level.getName()); } }; } /** * Create a format step which emits the localized log level name. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep localizedLevelFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return localizedLevelFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the localized log level name. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep localizedLevelFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { final Level level = record.getLevel(); builder.append(level.getResourceBundleName() != null ? level.getLocalizedName() : level.getName()); } }; } /** * Create a format step which emits the number of milliseconds since the given base time. * * @param baseTime the base time as milliseconds as per {@link System#currentTimeMillis()} * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep relativeTimeFormatStep(final long baseTime, final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return relativeTimeFormatStep(baseTime, leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the number of milliseconds since the given base time. * * @param baseTime the base time as milliseconds as per {@link System#currentTimeMillis()} * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep relativeTimeFormatStep(final long baseTime, final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(record.getMillis() - baseTime); } }; } /** * Create a format step which emits the id if {@code id} is passed as the argument, otherwise the the thread name * is used. * * @param argument the argument which may be {@code id} to indicate the thread id or {@code null} to * indicate the thread name * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * * @return the format step */ public static FormatStep threadFormatStep(final String argument, final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { if (argument != null && THREAD_ID.equals(argument.toLowerCase(Locale.ROOT))) { return threadIdFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth); } return threadNameFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth); } /** * Create a format step which emits the id of the thread which originated the log record. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep threadIdFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(record.getThreadID()); } }; } /** * Create a format step which emits the name of the thread which originated the log record. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep threadNameFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return threadNameFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the name of the thread which originated the log record. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep threadNameFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { builder.append(record.getThreadName()); } }; } /** * Create a format step which emits the NDC value of the log record. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep ndcFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return ndcFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth, 0); } /** * Create a format step which emits the NDC value of the log record. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @param count the limit to the number of segments to format * @return the format step */ public static FormatStep ndcFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth, final int count) { return new SegmentedFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth, count) { public String getSegmentedSubject(final ExtLogRecord record) { return NDC.get(); } }; } /** * Create a format step which emits the MDC value associated with the given key of the log record. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep mdcFormatStep(final String key, final boolean leftJustify, final int minimumWidth, final int maximumWidth) { return mdcFormatStep(key, leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth); } /** * Create a format step which emits the MDC value associated with the given key of the log record. * * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * @return the format step */ public static FormatStep mdcFormatStep(final String key, final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { final String value = record.getMdc(key); if (value != null) { builder.append(value); } } }; } public static FormatStep formatColor(final ColorMap colors, final String color) { return new FormatStep() { public void render(final StringBuilder builder, final ExtLogRecord record) { String code = colors.getCode(color, record.getLevel()); if (code != null) { builder.append(code); } } public int estimateLength() { return 7; } }; } /** * Create a format step which emits a system property value associated with the given key. * * @param argument the argument that may be a key or key with a default value separated by a colon, cannot * be {@code null} * @param leftJustify {@code true} to left justify, {@code false} to right justify * @param minimumWidth the minimum field width, or 0 for none * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none * * @return the format step * * @throws IllegalArgumentException if the {@code argument} is {@code null} * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't * allow access to the specified system property */ public static FormatStep systemPropertyFormatStep(final String argument, final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) { if (argument == null) { throw new IllegalArgumentException("System property requires a key for the lookup"); } return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) { public void renderRaw(final StringBuilder builder, final ExtLogRecord record) { // Check for a default value final String[] parts = argument.split("(?<!\\\\):"); final String key = parts[0]; String value = System.getProperty(key); if (value == null && parts.length > 1) { value = parts[1]; } builder.append(value); } }; } static Map<Integer, Segment> parsePatternSegments(final String pattern) { final Map<Integer, Segment> segments = new HashMap<Integer, Segment>(); StringBuilder len = new StringBuilder(); StringBuilder text = new StringBuilder(); int pos = 0; // Process each character for (char c : pattern.toCharArray()) { if (c >= '0' && c <= '9') { len.append(c); } else if (c == '.') { pos++; final int i = (len.length() > 0 ? Integer.parseInt(len.toString()) : 0); segments.put(pos, new Segment(i, text.length() > 0 ? text.toString() : null)); text = new StringBuilder(); len = new StringBuilder(); } else { text.append(c); } } if (len.length() > 0 || text.length() > 0) { pos++; final int i = (len.length() > 0 ? Integer.parseInt(len.toString()) : 0); segments.put(pos, new Segment(i, text.length() > 0 ? text.toString() : null)); } return Collections.unmodifiableMap(segments); } static Deque<String> parseCategorySegments(final String category) { // The category needs to be split into segments final Deque<String> categorySegments = new ArrayDeque<String>(); StringBuilder cat = new StringBuilder(); for (char c : category.toCharArray()) { if (c == '.') { if (cat.length() > 0) { categorySegments.add(cat.toString()); cat = new StringBuilder(); } else { categorySegments.add(""); } } else { cat.append(c); } } if (cat.length() > 0) { categorySegments.add(cat.toString()); } return categorySegments; } static class Segment { final int len; final String text; Segment(final int len, final String text) { this.len = len; this.text = text; } } }