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