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.util.ArrayList; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Queue; 023import java.util.concurrent.ConcurrentLinkedQueue; 024import java.util.concurrent.CopyOnWriteArrayList; 025import java.util.concurrent.locks.ReentrantLock; 026import java.util.concurrent.locks.ReentrantReadWriteLock; 027 028import org.apache.logging.log4j.Level; 029import org.apache.logging.log4j.Marker; 030import org.apache.logging.log4j.message.Message; 031import org.apache.logging.log4j.simple.SimpleLogger; 032import org.apache.logging.log4j.spi.AbstractLogger; 033import org.apache.logging.log4j.util.PropertiesUtil; 034 035/** 036 * Mechanism to record events that occur in the logging system. 037 */ 038public final class StatusLogger extends AbstractLogger { 039 040 private static final long serialVersionUID = 1L; 041 042 /** 043 * System property that can be configured with the number of entries in the queue. Once the limit 044 * is reached older entries will be removed as new entries are added. 045 */ 046 public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries"; 047 048 private static final String NOT_AVAIL = "?"; 049 050 private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties"); 051 052 private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200); 053 054 private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty("log4j2.StatusLogger.level"); 055 056 // private static final String FQCN = AbstractLogger.class.getName(); 057 058 private static final StatusLogger STATUS_LOGGER = new StatusLogger(); 059 060 private final SimpleLogger logger; 061 062 private final CopyOnWriteArrayList<StatusListener> listeners = new CopyOnWriteArrayList<StatusListener>(); 063 private final ReentrantReadWriteLock listenersLock = new ReentrantReadWriteLock(); 064 065 private final Queue<StatusData> messages = new BoundedQueue<StatusData>(MAX_ENTRIES); 066 private final ReentrantLock msgLock = new ReentrantLock(); 067 068 private int listenersLevel; 069 070 private StatusLogger() { 071 this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, "", null, PROPS, 072 System.err); 073 this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel(); 074 } 075 076 /** 077 * Retrieve the StatusLogger. 078 * @return The StatusLogger. 079 */ 080 public static StatusLogger getLogger() { 081 return STATUS_LOGGER; 082 } 083 084 public Level getLevel() { 085 return logger.getLevel(); 086 } 087 088 public void setLevel(final Level level) { 089 logger.setLevel(level); 090 } 091 092 /** 093 * Register a new listener. 094 * @param listener The StatusListener to register. 095 */ 096 public void registerListener(final StatusListener listener) { 097 listenersLock.writeLock().lock(); 098 try { 099 listeners.add(listener); 100 Level lvl = listener.getStatusLevel(); 101 if (listenersLevel < lvl.intLevel()) { 102 listenersLevel = lvl.intLevel(); 103 } 104 } finally { 105 listenersLock.writeLock().unlock(); 106 } 107 } 108 109 /** 110 * Remove a StatusListener. 111 * @param listener The StatusListener to remove. 112 */ 113 public void removeListener(final StatusListener listener) { 114 listenersLock.writeLock().lock(); 115 try { 116 listeners.remove(listener); 117 int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel(); 118 for (StatusListener l : listeners) { 119 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 Iterator for the StatusListener. 132 * @return An Iterator for the list of StatusListeners. 133 */ 134 public Iterator<StatusListener> getListeners() { 135 return listeners.iterator(); 136 } 137 138 /** 139 * Clears the list of status events and listeners. 140 */ 141 public void reset() { 142 listeners.clear(); 143 clear(); 144 } 145 146 /** 147 * Returns a List of all events as StatusData objects. 148 * @return The list of StatusData objects. 149 */ 150 public List<StatusData> getStatusData() { 151 msgLock.lock(); 152 try { 153 return new ArrayList<StatusData>(messages); 154 } finally { 155 msgLock.unlock(); 156 } 157 } 158 159 /** 160 * Clears the list of status events. 161 */ 162 public void clear() { 163 msgLock.lock(); 164 try { 165 messages.clear(); 166 } finally { 167 msgLock.unlock(); 168 } 169 } 170 171 172 /** 173 * Add an event. 174 * @param marker The Marker 175 * @param fqcn The fully qualified class name of the <b>caller</b> 176 * @param level The logging level 177 * @param msg The message associated with the event. 178 * @param t A Throwable or null. 179 */ 180 @Override 181 public void log(final Marker marker, final String fqcn, final Level level, final Message msg, final Throwable t) { 182 StackTraceElement element = null; 183 if (fqcn != null) { 184 element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace()); 185 } 186 final StatusData data = new StatusData(element, level, msg, t); 187 msgLock.lock(); 188 try { 189 messages.add(data); 190 } finally { 191 msgLock.unlock(); 192 } 193 if (listeners.size() > 0) { 194 for (final StatusListener listener : listeners) { 195 if (data.getLevel().isAtLeastAsSpecificAs(listener.getStatusLevel())) { 196 listener.log(data); 197 } 198 } 199 } else { 200 logger.log(marker, fqcn, level, msg, t); 201 } 202 } 203 204 private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) { 205 if (fqcn == null) { 206 return null; 207 } 208 boolean next = false; 209 for (final StackTraceElement element : stackTrace) { 210 if (next) { 211 return element; 212 } 213 final String className = element.getClassName(); 214 if (fqcn.equals(className)) { 215 next = true; 216 } else if (NOT_AVAIL.equals(className)) { 217 break; 218 } 219 } 220 return null; 221 } 222 223 @Override 224 protected boolean isEnabled(final Level level, final Marker marker, final String data) { 225 return isEnabled(level, marker); 226 } 227 228 @Override 229 protected boolean isEnabled(final Level level, final Marker marker, final String data, final Throwable t) { 230 return isEnabled(level, marker); 231 } 232 233 @Override 234 protected boolean isEnabled(final Level level, final Marker marker, final String data, final Object... p1) { 235 return isEnabled(level, marker); 236 } 237 238 @Override 239 protected boolean isEnabled(final Level level, final Marker marker, final Object data, final Throwable t) { 240 return isEnabled(level, marker); 241 } 242 243 @Override 244 protected boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) { 245 return isEnabled(level, marker); 246 } 247 248 @Override 249 public boolean isEnabled(final Level level, final Marker marker) { 250 if (listeners.size() > 0) { 251 return listenersLevel >= level.intLevel(); 252 } 253 254 switch (level.getStandardLevel()) { 255 case FATAL: 256 return logger.isFatalEnabled(marker); 257 case TRACE: 258 return logger.isTraceEnabled(marker); 259 case DEBUG: 260 return logger.isDebugEnabled(marker); 261 case INFO: 262 return logger.isInfoEnabled(marker); 263 case WARN: 264 return logger.isWarnEnabled(marker); 265 case ERROR: 266 return logger.isErrorEnabled(marker); 267 default: 268 return false; 269 } 270 } 271 272 /** 273 * Queue for status events. 274 * @param <E> Object type to be stored in the queue. 275 */ 276 private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> { 277 278 private static final long serialVersionUID = -3945953719763255337L; 279 280 private final int size; 281 282 public BoundedQueue(final int size) { 283 this.size = size; 284 } 285 286 @Override 287 public boolean add(final E object) { 288 while (messages.size() > size) { 289 messages.poll(); 290 } 291 return super.add(object); 292 } 293 } 294}