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
018 package org.apache.logging.log4j.core.config.status;
019
020 import java.io.File;
021 import java.io.FileNotFoundException;
022 import java.io.FileOutputStream;
023 import java.io.PrintStream;
024 import java.net.URI;
025 import java.net.URISyntaxException;
026 import java.util.Collection;
027 import java.util.Collections;
028 import java.util.LinkedList;
029
030 import org.apache.logging.log4j.Level;
031 import org.apache.logging.log4j.core.util.FileUtils;
032 import org.apache.logging.log4j.status.StatusConsoleListener;
033 import org.apache.logging.log4j.status.StatusListener;
034 import org.apache.logging.log4j.status.StatusLogger;
035
036 /**
037 * Configuration for setting up {@link StatusConsoleListener} instances.
038 */
039 public class StatusConfiguration {
040
041 @SuppressWarnings("UseOfSystemOutOrSystemErr")
042 private static final PrintStream DEFAULT_STREAM = System.out;
043 private static final Level DEFAULT_STATUS = Level.ERROR;
044 private static final Verbosity DEFAULT_VERBOSITY = Verbosity.QUIET;
045
046 private final Collection<String> errorMessages = Collections.synchronizedCollection(new LinkedList<String>());
047 private final StatusLogger logger = StatusLogger.getLogger();
048
049 private volatile boolean initialized = false;
050
051 private PrintStream destination = DEFAULT_STREAM;
052 private Level status = DEFAULT_STATUS;
053 private Verbosity verbosity = DEFAULT_VERBOSITY;
054 private String[] verboseClasses;
055
056 /**
057 * Specifies how verbose the StatusLogger should be.
058 */
059 public static enum Verbosity {
060 QUIET, VERBOSE;
061
062 /**
063 * Parses the verbosity property into an enum.
064 *
065 * @param value property value to parse.
066 * @return enum corresponding to value, or QUIET by default.
067 */
068 public static Verbosity toVerbosity(final String value) {
069 return Boolean.parseBoolean(value) ? VERBOSE : QUIET;
070 }
071 }
072
073 /**
074 * Logs an error message to the StatusLogger. If the StatusLogger hasn't been set up yet, queues the message to be
075 * logged after initialization.
076 *
077 * @param message error message to log.
078 */
079 public void error(final String message) {
080 if (!this.initialized) {
081 this.errorMessages.add(message);
082 } else {
083 this.logger.error(message);
084 }
085 }
086
087 /**
088 * Specifies the destination for StatusLogger events. This can be {@code out} (default) for using
089 * {@link System#out standard out}, {@code err} for using {@link System#err standard error}, or a file URI to
090 * which log events will be written. If the provided URI is invalid, then the default destination of standard
091 * out will be used.
092 *
093 * @param destination where status log messages should be output.
094 * @return {@code this}
095 */
096 public StatusConfiguration withDestination(final String destination) {
097 try {
098 this.destination = parseStreamName(destination);
099 } 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 }