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