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