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