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  
18  package org.apache.logging.log4j.core.config.status;
19  
20  import java.io.File;
21  import java.io.FileNotFoundException;
22  import java.io.FileOutputStream;
23  import java.io.PrintStream;
24  import java.net.URI;
25  import java.net.URISyntaxException;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.LinkedList;
29  
30  import org.apache.logging.log4j.Level;
31  import org.apache.logging.log4j.core.util.FileUtils;
32  import org.apache.logging.log4j.status.StatusConsoleListener;
33  import org.apache.logging.log4j.status.StatusListener;
34  import org.apache.logging.log4j.status.StatusLogger;
35  
36  /**
37   * Configuration for setting up {@link StatusConsoleListener} instances.
38   */
39  public class StatusConfiguration {
40  
41      @SuppressWarnings("UseOfSystemOutOrSystemErr")
42      private static final PrintStream DEFAULT_STREAM = System.out;
43      private static final Level DEFAULT_STATUS = Level.ERROR;
44      private static final Verbosity DEFAULT_VERBOSITY = Verbosity.QUIET;
45  
46      private final Collection<String> errorMessages = Collections.synchronizedCollection(new LinkedList<String>());
47      private final StatusLogger logger = StatusLogger.getLogger();
48  
49      private volatile boolean initialized = false;
50  
51      private PrintStream destination = DEFAULT_STREAM;
52      private Level status = DEFAULT_STATUS;
53      private Verbosity verbosity = DEFAULT_VERBOSITY;
54      private String[] verboseClasses;
55  
56      /**
57       * Specifies how verbose the StatusLogger should be.
58       */
59      public static enum Verbosity {
60          QUIET, VERBOSE;
61  
62          /**
63           * Parses the verbosity property into an enum.
64           *
65           * @param value property value to parse.
66           * @return enum corresponding to value, or QUIET by default.
67           */
68          public static Verbosity toVerbosity(final String value) {
69              return Boolean.parseBoolean(value) ? VERBOSE : QUIET;
70          }
71      }
72  
73      /**
74       * Logs an error message to the StatusLogger. If the StatusLogger hasn't been set up yet, queues the message to be
75       * logged after initialization.
76       *
77       * @param message error message to log.
78       */
79      public void error(final String message) {
80          if (!this.initialized) {
81              this.errorMessages.add(message);
82          } else {
83              this.logger.error(message);
84          }
85      }
86  
87      /**
88       * Specifies the destination for StatusLogger events. This can be {@code out} (default) for using
89       * {@link System#out standard out}, {@code err} for using {@link System#err standard error}, or a file URI to
90       * which log events will be written. If the provided URI is invalid, then the default destination of standard
91       * out will be used.
92       *
93       * @param destination where status log messages should be output.
94       * @return {@code this}
95       */
96      public StatusConfiguration withDestination(final String destination) {
97          try {
98              this.destination = parseStreamName(destination);
99          } catch (final URISyntaxException e) {
100             this.error("Could not parse URI [" + destination + "]. Falling back to default of stdout.");
101             this.destination = DEFAULT_STREAM;
102         } catch (final FileNotFoundException e) {
103             this.error("File could not be found at [" + destination + "]. Falling back to default of stdout.");
104             this.destination = DEFAULT_STREAM;
105         }
106         return this;
107     }
108 
109     private PrintStream parseStreamName(final String name) throws URISyntaxException, FileNotFoundException {
110         if (name == null || name.equalsIgnoreCase("out")) {
111             return DEFAULT_STREAM;
112         }
113         if (name.equalsIgnoreCase("err")) {
114             return System.err;
115         }
116         final URI destination = FileUtils.getCorrectedFilePathUri(name);
117         final File output = FileUtils.fileFromUri(destination);
118         if (output == null) {
119             // don't want any NPEs, no sir
120             return DEFAULT_STREAM;
121         }
122         final FileOutputStream fos = new FileOutputStream(output);
123         return new PrintStream(fos, true);
124     }
125 
126     /**
127      * Specifies the logging level by name to use for filtering StatusLogger messages.
128      *
129      * @param status name of logger level to filter below.
130      * @return {@code this}
131      * @see Level
132      */
133     public StatusConfiguration withStatus(final String status) {
134         this.status = Level.toLevel(status, null);
135         if (this.status == null) {
136             this.error("Invalid status level specified: " + status + ". Defaulting to ERROR.");
137             this.status = Level.ERROR;
138         }
139         return this;
140     }
141 
142     /**
143      * Specifies the logging level to use for filtering StatusLogger messages.
144      *
145      * @param status logger level to filter below.
146      * @return {@code this}
147      */
148     public StatusConfiguration withStatus(final Level status) {
149         this.status = status;
150         return this;
151     }
152 
153     /**
154      * Specifies the verbosity level to log at. This only applies to classes configured by
155      * {@link #withVerboseClasses(String...) verboseClasses}.
156      *
157      * @param verbosity basic filter for status logger messages.
158      * @return {@code this}
159      */
160     public StatusConfiguration withVerbosity(final String verbosity) {
161         this.verbosity = Verbosity.toVerbosity(verbosity);
162         return this;
163     }
164 
165     /**
166      * Specifies which class names to filter if the configured verbosity level is QUIET.
167      *
168      * @param verboseClasses names of classes to filter if not using VERBOSE.
169      * @return {@code this}
170      */
171     public StatusConfiguration withVerboseClasses(final String... verboseClasses) {
172         this.verboseClasses = verboseClasses;
173         return this;
174     }
175 
176     /**
177      * Configures and initializes the StatusLogger using the configured options in this instance.
178      */
179     public void initialize() {
180         if (!this.initialized) {
181             if (this.status == Level.OFF) {
182                 this.initialized = true;
183             } else {
184                 final boolean configured = configureExistingStatusConsoleListener();
185                 if (!configured) {
186                     registerNewStatusConsoleListener();
187                 }
188                 migrateSavedLogMessages();
189             }
190         }
191     }
192 
193     private boolean configureExistingStatusConsoleListener() {
194         boolean configured = false;
195         for (final StatusListener statusListener : this.logger.getListeners()) {
196             if (statusListener instanceof StatusConsoleListener) {
197                 final StatusConsoleListener listener = (StatusConsoleListener) statusListener;
198                 listener.setLevel(this.status);
199                 if (this.verbosity == Verbosity.QUIET) {
200                     listener.setFilters(this.verboseClasses);
201                 }
202                 configured = true;
203             }
204         }
205         return configured;
206     }
207 
208 
209     private void registerNewStatusConsoleListener() {
210         final StatusConsoleListener listener = new StatusConsoleListener(this.status, this.destination);
211         if (this.verbosity == Verbosity.QUIET) {
212             listener.setFilters(this.verboseClasses);
213         }
214         this.logger.registerListener(listener);
215     }
216 
217     private void migrateSavedLogMessages() {
218         for (final String message : this.errorMessages) {
219             this.logger.error(message);
220         }
221         this.initialized = true;
222         this.errorMessages.clear();
223     }
224 }