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