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.impl; 018 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Scanner; 022 023import org.apache.logging.log4j.core.util.Constants; 024import org.apache.logging.log4j.core.util.Patterns; 025 026/** 027 * Contains options which control how a {@link Throwable} pattern is formatted. 028 */ 029public final class ThrowableFormatOptions { 030 031 private static final int DEFAULT_LINES = Integer.MAX_VALUE; 032 033 /** 034 * Default instance of {@code ThrowableFormatOptions}. 035 */ 036 protected static final ThrowableFormatOptions DEFAULT = new ThrowableFormatOptions(); 037 038 /** 039 * Format the whole stack trace. 040 */ 041 private static final String FULL = "full"; 042 043 /** 044 * Do not format the exception. 045 */ 046 private static final String NONE = "none"; 047 048 /** 049 * Format only the first line of the throwable. 050 */ 051 private static final String SHORT = "short"; 052 053 /** 054 * The number of lines to write. 055 */ 056 private final int lines; 057 058 /** 059 * The stack trace separator. 060 */ 061 private final String separator; 062 063 /** 064 * The list of packages to filter. 065 */ 066 private final List<String> packages; 067 068 public static final String CLASS_NAME = "short.className"; 069 public static final String METHOD_NAME = "short.methodName"; 070 public static final String LINE_NUMBER = "short.lineNumber"; 071 public static final String FILE_NAME = "short.fileName"; 072 public static final String MESSAGE = "short.message"; 073 public static final String LOCALIZED_MESSAGE = "short.localizedMessage"; 074 075 /** 076 * Construct the options for printing stack trace. 077 * @param lines The number of lines. 078 * @param separator The stack trace separator. 079 * @param packages The packages to filter. 080 */ 081 protected ThrowableFormatOptions(final int lines, final String separator, final List<String> packages) { 082 this.lines = lines; 083 this.separator = separator == null ? Constants.LINE_SEPARATOR : separator; 084 this.packages = packages; 085 } 086 087 /** 088 * Construct the options for printing stack trace. 089 * @param packages The packages to filter. 090 */ 091 protected ThrowableFormatOptions(final List<String> packages) { 092 this(DEFAULT_LINES, null, packages); 093 } 094 095 /** 096 * Construct the options for printing stack trace. 097 */ 098 protected ThrowableFormatOptions() { 099 this(DEFAULT_LINES, null, null); 100 } 101 102 /** 103 * Returns the number of lines to write. 104 * @return The number of lines to write. 105 */ 106 public int getLines() { 107 return this.lines; 108 } 109 110 /** 111 * Returns the stack trace separator. 112 * @return The stack trace separator. 113 */ 114 public String getSeparator() { 115 return this.separator; 116 } 117 118 /** 119 * Returns the list of packages to filter. 120 * @return The list of packages to filter. 121 */ 122 public List<String> getPackages() { 123 return this.packages; 124 } 125 126 /** 127 * Determines if all lines should be printed. 128 * @return true for all lines, false otherwise. 129 */ 130 public boolean allLines() { 131 return this.lines == DEFAULT_LINES; 132 } 133 134 /** 135 * Determines if any lines should be printed. 136 * @return true for any lines, false otherwise. 137 */ 138 public boolean anyLines() { 139 return this.lines > 0; 140 } 141 142 /** 143 * Returns the minimum between the lines and the max lines. 144 * @param maxLines The maximum number of lines. 145 * @return The number of lines to print. 146 */ 147 public int minLines(final int maxLines) { 148 return this.lines > maxLines ? maxLines : this.lines; 149 } 150 151 /** 152 * Determines if there are any packages to filter. 153 * @return true if there are packages, false otherwise. 154 */ 155 public boolean hasPackages() { 156 return this.packages != null && !this.packages.isEmpty(); 157 } 158 159 /** 160 * {@inheritDoc} 161 */ 162 @Override 163 public String toString() { 164 final StringBuilder s = new StringBuilder(); 165 s.append('{').append(allLines() ? FULL : this.lines == 2 ? SHORT : anyLines() ? String.valueOf(this.lines) : NONE).append('}'); 166 s.append("{separator(").append(this.separator).append(")}"); 167 if (hasPackages()) { 168 s.append("{filters("); 169 for (final String p : this.packages) { 170 s.append(p).append(','); 171 } 172 s.deleteCharAt(s.length() - 1); 173 s.append(")}"); 174 } 175 return s.toString(); 176 } 177 178 /** 179 * Create a new instance based on the array of options. 180 * @param options The array of options. 181 */ 182 public static ThrowableFormatOptions newInstance(String[] options) { 183 if (options == null || options.length == 0) { 184 return DEFAULT; 185 } 186 // NOTE: The following code is present for backward compatibility 187 // and was copied from Extended/RootThrowablePatternConverter. 188 // This supports a single option with the format: 189 // %xEx{["none"|"short"|"full"|depth],[filters(packages)} 190 // However, the convention for multiple options should be: 191 // %xEx{["none"|"short"|"full"|depth]}[{filters(packages)}] 192 if (options.length == 1 && options[0] != null && options[0].length() > 0) { 193 final String[] opts = options[0].split(Patterns.COMMA_SEPARATOR, 2); 194 final String first = opts[0].trim(); 195 final Scanner scanner = new Scanner(first); 196 if (opts.length > 1 && (first.equalsIgnoreCase(FULL) || first.equalsIgnoreCase(SHORT) || first.equalsIgnoreCase(NONE) || scanner.hasNextInt())) { 197 options = new String[]{first, opts[1].trim()}; 198 } 199 scanner.close(); 200 } 201 202 int lines = DEFAULT.lines; 203 String separator = DEFAULT.separator; 204 List<String> packages = DEFAULT.packages; 205 for (final String rawOption : options) { 206 if (rawOption != null) { 207 final String option = rawOption.trim(); 208 if (option.isEmpty()) { 209 // continue; 210 } else if (option.startsWith("separator(") && option.endsWith(")")) { 211 separator = option.substring("separator(".length(), option.length() - 1); 212 } else if (option.startsWith("filters(") && option.endsWith(")")) { 213 final String filterStr = option.substring("filters(".length(), option.length() - 1); 214 if (filterStr.length() > 0) { 215 final String[] array = filterStr.split(Patterns.COMMA_SEPARATOR); 216 if (array.length > 0) { 217 packages = new ArrayList<String>(array.length); 218 for (String token : array) { 219 token = token.trim(); 220 if (token.length() > 0) { 221 packages.add(token); 222 } 223 } 224 } 225 } 226 } else if (option.equalsIgnoreCase(NONE)) { 227 lines = 0; 228 } else if (option.equalsIgnoreCase(SHORT) || option.equalsIgnoreCase(CLASS_NAME) || 229 option.equalsIgnoreCase(METHOD_NAME) || option.equalsIgnoreCase(LINE_NUMBER) || 230 option.equalsIgnoreCase(FILE_NAME) || option.equalsIgnoreCase(MESSAGE) || 231 option.equalsIgnoreCase(LOCALIZED_MESSAGE)) { 232 lines = 2; 233 } else if (!option.equalsIgnoreCase(FULL)) { 234 lines = Integer.parseInt(option); 235 } 236 } 237 } 238 return new ThrowableFormatOptions(lines, separator, packages); 239 } 240}