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