View Javadoc

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