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