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