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