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