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.connection.ConnectionWrapper;
18  import org.abstracthorizon.danube.support.logging.patternsupport.CurrentDateTimeProcessor;
19  import org.abstracthorizon.danube.support.logging.patternsupport.HandlingTimeProcessor;
20  import org.abstracthorizon.danube.support.logging.patternsupport.PatternProcessor;
21  import org.abstracthorizon.danube.support.logging.patternsupport.SocketDetailsProcessor;
22  import org.abstracthorizon.danube.support.logging.util.LogFileRotator;
23  
24  import java.io.IOException;
25  import java.io.OutputStream;
26  import java.text.MessageFormat;
27  import java.util.ArrayList;
28  import java.util.List;
29  
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /**
34   * <p>
35   * This handler implements simple access log.
36   * </p>
37   *
38   * <p>
39   * If internally defines following pattern codes (through {@link CurrentDateTimeProcessor},
40   * {@link HandlingTimeProcessor} and {@link SocketDetailsProcessor}):
41   * </p>
42   * <ul>
43   * <li><code>%A</code> - local IP address</li>
44   * <li><code>%a</code> - remote IP address</li>
45   * <li><code>%p</code> - local port number</li>
46   * <li><code>%h</code> - remote host name</li>
47   * <li><code>%T</code> - connection handling elapsed time in seconds</li>
48   * <li><code>%D</code> - connection handling elapsed time in milliseconds</li>
49   * <li><code>%t</code> - current time in Common Log Format</li>
50   * </ul>
51   * <p>
52   * Main aim was to keep as much compatibility with already known codes from Apache HTTP server
53   * </p>
54   *
55   * @author Daniel Sendula
56   */
57  public class AccessLogConnectionHandler implements ConnectionHandler {
58  
59      /** Logger */
60      protected final Logger logger = LoggerFactory.getLogger(getClass());
61  
62      /** Implementation of log rotator */
63      private LogFileRotator logFileRotator;
64  
65      /** Connection handler */
66      private ConnectionHandler connectionHandler;
67  
68      /** Log pattern */
69      private String logPattern;
70  
71      /** Custom processors' class names */
72      private List<String> customProcessors = new ArrayList<String>();
73  
74      /** Internal log pattern string */
75      protected String logPatternString;
76  
77      /** Defined providers */
78      protected PatternProcessor[] selectedProcessors;
79  
80      /** Number of arguments */
81      protected int argumentNumber;
82  
83      /**
84       * Constructor. It sets log pattern to &quot;%a %h %A %p %t %D %T&quot
85       */
86      public AccessLogConnectionHandler() {
87          String defaultLogPattern = getDefaultLogPattern();
88          if (defaultLogPattern != null) {
89              setLogPattern(defaultLogPattern);
90          }
91      }
92  
93      /**
94       * Returns default log pattern
95       * @return default log pattern
96       */
97      protected String getDefaultLogPattern() {
98          return "%a %h %A %p %t %D %T";
99      }
100 
101     /**
102      * Returns connection handler
103      * @return connection handler
104      */
105     public ConnectionHandler getConnectionHandler() {
106         return connectionHandler;
107     }
108 
109     /**
110      * Sets connection handler
111      * @param connectionHandler conneciton handler
112      */
113     public void setConnectionHandler(ConnectionHandler connectionHandler) {
114         this.connectionHandler = connectionHandler;
115     }
116 
117     /**
118      * Returns a list of custom processors' file names
119      * @return a list of custom processors' file names
120      */
121     public List<String> getCustomProcessors() {
122         return customProcessors;
123     }
124 
125     /**
126      * Sets a list of custom processors' file names
127      * @param customProcessors a list of custom processors' file names
128      */
129     public void setCustomProcessors(List<String> customProcessors) {
130         this.customProcessors = customProcessors;
131     }
132 
133     /**
134      * Returns log file rotator implementation
135      * @return log file rotator implementation
136      */
137     public LogFileRotator getLogFileRotator() {
138         return logFileRotator;
139     }
140 
141     /**
142      * Sets log file rotator implementation
143      * @param logFileRotator log file rotator implementation
144      */
145     public void setLogFileRotator(LogFileRotator logFileRotator) {
146         this.logFileRotator = logFileRotator;
147     }
148 
149     /**
150      * Returns log pattern
151      * @return log pattern
152      */
153     public String getLogPattern() {
154         return logPatternString;
155     }
156 
157     /**
158      * Sets log pattern
159      * @param logPattern log pattern
160      */
161     public void setLogPattern(String logPattern) {
162         this.logPatternString = logPattern;
163         StringBuffer message = new StringBuffer(logPattern);
164 
165         int index = 0;
166         ArrayList<String> providerClasses = new ArrayList<String>(getCustomProcessors());
167         addPredefinedProcessors(providerClasses);
168 
169         ArrayList<PatternProcessor> providers = new ArrayList<PatternProcessor>();
170         for (String className : providerClasses) {
171             try {
172                 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
173                 Class<?> cls = classLoader.loadClass(className);
174                 PatternProcessor processor = (PatternProcessor)cls.newInstance();
175                 int newIndex = processor.init(index, message);
176                 if (newIndex > index) {
177                     providers.add(processor);
178                 }
179                 index = newIndex;
180             } catch (Exception e) {
181                 logger.error("Problem with processing provider " + className, e);
182             }
183         }
184 
185         this.argumentNumber = index;
186         this.selectedProcessors = new PatternProcessor[providers.size()];
187         this.selectedProcessors = providers.toArray(this.selectedProcessors);
188 
189         this.logPattern = message.toString();
190     }
191 
192     /**
193      * <p>Adds lists of predefined processors to the lists of provider classes.</p>
194      * <p>This method adds following:</p>
195      * <ul>
196      * <li>{@link HandlingTimeProcessor}</li>
197      * <li>{@link CurrentDateTimeProcessor}</li>
198      * <li>{@link SocketDetailsProcessor}</li>
199      * </ul>
200      * @param providerClasses list of provider classes
201      */
202     protected void addPredefinedProcessors(List<String> providerClasses) {
203         if (!providerClasses.contains(HandlingTimeProcessor.class.getName())) {
204             providerClasses.add(HandlingTimeProcessor.class.getName());
205         }
206         if (!providerClasses.contains(CurrentDateTimeProcessor.class.getName())) {
207             providerClasses.add(CurrentDateTimeProcessor.class.getName());
208         }
209         if (!providerClasses.contains(SocketDetailsProcessor.class.getName())) {
210             providerClasses.add(SocketDetailsProcessor.class.getName());
211         }
212     }
213 
214     /**
215      * Invokes supplied {@link #connectionHandler} measuring time and then
216      * writes the log line
217      *
218      * @param connection connection
219      */
220     public void handleConnection(Connection connection) {
221         long start = System.currentTimeMillis();
222         try {
223             connectionHandler.handleConnection(connection);
224         } finally {
225             String logLine = createLogLine(connection, start);
226             if (logLine != null) {
227                 outputLogLine(logLine);
228             }
229             
230         }
231     }
232 
233     /**
234      * This method output log line to the log.
235      * @param logLine log line
236      */
237     protected void outputLogLine(String logLine) {
238         try {
239             OutputStream out = logFileRotator.logFile();
240             synchronized (out) {
241                 out.write(logLine.getBytes());
242                 out.write('\r');
243                 out.write('\n');
244             }
245         } catch (IOException e) {
246             logger.error("Problem writing to access log", e);
247             try {
248                 logFileRotator.rotate();
249             } catch (IOException e1) {
250                 logger.error("Problem rotating access log", e);
251             }
252         }
253     }
254     
255     /**
256      * Creates log line. This method can be overriden to prevent log from happening by returning
257      * <code>null</code>.
258      * 
259      * @param connection connection
260      * @param start start time
261      * @return log line or <code>null</code> for nothing to be logged
262      */
263     protected String createLogLine(Connection connection, long start) {
264         String logLine;
265 
266         if (selectedProcessors.length > 0) {
267             DateWrapper dateWrappedConnection = new DateWrapper(connection, start);
268 
269             Object[] arguments = new Object[argumentNumber];
270             for (PatternProcessor provider : selectedProcessors) {
271                 provider.process(dateWrappedConnection, arguments);
272             }
273             logLine = MessageFormat.format(logPattern, arguments);
274         } else {
275             logLine = logPattern;
276         }
277 
278         return logLine;
279     }
280     
281     /**
282      * Simple connection wrapper that adds handling started time in milliseconds
283      *
284      * @author Daniel Sendula
285      */
286     public static class DateWrapper extends ConnectionWrapper {
287         /** Handling started time */
288         long handlingStarted;
289 
290         /**
291          * Constructor
292          * @param connection connection to be wrapped
293          * @param handlingStarted handling started time
294          */
295         public DateWrapper(Connection connection, long handlingStarted) {
296             super(connection);
297             this.handlingStarted = handlingStarted;
298         }
299 
300         /**
301          * Returns handling started time
302          * @return handling started time
303          */
304         public long getHandlingStarted() {
305             return handlingStarted;
306         }
307 
308         /**
309          * If this class is request than returns this object otherwise calls super method
310          * @param cls class to be adapted to
311          * @return adapted object or nothing
312          */
313         @SuppressWarnings("unchecked")
314         public <T> T adapt(Class<T> cls) {
315             if (cls == DateWrapper.class) {
316                 return (T)this;
317             } else {
318                 return super.adapt(cls);
319             }
320         }
321 
322     }
323 
324 }