View Javadoc

1   /**
2    * Logback: the generic, reliable, fast and flexible logging framework.
3    * 
4    * Copyright (C) 1999-2006, QOS.ch
5    * 
6    * This library is free software, you can redistribute it and/or modify it under
7    * the terms of the GNU Lesser General Public License as published by the Free
8    * Software Foundation.
9    */
10  
11  // Contributors: Dan MacDonald <dan@redknee.com>
12  package ch.qos.logback.core.net;
13  
14  import java.io.IOException;
15  import java.io.ObjectOutputStream;
16  import java.net.InetAddress;
17  import java.net.Socket;
18  
19  import ch.qos.logback.core.AppenderBase;
20  import ch.qos.logback.core.CoreConstants;
21  
22  /**
23   * 
24   * This is the base class for module specific SocketAppender implementations.
25   * 
26   * @author Ceki G&uuml;lc&uuml;
27   * @author S&eacute;bastien Pennec
28   */
29  
30  public abstract class SocketAppenderBase<E> extends AppenderBase<E> {
31  
32    /**
33     * The default port number of remote logging server (4560).
34     */
35    static final int DEFAULT_PORT = 4560;
36  
37    /**
38     * The default reconnection delay (30000 milliseconds or 30 seconds).
39     */
40    static final int DEFAULT_RECONNECTION_DELAY = 30000;
41  
42    /**
43     * We remember host name as String in addition to the resolved InetAddress so
44     * that it can be returned via getOption().
45     */
46    protected String remoteHost;
47  
48    protected InetAddress address;
49    protected int port = DEFAULT_PORT;
50    protected ObjectOutputStream oos;
51    protected int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
52  
53    private Connector connector;
54  
55    protected int counter = 0;
56  
57    /**
58     * Start this appender.
59     */
60    public void start() {
61      int errorCount = 0;
62      if (port == 0) {
63        errorCount++;
64        addError("No port was configured for appender"
65            + name
66            + " For more information, please visit http://logback.qos.ch/codes.html#socket_no_port");
67      }
68  
69      if (address == null) {
70        errorCount++;
71        addError("No remote address was configured for appender"
72            + name
73            + " For more information, please visit http://logback.qos.ch/codes.html#socket_no_host");
74      }
75  
76      connect(address, port);
77  
78      if (errorCount == 0) {
79        this.started = true;
80      }
81    }
82  
83    /**
84     * Strop this appender.
85     * 
86     * <p>
87     * This will mark the appender as closed and call then {@link #cleanUp}
88     * method.
89     */
90    @Override
91    public void stop() {
92      if (!isStarted())
93        return;
94  
95      this.started = false;
96      cleanUp();
97    }
98  
99    /**
100    * Drop the connection to the remote host and release the underlying connector
101    * thread if it has been created
102    */
103   public void cleanUp() {
104     if (oos != null) {
105       try {
106         oos.close();
107       } catch (IOException e) {
108         addError("Could not close oos.", e);
109       }
110       oos = null;
111     }
112     if (connector != null) {
113       addInfo("Interrupting the connector.");
114       connector.interrupted = true;
115       connector = null; // allow gc
116     }
117   }
118 
119   void connect(InetAddress address, int port) {
120     if (this.address == null)
121       return;
122     try {
123       // First, close the previous connection if any.
124       cleanUp();
125       oos = new ObjectOutputStream(new Socket(address, port).getOutputStream());
126     } catch (IOException e) {
127 
128       String msg = "Could not connect to remote logback server at ["
129           + address.getHostName() + "].";
130       if (reconnectionDelay > 0) {
131         msg += " We will try again later.";
132         fireConnector(); // fire the connector thread
133       }
134       addError(msg, e);
135     }
136   }
137 
138   @Override
139   protected void append(E event) {
140 
141     if (event == null)
142       return;
143 
144     if (address == null) {
145       addError("No remote host is set for SocketAppender named \""
146           + this.name
147           + "\". For more information, please visit http://logback.qos.ch/codes.html#socket_no_host");
148       return;
149     }
150 
151     if (oos != null) {
152       try {
153         postProcessEvent(event);
154         oos.writeObject(event);
155         // addInfo("=========Flushing.");
156         oos.flush();
157         if (++counter >= CoreConstants.OOS_RESET_FREQUENCY) {
158           counter = 0;
159           // Failing to reset the object output stream every now and
160           // then creates a serious memory leak.
161           // System.err.println("Doing oos.reset()");
162           oos.reset();
163         }
164       } catch (IOException e) {
165         if (oos != null) {
166           try {
167             oos.close();
168           } catch (IOException ignore) {
169           }
170         }
171 
172         oos = null;
173         addWarn("Detected problem with connection: " + e);
174         if (reconnectionDelay > 0) {
175           fireConnector();
176         }
177       }
178     }
179   }
180 
181   protected abstract void postProcessEvent(E event);
182 
183   void fireConnector() {
184     if (connector == null) {
185       addInfo("Starting a new connector thread.");
186       connector = new Connector();
187       connector.setDaemon(true);
188       connector.setPriority(Thread.MIN_PRIORITY);
189       connector.start();
190     }
191   }
192 
193   protected static InetAddress getAddressByName(String host) {
194     try {
195       return InetAddress.getByName(host);
196     } catch (Exception e) {
197       // addError("Could not find address of [" + host + "].", e);
198       return null;
199     }
200   }
201 
202   /**
203    * The <b>RemoteHost</b> option takes a string value which should be the host
204    * name of the server where a {@link SocketNode} is running.
205    */
206   public void setRemoteHost(String host) {
207     address = getAddressByName(host);
208     remoteHost = host;
209   }
210 
211   /**
212    * Returns value of the <b>RemoteHost</b> option.
213    */
214   public String getRemoteHost() {
215     return remoteHost;
216   }
217 
218   /**
219    * The <b>Port</b> option takes a positive integer representing the port
220    * where the server is waiting for connections.
221    */
222   public void setPort(int port) {
223     this.port = port;
224   }
225 
226   /**
227    * Returns value of the <b>Port</b> option.
228    */
229   public int getPort() {
230     return port;
231   }
232 
233   /**
234    * The <b>ReconnectionDelay</b> option takes a positive integer representing
235    * the number of milliseconds to wait between each failed connection attempt
236    * to the server. The default value of this option is 30000 which corresponds
237    * to 30 seconds.
238    * 
239    * <p>
240    * Setting this option to zero turns off reconnection capability.
241    */
242   public void setReconnectionDelay(int delay) {
243     this.reconnectionDelay = delay;
244   }
245 
246   /**
247    * Returns value of the <b>ReconnectionDelay</b> option.
248    */
249   public int getReconnectionDelay() {
250     return reconnectionDelay;
251   }
252 
253   /**
254    * The Connector will reconnect when the server becomes available again. It
255    * does this by attempting to open a new connection every
256    * <code>reconnectionDelay</code> milliseconds.
257    * 
258    * <p>
259    * It stops trying whenever a connection is established. It will restart to
260    * try reconnect to the server when previpously open connection is droppped.
261    * 
262    * @author Ceki G&uuml;lc&uuml;
263    * @since 0.8.4
264    */
265   class Connector extends Thread {
266 
267     boolean interrupted = false;
268 
269     public void run() {
270       Socket socket;
271       while (!interrupted) {
272         try {
273           sleep(reconnectionDelay);
274           addInfo("Attempting connection to " + address.getHostName());
275           socket = new Socket(address, port);
276           synchronized (this) {
277             oos = new ObjectOutputStream(socket.getOutputStream());
278             connector = null;
279             addInfo("Connection established. Exiting connector thread.");
280             break;
281           }
282         } catch (InterruptedException e) {
283           addInfo("Connector interrupted. Leaving loop.");
284           return;
285         } catch (java.net.ConnectException e) {
286           addInfo("Remote host " + address.getHostName()
287               + " refused connection.");
288         } catch (IOException e) {
289           addInfo("Could not connect to " + address.getHostName()
290               + ". Exception is " + e);
291         }
292       }
293       // addInfo("Exiting Connector.run() method.");
294     }
295 
296     /**
297      * public void finalize() { LogLog.debug("Connector finalize() has been
298      * called."); }
299      */
300   }
301 
302 }