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