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