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