001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 package org.apache.logging.log4j.core.pattern; 018 019 import java.io.PrintWriter; 020 import java.io.StringWriter; 021 022 import org.apache.logging.log4j.core.LogEvent; 023 import org.apache.logging.log4j.core.config.plugins.Plugin; 024 import org.apache.logging.log4j.core.impl.ThrowableFormatOptions; 025 import org.apache.logging.log4j.core.util.Constants; 026 import org.apache.logging.log4j.util.Strings; 027 028 029 /** 030 * Outputs the Throwable portion of the LoggingEvent as a full stacktrace 031 * unless this converter's option is 'short', where it just outputs the first line of the trace, or if 032 * the number of lines to print is explicitly specified. 033 */ 034 @Plugin(name = "ThrowablePatternConverter", category = PatternConverter.CATEGORY) 035 @ConverterKeys({ "ex", "throwable", "exception" }) 036 public class ThrowablePatternConverter extends LogEventPatternConverter { 037 038 private String rawOption; 039 040 /** 041 * The number of lines to write. 042 */ 043 protected final ThrowableFormatOptions options; 044 045 /** 046 * Constructor. 047 * @param name Name of converter. 048 * @param style CSS style for output. 049 * @param options options, may be null. 050 */ 051 protected ThrowablePatternConverter(final String name, final String style, final String[] options) { 052 super(name, style); 053 this.options = ThrowableFormatOptions.newInstance(options); 054 if (options != null && options.length > 0) { 055 rawOption = options[0]; 056 } 057 } 058 059 /** 060 * Gets an instance of the class. 061 * 062 * @param options pattern options, may be null. If first element is "short", 063 * only the first line of the throwable will be formatted. 064 * @return instance of class. 065 */ 066 public static ThrowablePatternConverter newInstance(final String[] options) { 067 return new ThrowablePatternConverter("Throwable", "throwable", options); 068 } 069 070 /** 071 * {@inheritDoc} 072 */ 073 @Override 074 public void format(final LogEvent event, final StringBuilder buffer) { 075 final Throwable t = event.getThrown(); 076 077 if (isSubShortOption()) { 078 formatSubShortOption(t, buffer); 079 } 080 else if (t != null && options.anyLines()) { 081 formatOption(t, buffer); 082 } 083 } 084 085 private boolean isSubShortOption() { 086 return ThrowableFormatOptions.MESSAGE.equalsIgnoreCase(rawOption) || 087 ThrowableFormatOptions.LOCALIZED_MESSAGE.equalsIgnoreCase(rawOption) || 088 ThrowableFormatOptions.FILE_NAME.equalsIgnoreCase(rawOption) || 089 ThrowableFormatOptions.LINE_NUMBER.equalsIgnoreCase(rawOption) || 090 ThrowableFormatOptions.METHOD_NAME.equalsIgnoreCase(rawOption) || 091 ThrowableFormatOptions.CLASS_NAME.equalsIgnoreCase(rawOption); 092 } 093 094 private void formatSubShortOption(final Throwable t, final StringBuilder buffer) { 095 StackTraceElement[] trace; 096 StackTraceElement throwingMethod = null; 097 int len; 098 099 if (t != null) { 100 trace = t.getStackTrace(); 101 if (trace !=null && trace.length > 0) { 102 throwingMethod = trace[0]; 103 } 104 } 105 106 if (t != null && throwingMethod != null) { 107 String toAppend = Strings.EMPTY; 108 109 if (ThrowableFormatOptions.CLASS_NAME.equalsIgnoreCase(rawOption)) { 110 toAppend = throwingMethod.getClassName(); 111 } 112 else if (ThrowableFormatOptions.METHOD_NAME.equalsIgnoreCase(rawOption)) { 113 toAppend = throwingMethod.getMethodName(); 114 } 115 else if (ThrowableFormatOptions.LINE_NUMBER.equalsIgnoreCase(rawOption)) { 116 toAppend = String.valueOf(throwingMethod.getLineNumber()); 117 } 118 else if (ThrowableFormatOptions.MESSAGE.equalsIgnoreCase(rawOption)) { 119 toAppend = t.getMessage(); 120 } 121 else if (ThrowableFormatOptions.LOCALIZED_MESSAGE.equalsIgnoreCase(rawOption)) { 122 toAppend = t.getLocalizedMessage(); 123 } 124 else if (ThrowableFormatOptions.FILE_NAME.equalsIgnoreCase(rawOption)) { 125 toAppend = throwingMethod.getFileName(); 126 } 127 128 len = buffer.length(); 129 if (len > 0 && !Character.isWhitespace(buffer.charAt(len - 1))) { 130 buffer.append(' '); 131 } 132 buffer.append(toAppend); 133 } 134 } 135 136 private void formatOption(final Throwable throwable, final StringBuilder buffer) { 137 final StringWriter w = new StringWriter(); 138 139 throwable.printStackTrace(new PrintWriter(w)); 140 final int len = buffer.length(); 141 if (len > 0 && !Character.isWhitespace(buffer.charAt(len - 1))) { 142 buffer.append(' '); 143 } 144 if (!options.allLines() || !Constants.LINE_SEPARATOR.equals(options.getSeparator())) { 145 final StringBuilder sb = new StringBuilder(); 146 final String[] array = w.toString().split(Constants.LINE_SEPARATOR); 147 final int limit = options.minLines(array.length) - 1; 148 for (int i = 0; i <= limit; ++i) { 149 sb.append(array[i]); 150 if (i < limit) { 151 sb.append(options.getSeparator()); 152 } 153 } 154 buffer.append(sb.toString()); 155 156 } else { 157 buffer.append(w.toString()); 158 } 159 } 160 161 /** 162 * This converter obviously handles throwables. 163 * 164 * @return true. 165 */ 166 @Override 167 public boolean handlesThrowable() { 168 return true; 169 } 170 }