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