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 }