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