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.simple.SimpleLoggerContext;
39 import org.apache.logging.log4j.spi.AbstractLogger;
40 import org.apache.logging.log4j.util.Constants;
41 import org.apache.logging.log4j.util.PropertiesUtil;
42 import org.apache.logging.log4j.util.Strings;
43
44
45
46
47
48
49
50
51
52
53 public final class StatusLogger extends AbstractLogger {
54
55
56
57
58
59 public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
60
61
62
63
64
65 public static final String DEFAULT_STATUS_LISTENER_LEVEL = "log4j2.StatusLogger.level";
66
67
68
69
70
71
72 public static final String STATUS_DATE_FORMAT = "log4j2.StatusLogger.DateFormat";
73
74 private static final long serialVersionUID = 2L;
75
76 private static final String NOT_AVAIL = "?";
77
78 private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
79
80 private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
81
82 private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty(DEFAULT_STATUS_LISTENER_LEVEL);
83
84
85 private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(),
86 ParameterizedNoReferenceMessageFactory.INSTANCE);
87
88 private final SimpleLogger logger;
89
90 private final Collection<StatusListener> listeners = new CopyOnWriteArrayList<>();
91
92 @SuppressWarnings("NonSerializableFieldInSerializableClass")
93
94 private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
95
96 private final Queue<StatusData> messages = new BoundedQueue<>(MAX_ENTRIES);
97
98 @SuppressWarnings("NonSerializableFieldInSerializableClass")
99
100 private final Lock msgLock = new ReentrantLock();
101
102 private int listenersLevel;
103
104 private StatusLogger(final String name, final MessageFactory messageFactory) {
105 super(name, messageFactory);
106 final String dateFormat = PROPS.getStringProperty(STATUS_DATE_FORMAT, Strings.EMPTY);
107 final boolean showDateTime = !Strings.isEmpty(dateFormat);
108 this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, showDateTime, false,
109 dateFormat, messageFactory, PROPS, System.err);
110 this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
111
112
113 if (isDebugPropertyEnabled()) {
114 logger.setLevel(Level.TRACE);
115 }
116 }
117
118
119 private boolean isDebugPropertyEnabled() {
120 return PropertiesUtil.getProperties().getBooleanProperty(Constants.LOG4J2_DEBUG, false, true);
121 }
122
123
124
125
126
127
128 public static StatusLogger getLogger() {
129 return STATUS_LOGGER;
130 }
131
132 public void setLevel(final Level level) {
133 logger.setLevel(level);
134 }
135
136
137
138
139
140
141 public void registerListener(final StatusListener listener) {
142 listenersLock.writeLock().lock();
143 try {
144 listeners.add(listener);
145 final Level lvl = listener.getStatusLevel();
146 if (listenersLevel < lvl.intLevel()) {
147 listenersLevel = lvl.intLevel();
148 }
149 } finally {
150 listenersLock.writeLock().unlock();
151 }
152 }
153
154
155
156
157
158
159 public void removeListener(final StatusListener listener) {
160 closeSilently(listener);
161 listenersLock.writeLock().lock();
162 try {
163 listeners.remove(listener);
164 int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
165 for (final StatusListener statusListener : listeners) {
166 final int level = statusListener.getStatusLevel().intLevel();
167 if (lowest < level) {
168 lowest = level;
169 }
170 }
171 listenersLevel = lowest;
172 } finally {
173 listenersLock.writeLock().unlock();
174 }
175 }
176
177 public void updateListenerLevel(final Level status) {
178 if (status.intLevel() > listenersLevel) {
179 listenersLevel = status.intLevel();
180 }
181 }
182
183
184
185
186
187
188 public Iterable<StatusListener> getListeners() {
189 return listeners;
190 }
191
192
193
194
195 public void reset() {
196 listenersLock.writeLock().lock();
197 try {
198 for (final StatusListener listener : listeners) {
199 closeSilently(listener);
200 }
201 } finally {
202 listeners.clear();
203 listenersLock.writeLock().unlock();
204
205 clear();
206 }
207 }
208
209 private static void closeSilently(final Closeable resource) {
210 try {
211 resource.close();
212 } catch (final IOException ignored) {
213
214 }
215 }
216
217
218
219
220
221
222 public List<StatusData> getStatusData() {
223 msgLock.lock();
224 try {
225 return new ArrayList<>(messages);
226 } finally {
227 msgLock.unlock();
228 }
229 }
230
231
232
233
234 public void clear() {
235 msgLock.lock();
236 try {
237 messages.clear();
238 } finally {
239 msgLock.unlock();
240 }
241 }
242
243 @Override
244 public Level getLevel() {
245 return logger.getLevel();
246 }
247
248
249
250
251
252
253
254
255
256
257 @Override
258 public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg,
259 final Throwable t) {
260 StackTraceElement element = null;
261 if (fqcn != null) {
262 element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
263 }
264 final StatusData data = new StatusData(element, level, msg, t, null);
265 msgLock.lock();
266 try {
267 messages.add(data);
268 } finally {
269 msgLock.unlock();
270 }
271
272 if (isDebugPropertyEnabled()) {
273 logger.logMessage(fqcn, level, marker, msg, t);
274 } else {
275 if (listeners.size() > 0) {
276 for (final StatusListener listener : listeners) {
277 if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
278 listener.log(data);
279 }
280 }
281 } else {
282 logger.logMessage(fqcn, level, marker, msg, t);
283 }
284 }
285 }
286
287 private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
288 if (fqcn == null) {
289 return null;
290 }
291 boolean next = false;
292 for (final StackTraceElement element : stackTrace) {
293 final String className = element.getClassName();
294 if (next && !fqcn.equals(className)) {
295 return element;
296 }
297 if (fqcn.equals(className)) {
298 next = true;
299 } else if (NOT_AVAIL.equals(className)) {
300 break;
301 }
302 }
303 return null;
304 }
305
306 @Override
307 public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
308 return isEnabled(level, marker);
309 }
310
311 @Override
312 public boolean isEnabled(final Level level, final Marker marker, final String message) {
313 return isEnabled(level, marker);
314 }
315
316 @Override
317 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
318 return isEnabled(level, marker);
319 }
320
321 @Override
322 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) {
323 return isEnabled(level, marker);
324 }
325
326 @Override
327 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
328 final Object p1) {
329 return isEnabled(level, marker);
330 }
331
332 @Override
333 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
334 final Object p1, final Object p2) {
335 return isEnabled(level, marker);
336 }
337
338 @Override
339 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
340 final Object p1, final Object p2, final Object p3) {
341 return isEnabled(level, marker);
342 }
343
344 @Override
345 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
346 final Object p1, final Object p2, final Object p3,
347 final Object p4) {
348 return isEnabled(level, marker);
349 }
350
351 @Override
352 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
353 final Object p1, final Object p2, final Object p3,
354 final Object p4, final Object p5) {
355 return isEnabled(level, marker);
356 }
357
358 @Override
359 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
360 final Object p1, final Object p2, final Object p3,
361 final Object p4, final Object p5, final Object p6) {
362 return isEnabled(level, marker);
363 }
364
365 @Override
366 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
367 final Object p1, final Object p2, final Object p3,
368 final Object p4, final Object p5, final Object p6,
369 final Object p7) {
370 return isEnabled(level, marker);
371 }
372
373 @Override
374 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
375 final Object p1, final Object p2, final Object p3,
376 final Object p4, final Object p5, final Object p6,
377 final Object p7, final Object p8) {
378 return isEnabled(level, marker);
379 }
380
381 @Override
382 public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
383 final Object p1, final Object p2, final Object p3,
384 final Object p4, final Object p5, final Object p6,
385 final Object p7, final Object p8, final Object p9) {
386 return isEnabled(level, marker);
387 }
388
389 @Override
390 public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) {
391 return isEnabled(level, marker);
392 }
393
394 @Override
395 public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
396 return isEnabled(level, marker);
397 }
398
399 @Override
400 public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
401 return isEnabled(level, marker);
402 }
403
404 @Override
405 public boolean isEnabled(final Level level, final Marker marker) {
406
407 if (isDebugPropertyEnabled()) {
408 return true;
409 }
410 if (listeners.size() > 0) {
411 return listenersLevel >= level.intLevel();
412 }
413 return logger.isEnabled(level, marker);
414 }
415
416
417
418
419
420
421 private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
422
423 private static final long serialVersionUID = -3945953719763255337L;
424
425 private final int size;
426
427 BoundedQueue(final int size) {
428 this.size = size;
429 }
430
431 @Override
432 public boolean add(final E object) {
433 super.add(object);
434 while (messages.size() > size) {
435 messages.poll();
436 }
437 return size > 0;
438 }
439 }
440 }