View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.impl;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.Scanner;
22  
23  import org.apache.logging.log4j.core.helpers.Constants;
24  
25  /**
26   * Contains options which control how a {@link Throwable} pattern is formatted.
27   */
28  public final class ThrowableFormatOptions {
29  
30      private static final int DEFAULT_LINES = Integer.MAX_VALUE;
31  
32      /**
33       * Default instance of {@code ThrowableFormatOptions}.
34       */
35      protected static final ThrowableFormatOptions DEFAULT = new ThrowableFormatOptions();
36  
37      /**
38       * Format the whole stack trace.
39       */
40      private static final String FULL = "full";
41  
42      /**
43       * Do not format the exception.
44       */
45      private static final String NONE = "none";
46  
47      /**
48       * Format only the first line of the throwable.
49       */
50      private static final String SHORT = "short";
51  
52      /**
53       * The number of lines to write.
54       */
55      private final int lines;
56  
57      /**
58       * The stack trace separator.
59       */
60      private final String separator;
61  
62      /**
63       * The list of packages to filter.
64       */
65      private final List<String> packages;
66  
67      public static final String CLASS_NAME = "short.className";
68      public static final String METHOD_NAME = "short.methodName";
69      public static final String LINE_NUMBER = "short.lineNumber";
70      public static final String FILE_NAME = "short.fileName";
71      public static final String MESSAGE = "short.message";
72      public static final String LOCALIZED_MESSAGE = "short.localizedMessage";
73  
74      /**
75       * Construct the options for printing stack trace.
76       * @param lines The number of lines.
77       * @param separator The stack trace separator.
78       * @param packages The packages to filter.
79       */
80      protected ThrowableFormatOptions(final int lines, final String separator, final List<String> packages) {
81          this.lines = lines;
82          this.separator = separator == null ? Constants.LINE_SEP : separator;
83          this.packages = packages;
84      }
85  
86      /**
87       * Construct the options for printing stack trace.
88       * @param packages The packages to filter.
89       */
90      protected ThrowableFormatOptions(final List<String> packages) {
91          this(DEFAULT_LINES, null, packages);
92      }
93  
94      /**
95       * Construct the options for printing stack trace.
96       */
97      protected ThrowableFormatOptions() {
98          this(DEFAULT_LINES, null, null);
99      }
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 }