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
67 @SuppressWarnings("NonSerializableFieldInSerializableClass")
68 private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
69
70 private final Queue<StatusData> messages = new BoundedQueue<StatusData>(MAX_ENTRIES);
71
72 @SuppressWarnings("NonSerializableFieldInSerializableClass")
73 private final Lock msgLock = new ReentrantLock();
74
75 private int listenersLevel;
76
77 private StatusLogger() {
78 this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, Strings.EMPTY, null, PROPS,
79 System.err);
80 this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
81 }
82
83
84
85
86
87 public static StatusLogger getLogger() {
88 return STATUS_LOGGER;
89 }
90
91 public void setLevel(final Level level) {
92 logger.setLevel(level);
93 }
94
95
96
97
98
99 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
114
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
136
137
138 public Iterable<StatusListener> getListeners() {
139 return listeners;
140 }
141
142
143
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
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
168
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
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
198
199
200
201
202
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
282
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 }