View Javadoc

1   /**
2    *
3    *  Licensed to the Apache Software Foundation (ASF) under one or more
4    *  contributor license agreements.  See the NOTICE file distributed with
5    *  this work for additional information regarding copyright ownership.
6    *  The ASF licenses this file to You under the Apache License, Version 2.0
7    *  (the "License"); you may not use this file except in compliance with
8    *  the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing, software
13   *  distributed under the License is distributed on an "AS IS" BASIS,
14   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *  See the License for the specific language governing permissions and
16   *  limitations under the License.
17   */
18  package org.apache.geronimo.jetty.requestlog;
19  
20  import java.io.File;
21  import java.io.FilenameFilter;
22  import java.io.RandomAccessFile;
23  import java.nio.CharBuffer;
24  import java.nio.MappedByteBuffer;
25  import java.nio.channels.FileChannel;
26  import java.nio.charset.Charset;
27  import java.text.ParseException;
28  import java.text.SimpleDateFormat;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Date;
32  import java.util.Iterator;
33  import java.util.LinkedList;
34  import java.util.List;
35  import java.util.regex.Matcher;
36  import java.util.regex.Pattern;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.geronimo.gbean.GBeanInfo;
41  import org.apache.geronimo.gbean.GBeanInfoBuilder;
42  import org.apache.geronimo.system.serverinfo.ServerInfo;
43  
44  /**
45   * Jetty implementation of the WebAccessLog management interface.
46   *
47   * @version $Rev: 470597 $ $Date: 2006-11-02 15:30:55 -0800 (Thu, 02 Nov 2006) $
48   */
49  public class JettyLogManagerImpl implements JettyLogManager {
50      private final static Log log = LogFactory.getLog(JettyLogManagerImpl.class);
51  
52      // Pattern that matches the date in the logfile name
53      private final static Pattern FILENAME_DATE_PATTERN = Pattern.compile("[-_ /.](((19|20)\\d\\d)[-_ /.](0[1-9]|1[012])[-_ /.](0[1-9]|[12][0-9]|3[01]))");
54      private final static int GROUP_FILENAME_FULL_DATE = 1;
55      private final static int GROUP_FILENAME_YEAR  = 2;
56      private final static int GROUP_FILENAME_MONTH = 4;
57      private final static int GROUP_FILENAME_DAY   = 5;
58      // NOTE:  The file separators are specified here rather than using something like File.separator because
59      //        they are hard coded in config plans and sometimes in java code itself rather than being dependent
60      //        upon the OS.  This should be fixed someday, but for now we will manually check for either format.
61      private final static String FILE_SEPARATOR_UNIX_STYLE = "/";
62      private final static String FILE_SEPARATOR_WIN_STYLE = "\\";
63  
64      // Pattern that matches a single line  (used to calculate line numbers)
65      private final static Pattern FULL_LINE_PATTERN = Pattern.compile("^.*", Pattern.MULTILINE);
66      private final static Pattern ACCESS_LOG_PATTERN = Pattern.compile("(\\S*) (\\S*) (\\S*) \\[(.*)\\] \\\"(\\S*) (\\S*).*?\\\" (\\S*) (\\S*).*");
67      private final static int GROUP_HOST = 1;
68      private final static int GROUP_USER = 3;
69      private final static int GROUP_DATE = 4;
70      private final static int GROUP_METHOD = 5;
71      private final static int GROUP_URI = 6;
72      private final static int GROUP_RESPONSE_CODE = 7;
73      private final static int GROUP_RESPONSE_LENGTH = 8;
74      private final static String ACCESS_LOG_DATE_FORMAT = "dd/MMM/yyyy:HH:mm:ss ZZZZ";
75      private final static String LOG_FILE_NAME_FORMAT = "yyyy_MM_dd";
76      private final Collection logGbeans;   
77      private final ServerInfo serverInfo;  
78  
79      public JettyLogManagerImpl(ServerInfo serverInfo, Collection logGbeans) {
80          this.serverInfo = serverInfo;
81          this.logGbeans = logGbeans;
82      }
83  
84      /**
85       * Gets the name of all logs used by this system.  Typically there
86       * is only one, but specialized cases may use more.
87       *
88       * @return An array of all log names
89       *
90       */
91      public String[] getLogNames() {
92          List logNames = new ArrayList();
93          for (Iterator it = logGbeans.iterator(); it.hasNext();) {
94              JettyRequestLog jettyLog = (JettyRequestLog) it.next();
95              if(jettyLog.getFilename() != null) {
96                  logNames.add(jettyLog.getFilename());
97              }
98          }
99          return (String[]) logNames.toArray(new String[logNames.size()]);
100     }
101 
102     /**
103      * Gets the names of all log files for this log name.  
104      *
105      * @param logName The name of the log for which to return the specific file names.
106      *
107      * @return An array of log file names
108      *
109      */
110     public String[] getLogFileNames(String logName) {
111         List names = new ArrayList();
112 
113         // Find all the files for this logName
114         File[] logFiles = getLogFiles(logName);
115 
116         if (logFiles !=null) {
117             for (int i = 0; i < logFiles.length; i++) {
118                 names.add(logFiles[i].getName());
119             }
120         }
121         return (String[]) names.toArray(new String[names.size()]);
122     }
123 
124     /**
125      * Gets the name of all log files used by this log.  Typically there
126      * is only one, but specialized cases may use more.
127      *
128      * @param logName The name of the log for which to return the specific files.
129      *
130      * @return An array of all log file names
131      *
132      */
133     private File[] getLogFiles(String logName) {
134         File[] logFiles = null;
135 
136         try {
137             String fileNamePattern = logName;
138             if (fileNamePattern.indexOf(FILE_SEPARATOR_UNIX_STYLE) > -1) {
139                 fileNamePattern = fileNamePattern.substring(fileNamePattern.lastIndexOf(FILE_SEPARATOR_UNIX_STYLE) + 1);
140             } else if (fileNamePattern.indexOf(FILE_SEPARATOR_WIN_STYLE) > -1) {
141                 fileNamePattern = fileNamePattern.substring(fileNamePattern.lastIndexOf(FILE_SEPARATOR_WIN_STYLE) + 1);
142             }
143 
144             String logFile = serverInfo.resolvePath(logName);
145 
146             File parent = new File(logFile).getParentFile();
147 
148             if (parent != null) {
149                 logFiles = parent.listFiles(new PatternFilenameFilter(fileNamePattern));
150             }
151         } catch (Exception e) {
152             log.error("Exception attempting to locate Jetty log files", e);
153             logFiles = new File[0];
154         }
155         return logFiles;
156     }
157 
158     /**
159      * Searches the log for records matching the specified parameters.  The
160      * maximum results returned will be the lesser of 1000 and the
161      * provided maxResults argument.
162      *
163      * @see #MAX_SEARCH_RESULTS
164      */
165     public SearchResults getMatchingItems(String logName, String host, String user, String method, String uri, Date startDate,
166                                           Date endDate, Integer skipResults, Integer maxResults) {
167 
168         // Clean up the arguments so we know what we've really got
169         if(host != null && host.equals("")) host = null;
170         if(user != null && user.equals("")) user = null;
171         if(method != null && method.equals("")) method = null;
172         if(uri != null && uri.equals("")) uri = null;
173 
174         long start = startDate == null ? 0 : startDate.getTime();
175         long end = endDate == null ? 0 : endDate.getTime();
176 
177         List list = new LinkedList();
178         boolean capped = false;
179         int lineCount = 0, fileCount = 0;
180 
181         // Find all the files for this logName
182         File logFiles[] = getLogFiles(logName);
183 
184         if (logFiles !=null) {
185             for (int i = 0; i < logFiles.length; i++) {
186                 fileCount = 0;
187                 try {
188                     // Obtain the date for the current log file
189                     String fileName = logFiles[i].getName();
190                     Matcher fileDate = FILENAME_DATE_PATTERN.matcher(fileName);
191                     fileDate.find();
192                     SimpleDateFormat simpleFileDate = new SimpleDateFormat(LOG_FILE_NAME_FORMAT);
193                     long logFileTime = simpleFileDate.parse(fileDate.group(GROUP_FILENAME_FULL_DATE)).getTime();
194 
195                     // Check if the dates are null (ignore) or fall within the search range
196                     if (  (start==0 && end==0)
197                        || (start>0 && start<=logFileTime && end>0 && end>=logFileTime)) {
198 
199                         // It's in the range, so process the file
200                         RandomAccessFile raf = new RandomAccessFile(logFiles[i], "r");
201                         FileChannel fc = raf.getChannel();
202                         MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
203                         CharBuffer cb = Charset.forName("US-ASCII").decode(bb); //todo: does Jetty use a different charset on a foreign PC?
204                         Matcher lines = FULL_LINE_PATTERN.matcher(cb);
205                         Matcher target = ACCESS_LOG_PATTERN.matcher("");
206                         SimpleDateFormat format = (start == 0 && end == 0) ? null : new SimpleDateFormat(ACCESS_LOG_DATE_FORMAT);
207                         int max = maxResults == null ? MAX_SEARCH_RESULTS : Math.min(maxResults.intValue(), MAX_SEARCH_RESULTS);
208 
209                         while(lines.find()) {
210                             ++lineCount;
211                             ++fileCount;
212                             if(capped) {
213                                 continue;
214                             }
215                             CharSequence line = cb.subSequence(lines.start(), lines.end());
216                             target.reset(line);
217                             if(target.find()) {
218                                 if(host != null && !host.equals(target.group(GROUP_HOST))) {
219                                     continue;
220                                 }
221                                 if(user != null && !user.equals(target.group(GROUP_USER))) {
222                                     continue;
223                                 }
224                                 if(method != null && !method.equals(target.group(GROUP_METHOD))) {
225                                     continue;
226                                 }
227                                 if(uri != null && !target.group(GROUP_URI).startsWith(uri)) {
228                                     continue;
229                                 }
230                                 if(format != null) {
231                                     try {
232                                         long entry = format.parse(target.group(GROUP_DATE)).getTime();
233                                         if(start > entry) {
234                                             continue;
235                                         }
236                                         if(end > 0 && end < entry) {
237                                             continue;
238                                         }
239                                     } catch (ParseException e) {
240                                         // can't read the date, guess this record counts.
241                                     }
242                                 }
243                                 if(skipResults != null && skipResults.intValue() > lineCount) {
244                                     continue;
245                                 }
246                                 if(list.size() > max) {
247                                     capped = true;
248                                     continue;
249                                 }
250                                 list.add(new LogMessage(fileCount,line.toString()));
251                             }
252                         }
253                         fc.close();
254                         raf.close();
255                     }
256                 } catch (Exception e) {
257                     log.error("Unexpected error processing logs", e);
258                 }
259             }
260         }
261         return new SearchResults(lineCount, (LogMessage[]) list.toArray(new LogMessage[list.size()]), capped);
262     }
263 
264 
265     public static final GBeanInfo GBEAN_INFO;
266 
267     static {
268         GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("Jetty Log Manager", JettyLogManagerImpl.class);
269         infoFactory.addReference("LogGBeans", JettyRequestLog.class);
270         infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
271         infoFactory.addInterface(JettyLogManager.class);
272 
273         infoFactory.setConstructor(new String[]{"ServerInfo","LogGBeans"});  
274         GBEAN_INFO = infoFactory.getBeanInfo();
275     }
276 
277     public static GBeanInfo getGBeanInfo() {
278         return GBEAN_INFO;
279     }
280 
281     /*
282      * Static inner class implementation of java.io.Filename. This will help us
283      * filter for only the files that we are interested in.
284      */
285     static class PatternFilenameFilter implements FilenameFilter {
286         Pattern pattern;
287         //todo: put this pattern in a GBean parameter?
288         PatternFilenameFilter(String fileNamePattern) {
289             fileNamePattern = fileNamePattern.replaceAll("yyyy", "\\\\d{4}");
290             fileNamePattern = fileNamePattern.replaceAll("yy", "\\\\d{2}");
291             fileNamePattern = fileNamePattern.replaceAll("mm", "\\\\d{2}");
292             fileNamePattern = fileNamePattern.replaceAll("dd", "\\\\d{2}");
293             this.pattern = Pattern.compile(fileNamePattern);
294         }
295 
296         public boolean accept(File file, String fileName) {
297             return pattern.matcher(fileName).matches();
298         }
299     }
300 }