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.concurrent.LinkedBlockingQueue; 028 029import org.apache.logging.log4j.Level; 030import org.apache.logging.log4j.core.util.FileUtils; 031import org.apache.logging.log4j.core.util.NetUtils; 032import org.apache.logging.log4j.status.StatusConsoleListener; 033import org.apache.logging.log4j.status.StatusListener; 034import org.apache.logging.log4j.status.StatusLogger; 035 036/** 037 * Configuration for setting up {@link StatusConsoleListener} instances. 038 */ 039public 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 = new LinkedBlockingQueue<>(); 047 private final StatusLogger logger = StatusLogger.getLogger(); 048 049 private volatile boolean initialized; 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 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 destUri = NetUtils.toURI(name); 117 final File output = FileUtils.fileFromUri(destUri); 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 this.logger.updateListenerLevel(this.status); 200 if (this.verbosity == Verbosity.QUIET) { 201 listener.setFilters(this.verboseClasses); 202 } 203 configured = true; 204 } 205 } 206 return configured; 207 } 208 209 210 private void registerNewStatusConsoleListener() { 211 final StatusConsoleListener listener = new StatusConsoleListener(this.status, this.destination); 212 if (this.verbosity == Verbosity.QUIET) { 213 listener.setFilters(this.verboseClasses); 214 } 215 this.logger.registerListener(listener); 216 } 217 218 private void migrateSavedLogMessages() { 219 for (final String message : this.errorMessages) { 220 this.logger.error(message); 221 } 222 this.initialized = true; 223 this.errorMessages.clear(); 224 } 225}