Line 0
Link Here
|
|
|
1 |
/* |
2 |
* Copyright 1999,2004 The Apache Software Foundation. |
3 |
* |
4 |
* Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
* you may not use this file except in compliance with the License. |
6 |
* You may obtain a copy of the License at |
7 |
* |
8 |
* http://www.apache.org/licenses/LICENSE-2.0 |
9 |
* |
10 |
* Unless required by applicable law or agreed to in writing, software |
11 |
* distributed under the License is distributed on an "AS IS" BASIS, |
12 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
* See the License for the specific language governing permissions and |
14 |
* limitations under the License. |
15 |
*/ |
16 |
|
17 |
package org.apache.log4j.concurrent; |
18 |
|
19 |
import org.apache.log4j.Layout; |
20 |
import org.apache.log4j.Appender; |
21 |
import org.apache.log4j.Priority; |
22 |
import org.apache.log4j.helpers.ReaderWriterLock; |
23 |
import org.apache.log4j.spi.ComponentBase; |
24 |
import org.apache.log4j.spi.Filter; |
25 |
import org.apache.log4j.spi.LoggingEvent; |
26 |
import org.apache.log4j.spi.OptionHandler; |
27 |
|
28 |
/** |
29 |
* Base class for appenders that can benefit from a concurrency strategy. |
30 |
* Classes derived from this appender may have the {@link #append} method |
31 |
* called by multiple threads. Derived classes must also override {@link |
32 |
* #internalClose}. |
33 |
* <p> |
34 |
* Locking strategy: Internally, there is a read-write lock to handle |
35 |
* concurrent modification. A <i>write</i> lock is obtained to change states |
36 |
* (including before {@link #close}.) A <i>read</i> lock is obtained to read |
37 |
* options. Subclasses interested in state may check state using a public |
38 |
* method or within their own {@link #append} method. |
39 |
* </p> |
40 |
* <p> |
41 |
* This class is heavily based on the {@link |
42 |
* #org.apache.log4j.AppenderSkeleton} class. It may be a useful base class |
43 |
* for creating appenders that can benefit from concurrent I/O access. |
44 |
* </p> |
45 |
* |
46 |
* @see #getWriteLock |
47 |
*/ |
48 |
public abstract class ConcurrentAppender |
49 |
extends ComponentBase implements Appender, OptionHandler |
50 |
{ |
51 |
|
52 |
/** |
53 |
* The layout variable does not need to be set if the appender |
54 |
* implementation has its own layout. |
55 |
*/ |
56 |
private Layout layout; |
57 |
|
58 |
/** |
59 |
* The name of this appender. |
60 |
*/ |
61 |
protected String name; |
62 |
|
63 |
/** |
64 |
* There is no level threshold filtering by default. |
65 |
*/ |
66 |
private volatile Priority threshold; |
67 |
|
68 |
/** |
69 |
* Internal class, internally locked. |
70 |
*/ |
71 |
private FilterChain filters = new FilterChain(); |
72 |
|
73 |
/** |
74 |
* Is this appender closed? |
75 |
*/ |
76 |
private SynchronizedBoolean closed = new SynchronizedBoolean(false); |
77 |
|
78 |
/** |
79 |
* Set to true when the appender is activated. |
80 |
* Subclasses can set this to false to indicate things are not in order. |
81 |
*/ |
82 |
protected SynchronizedBoolean active = new SynchronizedBoolean(false); |
83 |
|
84 |
/** |
85 |
* The guard prevents an appender from repeatedly calling its own doAppend |
86 |
* method. This prevents same-thread re-entry looping. |
87 |
*/ |
88 |
private ThreadLocal guard = new ThreadLocal(); |
89 |
|
90 |
/** |
91 |
* A write lock is obtained to change options, a read lock is obtained to |
92 |
* append events. |
93 |
*/ |
94 |
private ReaderWriterLock lock = new ReaderWriterLock(); |
95 |
|
96 |
|
97 |
/** |
98 |
* Constructs a ConcurrentAppender. |
99 |
* |
100 |
* @param isActive true if appender is ready for use upon construction. |
101 |
*/ |
102 |
protected ConcurrentAppender(final boolean isActive) { |
103 |
active.set(isActive); |
104 |
} |
105 |
|
106 |
/** |
107 |
* Derived appenders should override this method if option structure |
108 |
* requires it. |
109 |
* By default, sets {@link #active} to true. |
110 |
*/ |
111 |
public void activateOptions() { |
112 |
active.set(true); |
113 |
} |
114 |
|
115 |
/** |
116 |
* Indicates if the appender is active and not closed. |
117 |
*/ |
118 |
public boolean isActive() { |
119 |
return active.get() && !closed.get(); |
120 |
} |
121 |
|
122 |
/** |
123 |
* Adds a filter to end of the filter list. |
124 |
* @param filter filter to use; cannot be null |
125 |
*/ |
126 |
public void addFilter(Filter filter) { |
127 |
filters.addFilter(filter); |
128 |
} |
129 |
|
130 |
/** |
131 |
* Clears the filters chain. |
132 |
*/ |
133 |
public void clearFilters() { |
134 |
filters.clear(); |
135 |
} |
136 |
|
137 |
/** |
138 |
* Returns the first {@link Filter}. |
139 |
*/ |
140 |
public Filter getFilter() { |
141 |
return filters.getHead(); |
142 |
} |
143 |
|
144 |
/** |
145 |
* Returns the layout of this appender. May return null if not set. |
146 |
*/ |
147 |
public Layout getLayout() { |
148 |
return this.layout; |
149 |
} |
150 |
|
151 |
/** |
152 |
* Returns the name of this appender. |
153 |
*/ |
154 |
public final String getName() { |
155 |
return this.name; |
156 |
} |
157 |
|
158 |
/** |
159 |
* Returns this appender's threshold level. See the {@link #setThreshold} |
160 |
* method for the meaning of this option. |
161 |
*/ |
162 |
public Priority getThreshold() { |
163 |
return threshold; |
164 |
} |
165 |
|
166 |
/** |
167 |
* Returns true if the message level is below the appender's threshold. If |
168 |
* there is no threshold set, returns <code>true</code>. |
169 |
*/ |
170 |
public boolean isAsSevereAsThreshold(final Priority level) { |
171 |
Priority copy = threshold; |
172 |
return ((copy == null) || copy.isGreaterOrEqual(level)); |
173 |
} |
174 |
|
175 |
/** |
176 |
* Performs threshold checks and checks filters before delegating actual |
177 |
* logging to the subclasses specific {@link #append} method. |
178 |
* This implementation also checks if this thread already is logging using |
179 |
* this appender, preventing possible stack overflow. |
180 |
*/ |
181 |
public final void doAppend(LoggingEvent event) { |
182 |
|
183 |
if (!isAsSevereAsThreshold(event.getLevel())) |
184 |
return; |
185 |
|
186 |
if (!filters.accept(event)) |
187 |
return; |
188 |
|
189 |
// Prevent concurrent re-entry by this thread |
190 |
// (There might be a cheaper way to do this) |
191 |
// (Or maybe this lock is not necessary) |
192 |
if (guard.get() != null) |
193 |
return; |
194 |
|
195 |
guard.set(this); // arbitrary thread lock object |
196 |
try { |
197 |
|
198 |
lock.getReadLock(); |
199 |
try { |
200 |
|
201 |
|
202 |
if (closed.get()) { |
203 |
getNonFloodingLogger().error( |
204 |
"Attempted to use closed appender named [" + name + "]."); |
205 |
return; |
206 |
} |
207 |
|
208 |
if (!active.get()) { |
209 |
getNonFloodingLogger().error( |
210 |
"Attempted to log with inactive named [" + name + "]."); |
211 |
return; |
212 |
} |
213 |
|
214 |
append(event); |
215 |
|
216 |
} finally { |
217 |
lock.releaseReadLock(); |
218 |
} |
219 |
|
220 |
} finally { |
221 |
guard.set(null); |
222 |
} |
223 |
} |
224 |
|
225 |
/** |
226 |
* Sets the layout for this appender. Note that some appenders have their own |
227 |
* (fixed) layouts or do not use one. For example, the {@link |
228 |
* org.apache.log4j.net.SocketAppender} ignores the layout set here. |
229 |
* <p> |
230 |
* Note that the implementation of {@link Layout} must be thread-safe. |
231 |
* Common layouts such as {@link org.apache.log4j.PatternLayout} are |
232 |
* thread-safe. |
233 |
* </p> |
234 |
* |
235 |
* @param layout new layout to use; may be null |
236 |
*/ |
237 |
public void setLayout(Layout layout) { |
238 |
this.layout = layout; |
239 |
} |
240 |
|
241 |
/** |
242 |
* Sets the name of this Appender. |
243 |
*/ |
244 |
public void setName(String name) { |
245 |
this.name = name; |
246 |
} |
247 |
|
248 |
/** |
249 |
* Sets the threshold level. |
250 |
* All log events with lower level than the threshold level are ignored by |
251 |
* the appender. |
252 |
* |
253 |
* @param threshold new threshold; may be null |
254 |
*/ |
255 |
public void setThreshold(final Priority threshold) { |
256 |
this.threshold = threshold; |
257 |
} |
258 |
|
259 |
/** |
260 |
* Returns true if this appender is closed. |
261 |
* An appender, once closed, is closed forever. |
262 |
*/ |
263 |
public boolean getClosed() { |
264 |
return closed.get(); |
265 |
} |
266 |
|
267 |
/** |
268 |
* Cleans up this appender. |
269 |
* Marked as <code>final</code> to prevent subclasses from accidentally |
270 |
* overriding and forgetting to call <code>super.close()</code> or obtain a |
271 |
* write lock. |
272 |
* Calls {@link #internalClose} when completed. |
273 |
* Implementation note: Obtains a write lock before starting close. |
274 |
* Calling this method more than once does nothing. |
275 |
*/ |
276 |
public final void close() { |
277 |
boolean wasClosed; |
278 |
getWriteLock(); |
279 |
try { |
280 |
wasClosed = closed.set(true); |
281 |
} finally { |
282 |
lock.releaseWriteLock(); |
283 |
} |
284 |
|
285 |
if (!wasClosed) |
286 |
internalClose(); |
287 |
} |
288 |
|
289 |
/** |
290 |
* Called to check if the appender is closed. |
291 |
*/ |
292 |
public boolean isClosed() { |
293 |
return closed.get(); |
294 |
} |
295 |
|
296 |
public final org.apache.log4j.spi.ErrorHandler getErrorHandler() { |
297 |
return org.apache.log4j.helpers.OnlyOnceErrorHandler.INSTANCE; |
298 |
} |
299 |
|
300 |
public final void setErrorHandler(org.apache.log4j.spi.ErrorHandler eh) { |
301 |
} |
302 |
|
303 |
/** |
304 |
* Returns a string representation of this object. |
305 |
*/ |
306 |
public String toString() { |
307 |
return super.toString() + " name=" + name + |
308 |
" threshold=" + threshold + |
309 |
" layout=" + layout + |
310 |
" filters=" + filters; |
311 |
} |
312 |
|
313 |
// PROTECTED METHODS |
314 |
|
315 |
/** |
316 |
* Subclasses of <code>ConcurrentAppender</code> should implement this method |
317 |
* to perform actual logging. |
318 |
* This object holds a read lock during this method. This method may be |
319 |
* called simultaneously by multiple threads. |
320 |
*/ |
321 |
protected abstract void append(LoggingEvent event); |
322 |
|
323 |
/** |
324 |
* Subclasses must implement their own close routines. |
325 |
* This method is called by the {@link #close} method. |
326 |
* This is guaranteed to be called only once, even if {@link #close} is |
327 |
* invoked more than once. |
328 |
* Note that further locking is not required, as {@link #append} can no |
329 |
* longer be called. |
330 |
*/ |
331 |
protected abstract void internalClose(); |
332 |
|
333 |
/** |
334 |
* Obtains a write lock that blocks logging to {@link #append}. |
335 |
* This is normally done when changing output behavior, closing and reopening |
336 |
* streams, etc. Call {@link #releaseWriteLock} to resume logging. |
337 |
* <p> |
338 |
* Usage pattern: |
339 |
<pre> |
340 |
getWriteLock(); |
341 |
try { |
342 |
// ... |
343 |
} finally { |
344 |
releaseWriteLock(); |
345 |
} |
346 |
</pre> |
347 |
* Note: Do not attempt to re-acquire this lock. This lock should only be |
348 |
* used for critical sections and not for long periods of time. |
349 |
*/ |
350 |
protected void getWriteLock() { |
351 |
lock.getWriteLock(); |
352 |
} |
353 |
|
354 |
/** |
355 |
* Releases a write lock; allows calls to the {@link #append} method. |
356 |
* @see #getWriteLock |
357 |
*/ |
358 |
protected void releaseWriteLock() { |
359 |
lock.releaseWriteLock(); |
360 |
} |
361 |
|
362 |
/** |
363 |
* Finalizes this appender by calling this {@link #close} method. |
364 |
*/ |
365 |
protected void finalize() { |
366 |
if (!getClosed()) |
367 |
getLogger().debug("Finalizing appender named [{}].", name); |
368 |
close(); |
369 |
} |
370 |
|
371 |
/** |
372 |
* A simple linked-list data structure containing filters. |
373 |
*/ |
374 |
private static class FilterChain { |
375 |
|
376 |
private Filter headFilter = null; |
377 |
private Filter tailFilter = null; |
378 |
|
379 |
public synchronized boolean accept(LoggingEvent event) { |
380 |
Filter f = headFilter; |
381 |
while (f != null) { |
382 |
switch (f.decide(event)) { |
383 |
case Filter.DENY: |
384 |
return false; |
385 |
case Filter.ACCEPT: |
386 |
return true; |
387 |
case Filter.NEUTRAL: |
388 |
f = f.getNext(); |
389 |
} |
390 |
} |
391 |
return true; |
392 |
} |
393 |
|
394 |
public synchronized void addFilter(Filter newFilter) { |
395 |
if (newFilter == null) |
396 |
throw new NullPointerException(); |
397 |
if (headFilter == null) { |
398 |
headFilter = newFilter; |
399 |
tailFilter = newFilter; |
400 |
} else { |
401 |
tailFilter.setNext(newFilter); |
402 |
tailFilter = newFilter; |
403 |
} |
404 |
} |
405 |
|
406 |
public synchronized Filter getHead() { |
407 |
return headFilter; |
408 |
} |
409 |
|
410 |
public synchronized void clear() { |
411 |
headFilter = null; |
412 |
tailFilter = null; |
413 |
} |
414 |
|
415 |
public synchronized String toString() { |
416 |
StringBuffer sb = new StringBuffer(); |
417 |
Filter f = headFilter; |
418 |
while (f != null) { |
419 |
sb.append(f); |
420 |
f = f.getNext(); |
421 |
if (f != null) |
422 |
sb.append(','); |
423 |
} |
424 |
return f.toString(); |
425 |
} |
426 |
|
427 |
} |
428 |
|
429 |
} |