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 */ 017package org.apache.logging.log4j.core.pattern; 018 019import java.io.PrintWriter; 020import java.io.StringWriter; 021 022import org.apache.logging.log4j.core.LogEvent; 023import org.apache.logging.log4j.core.config.plugins.Plugin; 024import org.apache.logging.log4j.core.helpers.Constants; 025import org.apache.logging.log4j.core.impl.ThrowableFormatOptions; 026 027 028/** 029 * Outputs the Throwable portion of the LoggingEvent as a full stacktrace 030 * unless this converter's option is 'short', where it just outputs the first line of the trace, or if 031 * the number of lines to print is explicitly specified. 032 */ 033@Plugin(name = "ThrowablePatternConverter", category = "Converter") 034@ConverterKeys({"ex", "throwable", "exception" }) 035public class ThrowablePatternConverter extends LogEventPatternConverter { 036 037 private String rawOption; 038 039 /** 040 * The number of lines to write. 041 */ 042 protected final ThrowableFormatOptions options; 043 044 /** 045 * Constructor. 046 * @param name Name of converter. 047 * @param style CSS style for output. 048 * @param options options, may be null. 049 */ 050 protected ThrowablePatternConverter(final String name, final String style, final String[] options) { 051 super(name, style); 052 this.options = ThrowableFormatOptions.newInstance(options); 053 if (options != null && options.length > 0) { 054 rawOption = options[0]; 055 } 056 } 057 058 /** 059 * Gets an instance of the class. 060 * 061 * @param options pattern options, may be null. If first element is "short", 062 * only the first line of the throwable will be formatted. 063 * @return instance of class. 064 */ 065 public static ThrowablePatternConverter newInstance(final String[] options) { 066 return new ThrowablePatternConverter("Throwable", "throwable", options); 067 } 068 069 /** 070 * {@inheritDoc} 071 */ 072 @Override 073 public void format(final LogEvent event, final StringBuilder buffer) { 074 final Throwable t = event.getThrown(); 075 076 if (isSubShortOption()) { 077 formatSubShortOption(t, buffer); 078 } 079 else if (t != null && options.anyLines()) { 080 formatOption(t, buffer); 081 } 082 } 083 084 private boolean isSubShortOption() { 085 return ThrowableFormatOptions.MESSAGE.equalsIgnoreCase(rawOption) || 086 ThrowableFormatOptions.LOCALIZED_MESSAGE.equalsIgnoreCase(rawOption) || 087 ThrowableFormatOptions.FILE_NAME.equalsIgnoreCase(rawOption) || 088 ThrowableFormatOptions.LINE_NUMBER.equalsIgnoreCase(rawOption) || 089 ThrowableFormatOptions.METHOD_NAME.equalsIgnoreCase(rawOption) || 090 ThrowableFormatOptions.CLASS_NAME.equalsIgnoreCase(rawOption); 091 } 092 093 private void formatSubShortOption(final Throwable t, final StringBuilder buffer) { 094 StackTraceElement[] trace; 095 StackTraceElement throwingMethod = null; 096 int len; 097 098 if (t != null) { 099 trace = t.getStackTrace(); 100 if (trace !=null && trace.length > 0) { 101 throwingMethod = trace[0]; 102 } 103 } 104 105 if (t != null && throwingMethod != null) { 106 String toAppend = ""; 107 108 if (ThrowableFormatOptions.CLASS_NAME.equalsIgnoreCase(rawOption)) { 109 toAppend = throwingMethod.getClassName(); 110 } 111 else if (ThrowableFormatOptions.METHOD_NAME.equalsIgnoreCase(rawOption)) { 112 toAppend = throwingMethod.getMethodName(); 113 } 114 else if (ThrowableFormatOptions.LINE_NUMBER.equalsIgnoreCase(rawOption)) { 115 toAppend = String.valueOf(throwingMethod.getLineNumber()); 116 } 117 else if (ThrowableFormatOptions.MESSAGE.equalsIgnoreCase(rawOption)) { 118 toAppend = t.getMessage(); 119 } 120 else if (ThrowableFormatOptions.LOCALIZED_MESSAGE.equalsIgnoreCase(rawOption)) { 121 toAppend = t.getLocalizedMessage(); 122 } 123 else if (ThrowableFormatOptions.FILE_NAME.equalsIgnoreCase(rawOption)) { 124 toAppend = throwingMethod.getFileName(); 125 } 126 127 len = buffer.length(); 128 if (len > 0 && !Character.isWhitespace(buffer.charAt(len - 1))) { 129 buffer.append(" "); 130 } 131 buffer.append(toAppend); 132 } 133 } 134 135 private void formatOption(final Throwable throwable, final StringBuilder buffer) { 136 final StringWriter w = new StringWriter(); 137 138 throwable.printStackTrace(new PrintWriter(w)); 139 final int len = buffer.length(); 140 if (len > 0 && !Character.isWhitespace(buffer.charAt(len - 1))) { 141 buffer.append(' '); 142 } 143 if (!options.allLines() || !Constants.LINE_SEP.equals(options.getSeparator())) { 144 final StringBuilder sb = new StringBuilder(); 145 final String[] array = w.toString().split(Constants.LINE_SEP); 146 final int limit = options.minLines(array.length) - 1; 147 for (int i = 0; i <= limit; ++i) { 148 sb.append(array[i]); 149 if (i < limit) { 150 sb.append(options.getSeparator()); 151 } 152 } 153 buffer.append(sb.toString()); 154 155 } else { 156 buffer.append(w.toString()); 157 } 158 } 159 160 /** 161 * This converter obviously handles throwables. 162 * 163 * @return true. 164 */ 165 @Override 166 public boolean handlesThrowable() { 167 return true; 168 } 169}