|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 5 of 6
The second order of business is to override a few methods from the AppenderSkeleton superclass.
After log4j has parsed the configuration file and called any associated setters, the
activateOptions()activeOptions()
activateOptions()accept()accept()close()listenerSocketSocketException
Once the connection is established, the thread wraps the output stream for the client socket in a
WriterWriterCollectionclientsWriterCollection
The synchronization is worth mentioning. I put objects into
clients
The code that does the actual appending is in the
append()Writer
That iteration is tricky. I could have wrapped the
clients CollectionCollections.synchronizedCollection()add()
Since clients aren't added often, I've solved the problem simply by locking the
clients Collection
The only appender method that remains is an override of
close()listenerSocketaccept()
Listing 4. RemoteAppender.java: A custom log4j appender
1 package com.holub.log;
2
3 import org.apache.log4j.AppenderSkeleton;
4 import org.apache.log4j.spi.LoggingEvent;
5 import org.apache.log4j.spi.ErrorCode;
6 import org.apache.log4j.Layout;
7 import org.apache.log4j.helpers.LogLog;
8
9 import java.util.*;
10 import java.io.*;
11 import java.net.*;
12
13 /** This appender works much like log4j's Socket Appender.
14 * The main difference is that it sends strings to
15 * remote clients rather than sending serialized
16 * LoggingEvent objects. This approach has the
17 * advantages of being considerably faster (serialization
18 * is not cheap) and of not requiring the client
19 * application to be coupled to log4j at all.
20 *
21 * <p>This appender takes only one "parameter," which specifies
22 * the port number (defaults to 9999). Set it with:
23 * <PRE>
24 * log4j.appender.R=com.holub.log4j.RemoteAppender;
25 * ...
26 * log4j.appender.R.Port=1234
27 * </PRE>
28 *
29 */
30
31 public class RemoteAppender extends AppenderSkeleton
32 {
33 // The iterator across the "clients" Collection must
34 // support a "remove()" method.
35
36 private Collection clients = new LinkedList();
37 private int port = 9999;
38 private ServerSocket listenerSocket;
39 private Thread listenerThread;
40
41 private void setPort(int port) { this.port = port; }
42 private int getPort() { return this.port; }
43
44 public boolean requiresLayout(){ return true; }
45
46 /** Called once all the options have been set. Starts
47 * listening for clients on the specified socket.
48 */
49 public void activateOptions()
50 { try
51 { listenerSocket = new ServerSocket( port );
52 listenerThread = new Thread()
53 { public void run()
54 { try
55 { Socket clientSocket;
56 while( (clientSocket = listenerSocket.accept()) != null )
57 { // Create a (deliberately) unbuffered writer
58 // to talk to the client and add it to the
59 // collection of listeners.
60
61 synchronized( clients )
62 { clients.add(
63 new OutputStreamWriter(
64 clientSocket.getOutputStream()) );
65 }
66 }
67 }
68 catch( SocketException e )
69 { // Normal close operation. Doing nothing
70 // terminates the thread gracefully.
71 }
72 catch( IOException e )
73 { // Other IO errors also kill the thread, but with
74 // a logged message.
75 errorHandler.error("I/O Exception in accept loop" + e );
76 }
77 }
78 };
79 listenerThread.setDaemon( true );
80 listenerThread.start();
81 }
82 catch( IOException e )
83 { errorHandler.error("Can't open server socket: " + e );
84 }
85 }
86
87 /** Actually do the logging. The AppenderSkeleton's
88 * doAppend() method calls append() to do the
89 * actual logging after it takes care of required
90 * housekeeping operations.
91 */
92
93 public synchronized void append( LoggingEvent event )
94 {
95 // If this Appender has been closed or if there are no
96 // clients to service, just return.
97
98 if( listenerSocket== null || clients.size() <= 0 )
99 return;
100
101 if( this.layout == null )
102 { errorHandler.error("No layout for appender " + name ,
103 null, ErrorCode.MISSING_LAYOUT );
104 return;
105 }
106
107 String message = this.layout.format(event);
108
109 // Normally, an exception is thrown by the synchronized collection
110 // when somebody (i.e., the listenerThread) tries to modify it
111 // while iterations are in progress. The following synchronized
112 // statement causes the listenerThread to block in this case,
113 // but note that connections that can't be serviced quickly
114 // enough might be refused.
115
116 synchronized( clients )
117 { for( Iterator i = clients.iterator(); i.hasNext(); )
118 { Writer out = (Writer)( i.next() );
119 try
120 { out.write( message, 0, message.length() );
121 out.flush();
122 // Boilerplate code: handle exceptions if not
123 // handled by layout object:
124 //
125 if( layout.ignoresThrowable() )
126 { String[] messages = event.getThrowableStrRep();
127 if( messages != null )
128 { for( int j = 0; j < messages.length; ++j )
129 { out.write( messages[j], 0, messages[j].length() );
130 out.write( '\n' );
131 out.flush();
132 }
133 }
134 }
135 }
136 catch( IOException e ) // Assume that the write failed
137 { i.remove(); // because the connection is closed.
138 }
139 }
140 }
141 }
142
143 public synchronized void close()
144 { try
145 {
146 if( listenerSocket == null ) // Already closed.
147 return;
148 listenerSocket.close(); // Also kills listenerThread.
149
150 // Now close all the client connections.
151 for( Iterator i = clients.iterator(); i.hasNext(); )
152 { ((Writer) i.next()).close();
153 i.remove();
154 }
155
156 listenerThread.join(); // Wait for thread to die.
157 listenerThread = null; // Allow everything to be
158 listenerSocket = null; // garbage collected.
159 clients = null;
160 }
161 catch( Exception e )
162 { errorHandler.error("Exception while closing: " + e);
163 }
164 }
165 }
That's all there is to building an appender. You override a few methods of AppenderSkeleton and add getter/setter methods for the parameters. Most of the nastiness here is socket-and-thread related. The actual log4j
code was simplicity itself (extend a class and overwrite a few methods). Writing your own appenders for things like logging
to a database, for example, is equally simple.
Archived Discussions (Read only)