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 */ 017package org.apache.logging.log4j.status; 018 019import java.io.Closeable; 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.List; 024import java.util.Queue; 025import java.util.concurrent.ConcurrentLinkedQueue; 026import java.util.concurrent.CopyOnWriteArrayList; 027import java.util.concurrent.locks.Lock; 028import java.util.concurrent.locks.ReadWriteLock; 029import java.util.concurrent.locks.ReentrantLock; 030import java.util.concurrent.locks.ReentrantReadWriteLock; 031 032import org.apache.logging.log4j.Level; 033import org.apache.logging.log4j.Marker; 034import org.apache.logging.log4j.message.Message; 035import org.apache.logging.log4j.message.MessageFactory; 036import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory; 037import org.apache.logging.log4j.simple.SimpleLogger; 038import org.apache.logging.log4j.simple.SimpleLoggerContext; 039import org.apache.logging.log4j.spi.AbstractLogger; 040import org.apache.logging.log4j.util.Constants; 041import org.apache.logging.log4j.util.PropertiesUtil; 042import org.apache.logging.log4j.util.Strings; 043 044/** 045 * Records events that occur in the logging system. By default, only error messages are logged to {@link System#err}. 046 * Normally, the Log4j StatusLogger is configured via the root {@code <Configuration status="LEVEL"/>} node in a Log4j 047 * configuration file. However, this can be overridden via a system property named 048 * "{@value SimpleLoggerContext#SYSTEM_PREFIX}StatusLogger.level" and will work with any Log4j provider. 049 * 050 * @see SimpleLogger 051 * @see SimpleLoggerContext 052 */ 053public final class StatusLogger extends AbstractLogger { 054 055 /** 056 * System property that can be configured with the number of entries in the queue. Once the limit is reached older 057 * entries will be removed as new entries are added. 058 */ 059 public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries"; 060 061 /** 062 * System property that can be configured with the {@link Level} name to use as the default level for 063 * {@link StatusListener}s. 064 */ 065 public static final String DEFAULT_STATUS_LISTENER_LEVEL = "log4j2.StatusLogger.level"; 066 067 /** 068 * System property that can be configured with a date-time format string to use as the format for timestamps 069 * in the status logger output. See {@link java.text.SimpleDateFormat} for supported formats. 070 * @since 2.11.0 071 */ 072 public static final String STATUS_DATE_FORMAT = "log4j2.StatusLogger.DateFormat"; 073 074 private static final long serialVersionUID = 2L; 075 076 private static final String NOT_AVAIL = "?"; 077 078 private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties"); 079 080 private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200); 081 082 private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty(DEFAULT_STATUS_LISTENER_LEVEL); 083 084 // LOG4J2-1176: normal parameterized message remembers param object, causing memory leaks. 085 private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(), 086 ParameterizedNoReferenceMessageFactory.INSTANCE); 087 088 private final SimpleLogger logger; 089 090 private final Collection<StatusListener> listeners = new CopyOnWriteArrayList<>(); 091 092 @SuppressWarnings("NonSerializableFieldInSerializableClass") 093 // ReentrantReadWriteLock is Serializable 094 private final ReadWriteLock listenersLock = new ReentrantReadWriteLock(); 095 096 private final Queue<StatusData> messages = new BoundedQueue<>(MAX_ENTRIES); 097 098 @SuppressWarnings("NonSerializableFieldInSerializableClass") 099 // ReentrantLock is Serializable 100 private final Lock msgLock = new ReentrantLock(); 101 102 private int listenersLevel; 103 104 private StatusLogger(final String name, final MessageFactory messageFactory) { 105 super(name, messageFactory); 106 final String dateFormat = PROPS.getStringProperty(STATUS_DATE_FORMAT, Strings.EMPTY); 107 final boolean showDateTime = !Strings.isEmpty(dateFormat); 108 this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, showDateTime, false, 109 dateFormat, messageFactory, PROPS, System.err); 110 this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel(); 111 112 // LOG4J2-1813 if system property "log4j2.debug" is defined, print all status logging 113 if (isDebugPropertyEnabled()) { 114 logger.setLevel(Level.TRACE); 115 } 116 } 117 118 // LOG4J2-1813 if system property "log4j2.debug" is defined, print all status logging 119 private boolean isDebugPropertyEnabled() { 120 return PropertiesUtil.getProperties().getBooleanProperty(Constants.LOG4J2_DEBUG, false, true); 121 } 122 123 /** 124 * Retrieve the StatusLogger. 125 * 126 * @return The StatusLogger. 127 */ 128 public static StatusLogger getLogger() { 129 return STATUS_LOGGER; 130 } 131 132 public void setLevel(final Level level) { 133 logger.setLevel(level); 134 } 135 136 /** 137 * Registers a new listener. 138 * 139 * @param listener The StatusListener to register. 140 */ 141 public void registerListener(final StatusListener listener) { 142 listenersLock.writeLock().lock(); 143 try { 144 listeners.add(listener); 145 final Level lvl = listener.getStatusLevel(); 146 if (listenersLevel < lvl.intLevel()) { 147 listenersLevel = lvl.intLevel(); 148 } 149 } finally { 150 listenersLock.writeLock().unlock(); 151 } 152 } 153 154 /** 155 * Removes a StatusListener. 156 * 157 * @param listener The StatusListener to remove. 158 */ 159 public void removeListener(final StatusListener listener) { 160 closeSilently(listener); 161 listenersLock.writeLock().lock(); 162 try { 163 listeners.remove(listener); 164 int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel(); 165 for (final StatusListener statusListener : listeners) { 166 final int level = statusListener.getStatusLevel().intLevel(); 167 if (lowest < level) { 168 lowest = level; 169 } 170 } 171 listenersLevel = lowest; 172 } finally { 173 listenersLock.writeLock().unlock(); 174 } 175 } 176 177 public void updateListenerLevel(final Level status) { 178 if (status.intLevel() > listenersLevel) { 179 listenersLevel = status.intLevel(); 180 } 181 } 182 183 /** 184 * Returns a thread safe Iterable for the StatusListener. 185 * 186 * @return An Iterable for the list of StatusListeners. 187 */ 188 public Iterable<StatusListener> getListeners() { 189 return listeners; 190 } 191 192 /** 193 * Clears the list of status events and listeners. 194 */ 195 public void reset() { 196 listenersLock.writeLock().lock(); 197 try { 198 for (final StatusListener listener : listeners) { 199 closeSilently(listener); 200 } 201 } finally { 202 listeners.clear(); 203 listenersLock.writeLock().unlock(); 204 // note this should certainly come after the unlock to avoid unnecessary nested locking 205 clear(); 206 } 207 } 208 209 private static void closeSilently(final Closeable resource) { 210 try { 211 resource.close(); 212 } catch (final IOException ignored) { 213 // ignored 214 } 215 } 216 217 /** 218 * Returns a List of all events as StatusData objects. 219 * 220 * @return The list of StatusData objects. 221 */ 222 public List<StatusData> getStatusData() { 223 msgLock.lock(); 224 try { 225 return new ArrayList<>(messages); 226 } finally { 227 msgLock.unlock(); 228 } 229 } 230 231 /** 232 * Clears the list of status events. 233 */ 234 public void clear() { 235 msgLock.lock(); 236 try { 237 messages.clear(); 238 } finally { 239 msgLock.unlock(); 240 } 241 } 242 243 @Override 244 public Level getLevel() { 245 return logger.getLevel(); 246 } 247 248 /** 249 * Adds an event. 250 * 251 * @param marker The Marker 252 * @param fqcn The fully qualified class name of the <b>caller</b> 253 * @param level The logging level 254 * @param msg The message associated with the event. 255 * @param t A Throwable or null. 256 */ 257 @Override 258 public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg, 259 final Throwable t) { 260 StackTraceElement element = null; 261 if (fqcn != null) { 262 element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace()); 263 } 264 final StatusData data = new StatusData(element, level, msg, t, null); 265 msgLock.lock(); 266 try { 267 messages.add(data); 268 } finally { 269 msgLock.unlock(); 270 } 271 // LOG4J2-1813 if system property "log4j2.debug" is defined, all status logging is enabled 272 if (isDebugPropertyEnabled() || (listeners.size() <= 0)) { 273 logger.logMessage(fqcn, level, marker, msg, t); 274 } else { 275 for (final StatusListener listener : listeners) { 276 if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) { 277 listener.log(data); 278 } 279 } 280 } 281 } 282 283 private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) { 284 if (fqcn == null) { 285 return null; 286 } 287 boolean next = false; 288 for (final StackTraceElement element : stackTrace) { 289 final String className = element.getClassName(); 290 if (next && !fqcn.equals(className)) { 291 return element; 292 } 293 if (fqcn.equals(className)) { 294 next = true; 295 } else if (NOT_AVAIL.equals(className)) { 296 break; 297 } 298 } 299 return null; 300 } 301 302 @Override 303 public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) { 304 return isEnabled(level, marker); 305 } 306 307 @Override 308 public boolean isEnabled(final Level level, final Marker marker, final String message) { 309 return isEnabled(level, marker); 310 } 311 312 @Override 313 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) { 314 return isEnabled(level, marker); 315 } 316 317 @Override 318 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) { 319 return isEnabled(level, marker); 320 } 321 322 @Override 323 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 324 final Object p1) { 325 return isEnabled(level, marker); 326 } 327 328 @Override 329 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 330 final Object p1, final Object p2) { 331 return isEnabled(level, marker); 332 } 333 334 @Override 335 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 336 final Object p1, final Object p2, final Object p3) { 337 return isEnabled(level, marker); 338 } 339 340 @Override 341 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 342 final Object p1, final Object p2, final Object p3, 343 final Object p4) { 344 return isEnabled(level, marker); 345 } 346 347 @Override 348 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 349 final Object p1, final Object p2, final Object p3, 350 final Object p4, final Object p5) { 351 return isEnabled(level, marker); 352 } 353 354 @Override 355 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 356 final Object p1, final Object p2, final Object p3, 357 final Object p4, final Object p5, final Object p6) { 358 return isEnabled(level, marker); 359 } 360 361 @Override 362 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 363 final Object p1, final Object p2, final Object p3, 364 final Object p4, final Object p5, final Object p6, 365 final Object p7) { 366 return isEnabled(level, marker); 367 } 368 369 @Override 370 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 371 final Object p1, final Object p2, final Object p3, 372 final Object p4, final Object p5, final Object p6, 373 final Object p7, final Object p8) { 374 return isEnabled(level, marker); 375 } 376 377 @Override 378 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 379 final Object p1, final Object p2, final Object p3, 380 final Object p4, final Object p5, final Object p6, 381 final Object p7, final Object p8, final Object p9) { 382 return isEnabled(level, marker); 383 } 384 385 @Override 386 public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) { 387 return isEnabled(level, marker); 388 } 389 390 @Override 391 public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) { 392 return isEnabled(level, marker); 393 } 394 395 @Override 396 public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) { 397 return isEnabled(level, marker); 398 } 399 400 @Override 401 public boolean isEnabled(final Level level, final Marker marker) { 402 // LOG4J2-1813 if system property "log4j2.debug" is defined, all status logging is enabled 403 if (isDebugPropertyEnabled()) { 404 return true; 405 } 406 if (listeners.size() > 0) { 407 return listenersLevel >= level.intLevel(); 408 } 409 return logger.isEnabled(level, marker); 410 } 411 412 /** 413 * Queues for status events. 414 * 415 * @param <E> Object type to be stored in the queue. 416 */ 417 private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> { 418 419 private static final long serialVersionUID = -3945953719763255337L; 420 421 private final int size; 422 423 BoundedQueue(final int size) { 424 this.size = size; 425 } 426 427 @Override 428 public boolean add(final E object) { 429 super.add(object); 430 while (messages.size() > size) { 431 messages.poll(); 432 } 433 return size > 0; 434 } 435 } 436}