View Javadoc

1   /*
2    * Copyright (c) 2006-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.danube.support.logging;
14  
15  import org.abstracthorizon.danube.connection.Connection;
16  import org.abstracthorizon.danube.connection.ConnectionHandler;
17  import org.abstracthorizon.danube.support.RuntimeIOException;
18  
19  import java.io.File;
20  import java.io.FileNotFoundException;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.net.InetAddress;
25  import java.net.InetSocketAddress;
26  import java.net.Socket;
27  import java.text.MessageFormat;
28  import java.util.Date;
29  import java.util.regex.Pattern;
30  
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  /**
35   * Connection handler that logs connection to a file.
36   *
37   * @author Daniel Sendula
38   */
39  public class LoggingConnectionHandler implements ConnectionHandler {
40  
41      /** Logger */
42      protected final Logger logger = LoggerFactory.getLogger(getClass());
43  
44      /** Connection handler connection to be passed to */
45      private ConnectionHandler connectionHandler;
46  
47      /** Is logging switched on or off */
48      private boolean logging = true;
49  
50      /** Is it direction/readable text logging or not */
51      private boolean directional = true;
52  
53      /** Should all input be logged on the temporary basis */
54      private boolean tempLogging = false;
55  
56      /** Client socket address pattern string */
57      private String addressPatternString;
58  
59      /** Client socket address pattern */
60      protected Pattern addressPattern;
61  
62      /** Path of log files */
63      private File logsPath;
64  
65      /** Log file name pattern string */
66      private String logFileNamePatternString;
67  
68      /** Log file name pattern */
69      protected String logFileNamePattern;
70  
71      /** Should remote host names be resolved for address pattern */
72      private boolean resolveRemoteHostNames = false;
73  
74      /**
75       * Constructor
76       */
77      public LoggingConnectionHandler() {
78          setLogsPath(new File(System.getProperty("java.io.tmpdir")));
79          setAddressPattern(".*");
80          setLogFileNamePattern("log-%D-%T-%a:%p.log");
81      }
82  
83      /**
84       * Returns address pattern.
85       *
86       * @return returns address pattern.
87       */
88      public String getAddressPattern() {
89          return addressPatternString;
90      }
91  
92      /**
93       * Sets socket address pattern. Only socket host addresses (or names, see {@link #setResolveRemoteHostNames(boolean))
94       * that match this pattern will create log files or temporary log files.
95       *
96       *
97       * @param addressPatternString
98       */
99      public void setAddressPattern(String addressPatternString) {
100         this.addressPatternString = addressPatternString;
101         this.addressPattern = Pattern.compile(addressPatternString);
102     }
103 
104     /**
105      * Returns connection handler
106      * @return connection handler
107      */
108     public ConnectionHandler getConnectionHandler() {
109         return connectionHandler;
110     }
111 
112     /**
113      * Sets connection handler
114      * @param connectionHandler connection handler
115      */
116     public void setConnectionHandler(ConnectionHandler connectionHandler) {
117         this.connectionHandler = connectionHandler;
118     }
119 
120     /**
121      * Is logging directional or not
122      * @return if logging is directional
123      */
124     public boolean isDirectional() {
125         return directional;
126     }
127 
128     /**
129      * Sets for logging to be directional or not
130      * @param directional is logging directional or not
131      */
132     public void setDirectional(boolean directional) {
133         this.directional = directional;
134     }
135 
136     /**
137      * Returns log file name pattern
138      *
139      * @return log file name pattern
140      */
141     public String getLogFileNamePattern() {
142         return logFileNamePatternString;
143     }
144 
145     /**
146      * Sets the log file name pattern. The following
147      * pattern codes are supported:
148      * <ul>
149      * <li><code>%c</code> - current time milliseconds as a long string</li>
150      * <li><code>%D</code> - current date</li>
151      * <li><code>%T</code> - current time</li>
152      * <li><code>%A</code> - local address</li>
153      * <li><code>%P</code> - local port</li>
154      * <li><code>%a</code> - remote address</li>
155      * <li><code>%p</code> - remote port</li>
156      * </ul>
157      *
158      * @return
159      */
160     public void setLogFileNamePattern(String logFileNamePatternString) {
161         this.logFileNamePatternString = logFileNamePatternString;
162         this.logFileNamePattern = logFileNamePatternString
163                                 .replaceAll("%c", "{1,number,#}")
164                                 .replaceAll("%D", "{0,date,yyyyMMdd}")
165                                 .replaceAll("%T", "{0,time,HHmmssSSSS}")
166                                 .replaceAll("%A", "{4}")
167                                 .replaceAll("%a", "{2}")
168                                 .replaceAll("%P", "{5,number,#}")
169                                 .replaceAll("%p", "{3,number,#}")
170                                 ;
171     }
172 
173     /**
174      * Returns if logging is switched on or off. If it is switched off
175      * no logging will occur for current connection
176      *
177      * @return if logging is switched on or off
178      */
179     public boolean isLogging() {
180         return logging;
181     }
182 
183     /**
184      * Switches logging on or off
185      * @param logging <code>true</code> if logging is to be switched on
186      */
187     public void setLogging(boolean logging) {
188         this.logging = logging;
189     }
190 
191     /**
192      * Returns log files path
193      * @return log files path
194      */
195     public File getLogsPath() {
196         return logsPath;
197     }
198 
199     /**
200      * Sets log files path
201      * @param logsPath log files path
202      */
203     public void setLogsPath(File logsPath) {
204         this.logsPath = logsPath;
205     }
206 
207     /**
208      * Returns if temporary logs be created or not.
209      *
210      * @return is temporary logging switched on
211      */
212     public boolean isTempLogging() {
213         return tempLogging;
214     }
215 
216     /**
217      * Sets temporary logging. If address is matched then log is going to be
218      * permanent. If address is not matched and temporary logging is on then log is going to be
219      * created as temporary log. Temporary log is removed at the end of the connection handling unless
220      * its state is changed within {@link LoggingConnection} itself.
221      *
222      *
223      * @param tempLogging
224      */
225     public void setTempLogging(boolean tempLogging) {
226         this.tempLogging = tempLogging;
227     }
228 
229     /**
230      * Returns if host names should be resolved or not. It is used in
231      * matching remote socket address ({@link #setAddressPattern(String)})
232      *
233      * @return if host names should be resolved or not.
234      */
235     public boolean isResolveRemoteHostNames() {
236         return resolveRemoteHostNames;
237     }
238 
239     /**
240      * Sets if remote host names are to be resolved or not. It is used in
241      * matching remote socket address ({@link #setAddressPattern(String)})
242      *
243      * @param resolveRemoteHostNames
244      */
245     public void setResolveRemoteHostNames(boolean resolveRemoteHostNames) {
246         this.resolveRemoteHostNames = resolveRemoteHostNames;
247     }
248 
249     /**
250      * This method wrapps connection to logging connection and passes it further.
251      * Will connection be wrapped or not depetns on
252      * {@link #isLogging()}, {@link #getAddressPattern()} and {@link #isTempLogging()}.
253      *
254      * @param connection original connection
255      */
256     public void handleConnection(Connection connection) {
257         boolean log = isLogging();
258         boolean temporary = false;
259         if (log) {
260             boolean socketMatched = false;
261             Socket socket = (Socket)connection.adapt(Socket.class);
262             if (socket != null) {
263                 String remoteHost = null;
264                 InetAddress remoteAddress = ((InetSocketAddress)socket.getRemoteSocketAddress()).getAddress();
265                 if (isResolveRemoteHostNames()) {
266                     remoteHost = remoteAddress.getHostName();
267                 } else {
268                     remoteHost = remoteAddress.getHostAddress();
269                 }
270                 socketMatched = addressPattern.matcher(remoteHost).matches();
271             }
272             if (!socketMatched) {
273                 if (isTempLogging()) {
274                     temporary = true;
275                 } else {
276                     log = false;
277                 }
278             } else {
279                 temporary = false;
280             }
281         }
282 
283         if (log) {
284             OutputStream logOutputStream = createLogOutputStream(connection, temporary);
285             LoggingConnection loggingConnection = null;
286             try {
287                 loggingConnection = new LoggingConnection(connection, logOutputStream, directional, temporary);
288                 loggingConnection.setTemporaryLog(temporary);
289                 connectionHandler.handleConnection(loggingConnection);
290             } finally {
291                 // Ensure logOutputStream is closed
292                 closeOutputStream(loggingConnection, logOutputStream);
293             }
294         } else {
295             connectionHandler.handleConnection(connection);
296         }
297     }
298 
299     /**
300      * This method creates log output stream.
301      * This implementation returns {@link InternalFileOutputStream} but it can be overriden
302      * with any other output stream.
303      *
304      * @param connection original connection
305      * @param temporary is file supposed to be temporary or not
306      * @return log output stream
307      */
308     protected OutputStream createLogOutputStream(Connection connection, boolean temporary) {
309         String fileName;
310         Socket socket = (Socket)connection.adapt(Socket.class);
311         Date now = new Date();
312         if (socket != null) {
313             InetSocketAddress remote = (InetSocketAddress)socket.getRemoteSocketAddress();
314             InetSocketAddress local = (InetSocketAddress)socket.getLocalSocketAddress();
315             fileName = MessageFormat.format(logFileNamePattern, new Object[]{
316                         now,
317                         now.getTime(),
318                         remote.getHostName(),
319                         remote.getPort(),
320                         local.getHostName(),
321                         local.getPort()
322                     }
323                 );
324         } else {
325             fileName = MessageFormat.format(logFileNamePattern, new Object[]{
326                         System.currentTimeMillis(),
327                         null,
328                         null,
329                         null,
330                         null
331                     }
332                 );
333         }
334 
335         try {
336             File file = new File(logsPath, fileName);
337             if (logger.isDebugEnabled()) {
338                 if (temporary) {
339                     logger.debug("Creating temporary log file " + file.getAbsolutePath());
340                 } else {
341                     logger.debug("Creating log file " + file.getAbsolutePath());
342                 }
343             }
344             FileOutputStream fileOutputStream = new InternalFileOutputStream(file);
345             return fileOutputStream;
346         } catch (IOException e) {
347             throw new RuntimeIOException(e);
348         }
349     }
350 
351     /**
352      * This method closes output stream and then checks if it needs to be removed or not.
353      *
354      * @param loggingConnection logging connection
355      * @param logOutputStream log output stream
356      */
357     protected void closeOutputStream(LoggingConnection loggingConnection, OutputStream logOutputStream) {
358         try {
359             logOutputStream.close();
360         } catch (IOException ignore) {
361         }
362         if ((loggingConnection == null) || loggingConnection.isTermporaryLog()) {
363             if (logOutputStream instanceof InternalFileOutputStream) {
364                 InternalFileOutputStream fileOutputStream = (InternalFileOutputStream)logOutputStream;
365                 File file = fileOutputStream.getFile();
366                 if (logger.isDebugEnabled()) {
367                     logger.debug("Removing temporary log file " + file.getAbsolutePath());
368                 }
369                 file.delete();
370             }
371         }
372     }
373 
374     /**
375      * This is helper class that adds reference to original {@link File} class
376      * passed in {@link FileOutputStream}.
377      *
378      * @author Daniel Sendula
379      */
380     public static class InternalFileOutputStream extends FileOutputStream {
381 
382         /** File */
383         protected File file;
384 
385         /**
386          * Constructor
387          * @param file file
388          * @throws FileNotFoundException file not found exception
389          */
390         public InternalFileOutputStream(File file) throws FileNotFoundException {
391             super(file);
392             this.file = file;
393         }
394 
395         /**
396          * Returns reference to original file object this stream is created with.
397          *
398          * @return reference to original file object this stream is created with
399          */
400         public File getFile() {
401             return file;
402         }
403 
404     }
405 }