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