001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.geronimo.jetty6.requestlog; 018 019 import java.io.BufferedReader; 020 import java.io.File; 021 import java.io.FileInputStream; 022 import java.io.FilenameFilter; 023 import java.io.IOException; 024 import java.io.InputStreamReader; 025 import java.text.ParseException; 026 import java.text.SimpleDateFormat; 027 import java.util.ArrayList; 028 import java.util.Collection; 029 import java.util.Date; 030 import java.util.Iterator; 031 import java.util.LinkedList; 032 import java.util.List; 033 import java.util.regex.Matcher; 034 import java.util.regex.Pattern; 035 036 import org.apache.commons.logging.Log; 037 import org.apache.commons.logging.LogFactory; 038 import org.apache.geronimo.gbean.GBeanInfo; 039 import org.apache.geronimo.gbean.GBeanInfoBuilder; 040 import org.apache.geronimo.system.serverinfo.ServerInfo; 041 042 /** 043 * Jetty implementation of the WebAccessLog management interface. 044 * 045 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $ 046 */ 047 public class JettyLogManagerImpl implements JettyLogManager { 048 private final static Log log = LogFactory.getLog(JettyLogManagerImpl.class); 049 050 // Pattern that matches the date in the logfile name 051 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]))"); 052 private final static int GROUP_FILENAME_FULL_DATE = 1; 053 private final static int GROUP_FILENAME_YEAR = 2; 054 private final static int GROUP_FILENAME_MONTH = 4; 055 private final static int GROUP_FILENAME_DAY = 5; 056 // NOTE: The file separators are specified here rather than using something like File.separator because 057 // they are hard coded in config plans and sometimes in java code itself rather than being dependent 058 // upon the OS. This should be fixed someday, but for now we will manually check for either format. 059 private final static String FILE_SEPARATOR_UNIX_STYLE = "/"; 060 private final static String FILE_SEPARATOR_WIN_STYLE = "\\"; 061 062 // Pattern that matches a single line (used to calculate line numbers) 063 private final static Pattern FULL_LINE_PATTERN = Pattern.compile("^.*", Pattern.MULTILINE); 064 private final static Pattern ACCESS_LOG_PATTERN = Pattern.compile("(\\S*) (\\S*) (\\S*) \\[(.*)\\] \\\"(\\S*) (\\S*).*?\\\" (\\S*) (\\S*).*"); 065 private final static int GROUP_HOST = 1; 066 private final static int GROUP_USER = 3; 067 private final static int GROUP_DATE = 4; 068 private final static int GROUP_METHOD = 5; 069 private final static int GROUP_URI = 6; 070 private final static int GROUP_RESPONSE_CODE = 7; 071 private final static int GROUP_RESPONSE_LENGTH = 8; 072 private final static String ACCESS_LOG_DATE_FORMAT = "dd/MMM/yyyy:HH:mm:ss ZZZZ"; 073 private final static String LOG_FILE_NAME_FORMAT = "yyyy_MM_dd"; 074 private final Collection logGbeans; 075 private final ServerInfo serverInfo; 076 077 public JettyLogManagerImpl(ServerInfo serverInfo, Collection logGbeans) { 078 this.serverInfo = serverInfo; 079 this.logGbeans = logGbeans; 080 } 081 082 /** 083 * Gets the name of all logs used by this system. Typically there 084 * is only one, but specialized cases may use more. 085 * 086 * @return An array of all log names 087 * 088 */ 089 public String[] getLogNames() { 090 List logNames = new ArrayList(); 091 for (Iterator it = logGbeans.iterator(); it.hasNext();) { 092 JettyRequestLog jettyLog = (JettyRequestLog) it.next(); 093 if(jettyLog.getFilename() != null) { 094 logNames.add(jettyLog.getFilename()); 095 } 096 } 097 return (String[]) logNames.toArray(new String[logNames.size()]); 098 } 099 100 /** 101 * Gets the names of all log files for this log name. 102 * 103 * @param logName The name of the log for which to return the specific file names. 104 * 105 * @return An array of log file names 106 * 107 */ 108 public String[] getLogFileNames(String logName) { 109 List names = new ArrayList(); 110 111 // Find all the files for this logName 112 File[] logFiles = getLogFiles(logName); 113 114 if (logFiles !=null) { 115 for (int i = 0; i < logFiles.length; i++) { 116 names.add(logFiles[i].getName()); 117 } 118 } 119 return (String[]) names.toArray(new String[names.size()]); 120 } 121 122 /** 123 * Gets the name of all log files used by this log. Typically there 124 * is only one, but specialized cases may use more. 125 * 126 * @param logName The name of the log for which to return the specific files. 127 * 128 * @return An array of all log file names 129 * 130 */ 131 private File[] getLogFiles(String logName) { 132 File[] logFiles = null; 133 134 try { 135 String fileNamePattern = logName; 136 if (fileNamePattern.indexOf(FILE_SEPARATOR_UNIX_STYLE) > -1) { 137 fileNamePattern = fileNamePattern.substring(fileNamePattern.lastIndexOf(FILE_SEPARATOR_UNIX_STYLE) + 1); 138 } else if (fileNamePattern.indexOf(FILE_SEPARATOR_WIN_STYLE) > -1) { 139 fileNamePattern = fileNamePattern.substring(fileNamePattern.lastIndexOf(FILE_SEPARATOR_WIN_STYLE) + 1); 140 } 141 142 String logFile = serverInfo.resolveServerPath(logName); 143 144 File parent = new File(logFile).getParentFile(); 145 146 if (parent != null) { 147 logFiles = parent.listFiles(new PatternFilenameFilter(fileNamePattern)); 148 } 149 } catch (Exception e) { 150 log.error("Exception attempting to locate Jetty log files", e); 151 logFiles = new File[0]; 152 } 153 return logFiles; 154 } 155 156 /** 157 * Searches the log for records matching the specified parameters. The 158 * maximum results returned will be the lesser of 1000 and the 159 * provided maxResults argument. 160 * 161 * @see #MAX_SEARCH_RESULTS 162 */ 163 public SearchResults getMatchingItems(String logName, String host, String user, String method, String uri, Date startDate, 164 Date endDate, Integer skipResults, Integer maxResults) { 165 166 // Clean up the arguments so we know what we've really got 167 if(host != null && host.equals("")) host = null; 168 if(user != null && user.equals("")) user = null; 169 if(method != null && method.equals("")) method = null; 170 if(uri != null && uri.equals("")) uri = null; 171 172 long start = startDate == null ? 0 : startDate.getTime(); 173 long end = endDate == null ? 0 : endDate.getTime(); 174 175 List list = new LinkedList(); 176 boolean capped = false; 177 int lineCount = 0, fileCount = 0; 178 179 // Find all the files for this logName 180 File logFiles[] = getLogFiles(logName); 181 182 if (logFiles !=null) { 183 for (int i = 0; i < logFiles.length; i++) { 184 fileCount = 0; 185 FileInputStream logInputStream = null; 186 try { 187 // Obtain the date for the current log file 188 String fileName = logFiles[i].getName(); 189 Matcher fileDate = FILENAME_DATE_PATTERN.matcher(fileName); 190 fileDate.find(); 191 SimpleDateFormat simpleFileDate = new SimpleDateFormat(LOG_FILE_NAME_FORMAT); 192 long logFileTime = simpleFileDate.parse(fileDate.group(GROUP_FILENAME_FULL_DATE)).getTime(); 193 194 // Check if the dates are null (ignore) or fall within the search range 195 if ( (start==0 && end==0) 196 || (start>0 && start<=logFileTime && end>0 && end>=logFileTime)) { 197 198 // It's in the range, so process the file 199 logInputStream = new FileInputStream(logFiles[i]); 200 BufferedReader reader = new BufferedReader(new InputStreamReader(logInputStream, "US-ASCII")); 201 202 Matcher target = ACCESS_LOG_PATTERN.matcher(""); 203 SimpleDateFormat format = (start == 0 && end == 0) ? null : new SimpleDateFormat(ACCESS_LOG_DATE_FORMAT); 204 int max = maxResults == null ? MAX_SEARCH_RESULTS : Math.min(maxResults.intValue(), MAX_SEARCH_RESULTS); 205 206 String line; 207 while ((line = reader.readLine()) != null) { 208 ++lineCount; 209 ++fileCount; 210 if(capped) { 211 continue; 212 } 213 target.reset(line); 214 if(target.find()) { 215 if(host != null && !host.equals(target.group(GROUP_HOST))) { 216 continue; 217 } 218 if(user != null && !user.equals(target.group(GROUP_USER))) { 219 continue; 220 } 221 if(method != null && !method.equals(target.group(GROUP_METHOD))) { 222 continue; 223 } 224 if(uri != null && !target.group(GROUP_URI).startsWith(uri)) { 225 continue; 226 } 227 if(format != null) { 228 try { 229 long entry = format.parse(target.group(GROUP_DATE)).getTime(); 230 if(start > entry) { 231 continue; 232 } 233 if(end > 0 && end < entry) { 234 continue; 235 } 236 } catch (ParseException e) { 237 // can't read the date, guess this record counts. 238 } 239 } 240 if(skipResults != null && skipResults.intValue() > lineCount) { 241 continue; 242 } 243 if(list.size() > max) { 244 capped = true; 245 continue; 246 } 247 list.add(new LogMessage(fileCount,line.toString())); 248 } 249 } 250 } 251 } catch (Exception e) { 252 log.error("Unexpected error processing logs", e); 253 } finally { 254 if (logInputStream != null) { 255 try { 256 logInputStream.close(); 257 } catch (IOException e) { 258 // ignore 259 } 260 } 261 } 262 } 263 } 264 return new SearchResults(lineCount, (LogMessage[]) list.toArray(new LogMessage[list.size()]), capped); 265 } 266 267 268 public static final GBeanInfo GBEAN_INFO; 269 270 static { 271 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("Jetty Log Manager", JettyLogManagerImpl.class); 272 infoFactory.addReference("LogGBeans", JettyRequestLog.class); 273 infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean"); 274 infoFactory.addInterface(JettyLogManager.class); 275 276 infoFactory.setConstructor(new String[]{"ServerInfo","LogGBeans"}); 277 GBEAN_INFO = infoFactory.getBeanInfo(); 278 } 279 280 public static GBeanInfo getGBeanInfo() { 281 return GBEAN_INFO; 282 } 283 284 /* 285 * Static inner class implementation of java.io.Filename. This will help us 286 * filter for only the files that we are interested in. 287 */ 288 static class PatternFilenameFilter implements FilenameFilter { 289 Pattern pattern; 290 //todo: put this pattern in a GBean parameter? 291 PatternFilenameFilter(String fileNamePattern) { 292 fileNamePattern = fileNamePattern.replaceAll("yyyy", "\\\\d{4}"); 293 fileNamePattern = fileNamePattern.replaceAll("yy", "\\\\d{2}"); 294 fileNamePattern = fileNamePattern.replaceAll("mm", "\\\\d{2}"); 295 fileNamePattern = fileNamePattern.replaceAll("dd", "\\\\d{2}"); 296 this.pattern = Pattern.compile(fileNamePattern); 297 } 298 299 public boolean accept(File file, String fileName) { 300 return pattern.matcher(fileName).matches(); 301 } 302 } 303 }