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 void setLevel(final Level level) { 078 logger.setLevel(level); 079 } 080 081 /** 082 * Register a new listener. 083 * @param listener The StatusListener to register. 084 */ 085 public void registerListener(final StatusListener listener) { 086 listenersLock.writeLock().lock(); 087 try { 088 listeners.add(listener); 089 } finally { 090 listenersLock.writeLock().unlock(); 091 } 092 } 093 094 /** 095 * Remove a StatusListener. 096 * @param listener The StatusListener to remove. 097 */ 098 public void removeListener(final StatusListener listener) { 099 listenersLock.writeLock().lock(); 100 try { 101 listeners.remove(listener); 102 } finally { 103 listenersLock.writeLock().unlock(); 104 } 105 } 106 107 /** 108 * Returns a thread safe Iterator for the StatusListener. 109 * @return An Iterator for the list of StatusListeners. 110 */ 111 public Iterator<StatusListener> getListeners() { 112 return listeners.iterator(); 113 } 114 115 /** 116 * Clears the list of status events and listeners. 117 */ 118 public void reset() { 119 listeners.clear(); 120 clear(); 121 } 122 123 /** 124 * Returns a List of all events as StatusData objects. 125 * @return The list of StatusData objects. 126 */ 127 public List<StatusData> getStatusData() { 128 msgLock.lock(); 129 try { 130 return new ArrayList<StatusData>(messages); 131 } finally { 132 msgLock.unlock(); 133 } 134 } 135 136 /** 137 * Clears the list of status events. 138 */ 139 public void clear() { 140 msgLock.lock(); 141 try { 142 messages.clear(); 143 } finally { 144 msgLock.unlock(); 145 } 146 } 147 148 149 /** 150 * Add an event. 151 * @param marker The Marker 152 * @param fqcn The fully qualified class name of the <b>caller</b> 153 * @param level The logging level 154 * @param msg The message associated with the event. 155 * @param t A Throwable or null. 156 */ 157 @Override 158 public void log(final Marker marker, final String fqcn, final Level level, final Message msg, final Throwable t) { 159 StackTraceElement element = null; 160 if (fqcn != null) { 161 element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace()); 162 } 163 final StatusData data = new StatusData(element, level, msg, t); 164 msgLock.lock(); 165 try { 166 messages.add(data); 167 } finally { 168 msgLock.unlock(); 169 } 170 if (listeners.size() > 0) { 171 for (final StatusListener listener : listeners) { 172 listener.log(data); 173 } 174 } else { 175 logger.log(marker, fqcn, level, msg, t); 176 } 177 } 178 179 private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) { 180 if (fqcn == null) { 181 return null; 182 } 183 boolean next = false; 184 for (final StackTraceElement element : stackTrace) { 185 if (next) { 186 return element; 187 } 188 final String className = element.getClassName(); 189 if (fqcn.equals(className)) { 190 next = true; 191 } else if (NOT_AVAIL.equals(className)) { 192 break; 193 } 194 } 195 return null; 196 } 197 198 @Override 199 protected boolean isEnabled(final Level level, final Marker marker, final String data) { 200 return isEnabled(level, marker); 201 } 202 203 @Override 204 protected boolean isEnabled(final Level level, final Marker marker, final String data, final Throwable t) { 205 return isEnabled(level, marker); 206 } 207 208 @Override 209 protected boolean isEnabled(final Level level, final Marker marker, final String data, final Object... p1) { 210 return isEnabled(level, marker); 211 } 212 213 @Override 214 protected boolean isEnabled(final Level level, final Marker marker, final Object data, final Throwable t) { 215 return isEnabled(level, marker); 216 } 217 218 @Override 219 protected boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) { 220 return isEnabled(level, marker); 221 } 222 223 protected boolean isEnabled(final Level level, final Marker marker) { 224 if (listeners.size() > 0) { 225 return true; 226 } 227 switch (level) { 228 case FATAL: 229 return logger.isFatalEnabled(marker); 230 case TRACE: 231 return logger.isTraceEnabled(marker); 232 case DEBUG: 233 return logger.isDebugEnabled(marker); 234 case INFO: 235 return logger.isInfoEnabled(marker); 236 case WARN: 237 return logger.isWarnEnabled(marker); 238 case ERROR: 239 return logger.isErrorEnabled(marker); 240 } 241 return false; 242 } 243 244 /** 245 * Queue for status events. 246 * @param <E> Object type to be stored in the queue. 247 */ 248 private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> { 249 250 private static final long serialVersionUID = -3945953719763255337L; 251 252 private final int size; 253 254 public BoundedQueue(final int size) { 255 this.size = size; 256 } 257 258 @Override 259 public boolean add(final E object) { 260 while (messages.size() > size) { 261 messages.poll(); 262 } 263 return super.add(object); 264 } 265 } 266 }