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()) { 273 logger.logMessage(fqcn, level, marker, msg, t); 274 } else { 275 if (listeners.size() > 0) { 276 for (final StatusListener listener : listeners) { 277 if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) { 278 listener.log(data); 279 } 280 } 281 } else { 282 logger.logMessage(fqcn, level, marker, msg, t); 283 } 284 } 285 } 286 287 private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) { 288 if (fqcn == null) { 289 return null; 290 } 291 boolean next = false; 292 for (final StackTraceElement element : stackTrace) { 293 final String className = element.getClassName(); 294 if (next && !fqcn.equals(className)) { 295 return element; 296 } 297 if (fqcn.equals(className)) { 298 next = true; 299 } else if (NOT_AVAIL.equals(className)) { 300 break; 301 } 302 } 303 return null; 304 } 305 306 @Override 307 public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) { 308 return isEnabled(level, marker); 309 } 310 311 @Override 312 public boolean isEnabled(final Level level, final Marker marker, final String message) { 313 return isEnabled(level, marker); 314 } 315 316 @Override 317 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) { 318 return isEnabled(level, marker); 319 } 320 321 @Override 322 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) { 323 return isEnabled(level, marker); 324 } 325 326 @Override 327 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 328 final Object p1) { 329 return isEnabled(level, marker); 330 } 331 332 @Override 333 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 334 final Object p1, final Object p2) { 335 return isEnabled(level, marker); 336 } 337 338 @Override 339 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 340 final Object p1, final Object p2, final Object p3) { 341 return isEnabled(level, marker); 342 } 343 344 @Override 345 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 346 final Object p1, final Object p2, final Object p3, 347 final Object p4) { 348 return isEnabled(level, marker); 349 } 350 351 @Override 352 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 353 final Object p1, final Object p2, final Object p3, 354 final Object p4, final Object p5) { 355 return isEnabled(level, marker); 356 } 357 358 @Override 359 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 360 final Object p1, final Object p2, final Object p3, 361 final Object p4, final Object p5, final Object p6) { 362 return isEnabled(level, marker); 363 } 364 365 @Override 366 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 367 final Object p1, final Object p2, final Object p3, 368 final Object p4, final Object p5, final Object p6, 369 final Object p7) { 370 return isEnabled(level, marker); 371 } 372 373 @Override 374 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 375 final Object p1, final Object p2, final Object p3, 376 final Object p4, final Object p5, final Object p6, 377 final Object p7, final Object p8) { 378 return isEnabled(level, marker); 379 } 380 381 @Override 382 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, 383 final Object p1, final Object p2, final Object p3, 384 final Object p4, final Object p5, final Object p6, 385 final Object p7, final Object p8, final Object p9) { 386 return isEnabled(level, marker); 387 } 388 389 @Override 390 public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) { 391 return isEnabled(level, marker); 392 } 393 394 @Override 395 public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) { 396 return isEnabled(level, marker); 397 } 398 399 @Override 400 public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) { 401 return isEnabled(level, marker); 402 } 403 404 @Override 405 public boolean isEnabled(final Level level, final Marker marker) { 406 // LOG4J2-1813 if system property "log4j2.debug" is defined, all status logging is enabled 407 if (isDebugPropertyEnabled()) { 408 return true; 409 } 410 if (listeners.size() > 0) { 411 return listenersLevel >= level.intLevel(); 412 } 413 return logger.isEnabled(level, marker); 414 } 415 416 /** 417 * Queues for status events. 418 * 419 * @param <E> Object type to be stored in the queue. 420 */ 421 private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> { 422 423 private static final long serialVersionUID = -3945953719763255337L; 424 425 private final int size; 426 427 BoundedQueue(final int size) { 428 this.size = size; 429 } 430 431 @Override 432 public boolean add(final E object) { 433 super.add(object); 434 while (messages.size() > size) { 435 messages.poll(); 436 } 437 return size > 0; 438 } 439 } 440}