1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.status;
18
19 import java.io.Closeable;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.List;
24 import java.util.Queue;
25 import java.util.concurrent.ConcurrentLinkedQueue;
26 import java.util.concurrent.CopyOnWriteArrayList;
27 import java.util.concurrent.locks.Lock;
28 import java.util.concurrent.locks.ReadWriteLock;
29 import java.util.concurrent.locks.ReentrantLock;
30 import java.util.concurrent.locks.ReentrantReadWriteLock;
31
32 import org.apache.logging.log4j.Level;
33 import org.apache.logging.log4j.Marker;
34 import org.apache.logging.log4j.message.Message;
35 import org.apache.logging.log4j.message.MessageFactory;
36 import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory;
37 import org.apache.logging.log4j.simple.SimpleLogger;
38 import org.apache.logging.log4j.spi.AbstractLogger;
39 import org.apache.logging.log4j.util.PropertiesUtil;
40 import org.apache.logging.log4j.util.Strings;
41
42
43
44
45 public final class StatusLogger extends AbstractLogger {
46
47
48
49
50
51 public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
52
53 private static final long serialVersionUID = 2L;
54
55 private static final String NOT_AVAIL = "?";
56
57 private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
58
59 private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
60
61 private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty("log4j2.StatusLogger.level");
62
63
64 private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(),
65 ParameterizedNoReferenceMessageFactory.INSTANCE);
66
67 private final SimpleLogger logger;
68
69 private final Collection<StatusListener> listeners = new CopyOnWriteArrayList<>();
70
71 @SuppressWarnings("NonSerializableFieldInSerializableClass")
72
73 private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
74
75 private final Queue<StatusData> messages = new BoundedQueue<>(MAX_ENTRIES);
76
77 @SuppressWarnings("NonSerializableFieldInSerializableClass")
78
79 private final Lock msgLock = new ReentrantLock();
80
81 private int listenersLevel;
82
83 private StatusLogger(String name, MessageFactory messageFactory) {
84 super(name, messageFactory);
85 this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, Strings.EMPTY,
86 messageFactory, PROPS, System.err);
87 this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
88 }
89
90
91
92
93
94
95 public static StatusLogger getLogger() {
96 return STATUS_LOGGER;
97 }
98
99 public void setLevel(final Level level) {
100 logger.setLevel(level);
101 }
102
103
104
105
106
107
108 public void registerListener(final StatusListener listener) {
109 listenersLock.writeLock().lock();
110 try {
111 listeners.add(listener);
112 final Level lvl = listener.getStatusLevel();
113 if (listenersLevel < lvl.intLevel()) {
114 listenersLevel = lvl.intLevel();
115 }
116 } finally {
117 listenersLock.writeLock().unlock();
118 }
119 }
120
121
122
123
124
125
126 public void removeListener(final StatusListener listener) {
127 closeSilently(listener);
128 listenersLock.writeLock().lock();
129 try {
130 listeners.remove(listener);
131 int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
132 for (final StatusListener statusListener : listeners) {
133 final int level = statusListener.getStatusLevel().intLevel();
134 if (lowest < level) {
135 lowest = level;
136 }
137 }
138 listenersLevel = lowest;
139 } finally {
140 listenersLock.writeLock().unlock();
141 }
142 }
143
144
145
146
147
148
149 public Iterable<StatusListener> getListeners() {
150 return listeners;
151 }
152
153
154
155
156 public void reset() {
157 listenersLock.writeLock().lock();
158 try {
159 for (final StatusListener listener : listeners) {
160 closeSilently(listener);
161 }
162 } finally {
163 listeners.clear();
164 listenersLock.writeLock().unlock();
165
166 clear();
167 }
168 }
169
170 private static void closeSilently(final Closeable resource) {
171 try {
172 resource.close();
173 } catch (final IOException ignored) {
174
175 }
176 }
177
178
179
180
181
182
183 public List<StatusData> getStatusData() {
184 msgLock.lock();
185 try {
186 return new ArrayList<>(messages);
187 } finally {
188 msgLock.unlock();
189 }
190 }
191
192
193
194
195 public void clear() {
196 msgLock.lock();
197 try {
198 messages.clear();
199 } finally {
200 msgLock.unlock();
201 }
202 }
203
204 @Override
205 public Level getLevel() {
206 return logger.getLevel();
207 }
208
209
210
211
212
213
214
215
216
217
218 @Override
219 public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg,
220 final Throwable t) {
221 StackTraceElement element = null;
222 if (fqcn != null) {
223 element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
224 }
225 final StatusData data = new StatusData(element, level, msg, t, null);
226 msgLock.lock();
227 try {
228 messages.add(data);
229 } finally {
230 msgLock.unlock();
231 }
232 if (listeners.size() > 0) {
233 for (final StatusListener listener : listeners) {
234 if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
235 listener.log(data);
236 }
237 }
238 } else {
239 logger.logMessage(fqcn, level, marker, msg, t);
240 }
241 }
242
243 private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
244 if (fqcn == null) {
245 return null;
246 }
247 boolean next = false;
248 for (final StackTraceElement element : stackTrace) {
249 final String className = element.getClassName();
250 if (next && !fqcn.equals(className)) {
251 return element;
252 }
253 if (fqcn.equals(className)) {
254 next = true;
255 } else if (NOT_AVAIL.equals(className)) {
256 break;
257 }
258 }
259 return null;
260 }
261
262 @Override
263 public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
264 return isEnabled(level, marker);
265 }
266
267 @Override
268 public boolean isEnabled(final Level level, final Marker marker, final String message) {
269 return isEnabled(level, marker);
270 }
271
272 @Override
273 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
274 return isEnabled(level, marker);
275 }
276
277 @Override
278 public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
279 return isEnabled(level, marker);
280 }
281
282 @Override
283 public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
284 return isEnabled(level, marker);
285 }
286
287 @Override
288 public boolean isEnabled(final Level level, final Marker marker) {
289 if (listeners.size() > 0) {
290 return listenersLevel >= level.intLevel();
291 }
292 return logger.isEnabled(level, marker);
293 }
294
295
296
297
298
299
300 private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
301
302 private static final long serialVersionUID = -3945953719763255337L;
303
304 private final int size;
305
306 public BoundedQueue(final int size) {
307 this.size = size;
308 }
309
310 @Override
311 public boolean add(final E object) {
312 super.add(object);
313 while (messages.size() > size) {
314 messages.poll();
315 }
316 return size > 0;
317 }
318 }
319 }