1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.log4j.net;
19
20 import java.util.Vector;
21 import java.net.Socket;
22 import java.net.ServerSocket;
23 import java.net.SocketException;
24 import java.io.ObjectOutputStream;
25 import java.io.IOException;
26 import java.io.InterruptedIOException;
27 import java.net.InetAddress;
28
29 import org.apache.log4j.helpers.LogLog;
30 import org.apache.log4j.spi.LoggingEvent;
31 import org.apache.log4j.AppenderSkeleton;
32
33 /***
34 Sends {@link LoggingEvent} objects to a set of remote log servers,
35 usually a {@link SocketNode SocketNodes}.
36
37 <p>Acts just like {@link SocketAppender} except that instead of
38 connecting to a given remote log server,
39 <code>SocketHubAppender</code> accepts connections from the remote
40 log servers as clients. It can accept more than one connection.
41 When a log event is received, the event is sent to the set of
42 currently connected remote log servers. Implemented this way it does
43 not require any update to the configuration file to send data to
44 another remote log server. The remote log server simply connects to
45 the host and port the <code>SocketHubAppender</code> is running on.
46
47 <p>The <code>SocketHubAppender</code> does not store events such
48 that the remote side will events that arrived after the
49 establishment of its connection. Once connected, events arrive in
50 order as guaranteed by the TCP protocol.
51
52 <p>This implementation borrows heavily from the {@link
53 SocketAppender}.
54
55 <p>The SocketHubAppender has the following characteristics:
56
57 <ul>
58
59 <p><li>If sent to a {@link SocketNode}, logging is non-intrusive as
60 far as the log event is concerned. In other words, the event will be
61 logged with the same time stamp, {@link org.apache.log4j.NDC},
62 location info as if it were logged locally.
63
64 <p><li><code>SocketHubAppender</code> does not use a layout. It
65 ships a serialized {@link LoggingEvent} object to the remote side.
66
67 <p><li><code>SocketHubAppender</code> relies on the TCP
68 protocol. Consequently, if the remote side is reachable, then log
69 events will eventually arrive at remote client.
70
71 <p><li>If no remote clients are attached, the logging requests are
72 simply dropped.
73
74 <p><li>Logging events are automatically <em>buffered</em> by the
75 native TCP implementation. This means that if the link to remote
76 client is slow but still faster than the rate of (log) event
77 production, the application will not be affected by the slow network
78 connection. However, if the network connection is slower then the
79 rate of event production, then the local application can only
80 progress at the network rate. In particular, if the network link to
81 the the remote client is down, the application will be blocked.
82
83 <p>On the other hand, if the network link is up, but the remote
84 client is down, the client will not be blocked when making log
85 requests but the log events will be lost due to client
86 unavailability.
87
88 <p>The single remote client case extends to multiple clients
89 connections. The rate of logging will be determined by the slowest
90 link.
91
92 <p><li>If the JVM hosting the <code>SocketHubAppender</code> exits
93 before the <code>SocketHubAppender</code> is closed either
94 explicitly or subsequent to garbage collection, then there might
95 be untransmitted data in the pipe which might be lost. This is a
96 common problem on Windows based systems.
97
98 <p>To avoid lost data, it is usually sufficient to {@link #close}
99 the <code>SocketHubAppender</code> either explicitly or by calling
100 the {@link org.apache.log4j.LogManager#shutdown} method before
101 exiting the application.
102
103 </ul>
104
105 @author Mark Womack */
106
107 public class SocketHubAppender extends AppenderSkeleton {
108
109 /***
110 The default port number of the ServerSocket will be created on. */
111 static final int DEFAULT_PORT = 4560;
112
113 private int port = DEFAULT_PORT;
114 private Vector oosList = new Vector();
115 private ServerMonitor serverMonitor = null;
116 private boolean locationInfo = false;
117
118 public SocketHubAppender() { }
119
120 /***
121 Connects to remote server at <code>address</code> and <code>port</code>. */
122 public
123 SocketHubAppender(int _port) {
124 port = _port;
125 startServer();
126 }
127
128 /***
129 Set up the socket server on the specified port. */
130 public
131 void activateOptions() {
132 startServer();
133 }
134
135 /***
136 Close this appender.
137 <p>This will mark the appender as closed and
138 call then {@link #cleanUp} method. */
139 synchronized
140 public
141 void close() {
142 if(closed)
143 return;
144
145 LogLog.debug("closing SocketHubAppender " + getName());
146 this.closed = true;
147 cleanUp();
148 LogLog.debug("SocketHubAppender " + getName() + " closed");
149 }
150
151 /***
152 Release the underlying ServerMonitor thread, and drop the connections
153 to all connected remote servers. */
154 public
155 void cleanUp() {
156
157 LogLog.debug("stopping ServerSocket");
158 serverMonitor.stopMonitor();
159 serverMonitor = null;
160
161
162 LogLog.debug("closing client connections");
163 while (oosList.size() != 0) {
164 ObjectOutputStream oos = (ObjectOutputStream)oosList.elementAt(0);
165 if(oos != null) {
166 try {
167 oos.close();
168 }
169 catch(IOException e) {
170 LogLog.error("could not close oos.", e);
171 }
172
173 oosList.removeElementAt(0);
174 }
175 }
176 }
177
178 /***
179 Append an event to all of current connections. */
180 public
181 void append(LoggingEvent event) {
182
183 if(event == null || oosList.size() == 0)
184 return;
185
186
187 if (locationInfo) {
188 event.getLocationInformation();
189 }
190
191
192 for (int streamCount = 0; streamCount < oosList.size(); streamCount++) {
193
194 ObjectOutputStream oos = null;
195 try {
196 oos = (ObjectOutputStream)oosList.elementAt(streamCount);
197 }
198 catch (ArrayIndexOutOfBoundsException e) {
199
200
201
202 }
203
204
205 if (oos == null)
206 break;
207
208 try {
209 oos.writeObject(event);
210 oos.flush();
211
212
213
214 oos.reset();
215 }
216 catch(IOException e) {
217
218 oosList.removeElementAt(streamCount);
219 LogLog.debug("dropped connection");
220
221
222 streamCount--;
223 }
224 }
225 }
226
227 /***
228 The SocketHubAppender does not use a layout. Hence, this method returns
229 <code>false</code>. */
230 public
231 boolean requiresLayout() {
232 return false;
233 }
234
235 /***
236 The <b>Port</b> option takes a positive integer representing
237 the port where the server is waiting for connections. */
238 public
239 void setPort(int _port) {
240 port = _port;
241 }
242
243 /***
244 Returns value of the <b>Port</b> option. */
245 public
246 int getPort() {
247 return port;
248 }
249
250 /***
251 The <b>LocationInfo</b> option takes a boolean value. If true,
252 the information sent to the remote host will include location
253 information. By default no location information is sent to the server. */
254 public
255 void setLocationInfo(boolean _locationInfo) {
256 locationInfo = _locationInfo;
257 }
258
259 /***
260 Returns value of the <b>LocationInfo</b> option. */
261 public
262 boolean getLocationInfo() {
263 return locationInfo;
264 }
265
266 /***
267 Start the ServerMonitor thread. */
268 private
269 void startServer() {
270 serverMonitor = new ServerMonitor(port, oosList);
271 }
272
273 /***
274 This class is used internally to monitor a ServerSocket
275 and register new connections in a vector passed in the
276 constructor. */
277 private
278 class ServerMonitor implements Runnable {
279 private int port;
280 private Vector oosList;
281 private boolean keepRunning;
282 private Thread monitorThread;
283
284 /***
285 Create a thread and start the monitor. */
286 public
287 ServerMonitor(int _port, Vector _oosList) {
288 port = _port;
289 oosList = _oosList;
290 keepRunning = true;
291 monitorThread = new Thread(this);
292 monitorThread.setDaemon(true);
293 monitorThread.start();
294 }
295
296 /***
297 Stops the monitor. This method will not return until
298 the thread has finished executing. */
299 public
300 synchronized
301 void stopMonitor() {
302 if (keepRunning) {
303 LogLog.debug("server monitor thread shutting down");
304 keepRunning = false;
305 try {
306 monitorThread.join();
307 }
308 catch (InterruptedException e) {
309
310 }
311
312
313 monitorThread = null;
314 LogLog.debug("server monitor thread shut down");
315 }
316 }
317
318 /***
319 Method that runs, monitoring the ServerSocket and adding connections as
320 they connect to the socket. */
321 public
322 void run() {
323 ServerSocket serverSocket = null;
324 try {
325 serverSocket = new ServerSocket(port);
326 serverSocket.setSoTimeout(1000);
327 }
328 catch (Exception e) {
329 LogLog.error("exception setting timeout, shutting down server socket.", e);
330 keepRunning = false;
331 return;
332 }
333
334 try {
335 try {
336 serverSocket.setSoTimeout(1000);
337 }
338 catch (SocketException e) {
339 LogLog.error("exception setting timeout, shutting down server socket.", e);
340 return;
341 }
342
343 while (keepRunning) {
344 Socket socket = null;
345 try {
346 socket = serverSocket.accept();
347 }
348 catch (InterruptedIOException e) {
349
350 }
351 catch (SocketException e) {
352 LogLog.error("exception accepting socket, shutting down server socket.", e);
353 keepRunning = false;
354 }
355 catch (IOException e) {
356 LogLog.error("exception accepting socket.", e);
357 }
358
359
360 if (socket != null) {
361 try {
362 InetAddress remoteAddress = socket.getInetAddress();
363 LogLog.debug("accepting connection from " + remoteAddress.getHostName()
364 + " (" + remoteAddress.getHostAddress() + ")");
365
366
367 ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
368
369
370 oosList.addElement(oos);
371 }
372 catch (IOException e) {
373 LogLog.error("exception creating output stream on socket.", e);
374 }
375 }
376 }
377 }
378 finally {
379
380 try {
381 serverSocket.close();
382 }
383 catch (IOException e) {
384
385 }
386 }
387 }
388 }
389 }
390