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 org.apache.logging.log4j.core.helpers.Constants;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Scanner;
24  
25  /**
26   * Contains options which control how a {@code throwable} pattern is formatted.
27   */
28  public final class ThrowableFormatOptions {
29  
30      /**
31       * Default instance of {@code ThrowableFormatOptions}.
32       */
33      protected static final ThrowableFormatOptions DEFAULT = new ThrowableFormatOptions();
34  
35      /**
36       * Format the whole stack trace.
37       */
38      private static final String FULL = "full";
39  
40      /**
41       * Do not format the exception.
42       */
43      private static final String NONE = "none";
44  
45      /**
46       * Format only the first line of the throwable.
47       */
48      private static final String SHORT = "short";
49  
50      /**
51       * The number of lines to write.
52       */
53      private final int lines;
54  
55      /**
56       * The stack trace separator.
57       */
58      private final String separator;
59  
60      /**
61       * The list of packages to filter.
62       */
63      private final List<String> packages;
64  
65      /**
66       * Construct the options for printing stack trace.
67       * @param lines The number of lines.
68       * @param separator The stack trace separator.
69       * @param packages The packages to filter.
70       */
71      protected ThrowableFormatOptions(final Integer lines, final String separator, final List<String> packages) {
72          this.lines = lines == null ? Integer.MAX_VALUE : lines;
73          this.separator = separator == null ? Constants.LINE_SEP : separator;
74          this.packages = packages;
75      }
76  
77      /**
78       * Construct the options for printing stack trace.
79       * @param packages The packages to filter.
80       */
81      protected ThrowableFormatOptions(final List<String> packages) {
82          this(null, null, packages);
83      }
84  
85      /**
86       * Construct the options for printing stack trace.
87       */
88      protected ThrowableFormatOptions() {
89          this(null, null, null);
90      }
91  
92      /**
93       * Returns the number of lines to write.
94       * @return The number of lines to write.
95       */
96      public int getLines() {
97          return this.lines;
98      }
99  
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 }