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