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 /** 145 * Returns a thread safe Iterable for the StatusListener. 146 * 147 * @return An Iterable for the list of StatusListeners. 148 */ 149 public Iterable<StatusListener> getListeners() { 150 return listeners; 151 } 152 153 /** 154 * Clears the list of status events and listeners. 155 */ 156 public void reset() { 157 listenersLock.writeLock().lock(); 158 try { 159 for (final StatusListener listener : listeners) { 160 closeSilently(listener); 161 } 162 } finally { 163 listeners.clear(); 164 listenersLock.writeLock().unlock(); 165 // note this should certainly come after the unlock to avoid unnecessary nested locking 166 clear(); 167 } 168 } 169 170 private static void closeSilently(final Closeable resource) { 171 try { 172 resource.close(); 173 } catch (final IOException ignored) { 174 // ignored 175 } 176 } 177 178 /** 179 * Returns a List of all events as StatusData objects. 180 * 181 * @return The list of StatusData objects. 182 */ 183 public List<StatusData> getStatusData() { 184 msgLock.lock(); 185 try { 186 return new ArrayList<>(messages); 187 } finally { 188 msgLock.unlock(); 189 } 190 } 191 192 /** 193 * Clears the list of status events. 194 */ 195 public void clear() { 196 msgLock.lock(); 197 try { 198 messages.clear(); 199 } finally { 200 msgLock.unlock(); 201 } 202 } 203 204 @Override 205 public Level getLevel() { 206 return logger.getLevel(); 207 } 208 209 /** 210 * Adds an event. 211 * 212 * @param marker The Marker 213 * @param fqcn The fully qualified class name of the <b>caller</b> 214 * @param level The logging level 215 * @param msg The message associated with the event. 216 * @param t A Throwable or null. 217 */ 218 @Override 219 public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg, 220 final Throwable t) { 221 StackTraceElement element = null; 222 if (fqcn != null) { 223 element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace()); 224 } 225 final StatusData data = new StatusData(element, level, msg, t, null); 226 msgLock.lock(); 227 try { 228 messages.add(data); 229 } finally { 230 msgLock.unlock(); 231 } 232 if (listeners.size() > 0) { 233 for (final StatusListener listener : listeners) { 234 if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) { 235 listener.log(data); 236 } 237 } 238 } else { 239 logger.logMessage(fqcn, level, marker, msg, t); 240 } 241 } 242 243 private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) { 244 if (fqcn == null) { 245 return null; 246 } 247 boolean next = false; 248 for (final StackTraceElement element : stackTrace) { 249 final String className = element.getClassName(); 250 if (next && !fqcn.equals(className)) { 251 return element; 252 } 253 if (fqcn.equals(className)) { 254 next = true; 255 } else if (NOT_AVAIL.equals(className)) { 256 break; 257 } 258 } 259 return null; 260 } 261 262 @Override 263 public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) { 264 return isEnabled(level, marker); 265 } 266 267 @Override 268 public boolean isEnabled(final Level level, final Marker marker, final String message) { 269 return isEnabled(level, marker); 270 } 271 272 @Override 273 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) { 274 return isEnabled(level, marker); 275 } 276 277 @Override 278 public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) { 279 return isEnabled(level, marker); 280 } 281 282 @Override 283 public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) { 284 return isEnabled(level, marker); 285 } 286 287 @Override 288 public boolean isEnabled(final Level level, final Marker marker) { 289 if (listeners.size() > 0) { 290 return listenersLevel >= level.intLevel(); 291 } 292 return logger.isEnabled(level, marker); 293 } 294 295 /** 296 * Queues for status events. 297 * 298 * @param <E> Object type to be stored in the queue. 299 */ 300 private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> { 301 302 private static final long serialVersionUID = -3945953719763255337L; 303 304 private final int size; 305 306 public BoundedQueue(final int size) { 307 this.size = size; 308 } 309 310 @Override 311 public boolean add(final E object) { 312 super.add(object); 313 while (messages.size() > size) { 314 messages.poll(); 315 } 316 return size > 0; 317 } 318 } 319}