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.tomcat; 018 019 import org.apache.geronimo.gbean.GBeanInfo; 020 import org.apache.geronimo.gbean.GBeanInfoBuilder; 021 import org.apache.geronimo.system.serverinfo.ServerInfo; 022 import org.apache.commons.logging.Log; 023 import org.apache.commons.logging.LogFactory; 024 import org.apache.catalina.valves.AccessLogValve; 025 026 import java.util.*; 027 import java.util.regex.Pattern; 028 import java.util.regex.Matcher; 029 import java.io.File; 030 import java.io.FilenameFilter; 031 import java.io.RandomAccessFile; 032 import java.nio.channels.FileChannel; 033 import java.nio.MappedByteBuffer; 034 import java.nio.CharBuffer; 035 import java.nio.charset.Charset; 036 import java.text.SimpleDateFormat; 037 import java.text.ParseException; 038 039 /** 040 * Tomcat implementation of the WebAccessLog management interface. 041 * 042 * @version $Rev: 476049 $ $Date: 2006-11-16 23:35:17 -0500 (Thu, 16 Nov 2006) $ 043 */ 044 public class TomcatLogManagerImpl implements TomcatLogManager { 045 private final static Log log = LogFactory.getLog(TomcatLogManagerImpl.class); 046 047 // Pattern that matches the date in the logfile name 048 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]))"); 049 private final static int GROUP_FILENAME_FULL_DATE = 1; 050 private final static int GROUP_FILENAME_YEAR = 2; 051 private final static int GROUP_FILENAME_MONTH = 4; 052 private final static int GROUP_FILENAME_DAY = 5; 053 // NOTE: The file separators are specified here rather than using something like File.separator because 054 // they are hard coded in config plans and sometimes in java code itself rather than being dependent 055 // upon the OS. This should be fixed someday, but for now we will manually check for either format. 056 private final static String FILE_SEPARATOR_UNIX_STYLE = "/"; 057 private final static String FILE_SEPARATOR_WIN_STYLE = "\\"; 058 059 // Pattern that matches a single line (used to calculate line numbers) 060 private final static Pattern FULL_LINE_PATTERN = Pattern.compile("^.*", Pattern.MULTILINE); 061 private final static Pattern ACCESS_LOG_PATTERN = Pattern.compile("(\\S*) (\\S*) (\\S*) \\[(.*)\\] \\\"(\\S*) (\\S*).*?\\\" (\\S*) (\\S*).*"); 062 private final static int GROUP_HOST = 1; 063 private final static int GROUP_USER = 3; 064 private final static int GROUP_DATE = 4; 065 private final static int GROUP_METHOD = 5; 066 private final static int GROUP_URI = 6; 067 private final static int GROUP_RESPONSE_CODE = 7; 068 private final static int GROUP_RESPONSE_LENGTH = 8; 069 private final static String ACCESS_LOG_DATE_FORMAT = "dd/MMM/yyyy:HH:mm:ss ZZZZ"; 070 private final static String LOG_FILE_NAME_FORMAT = "yyyy-MM-dd"; 071 private final Collection logGbeans; 072 private final ServerInfo serverInfo; 073 074 public TomcatLogManagerImpl(ServerInfo serverInfo, Collection logGbeans) { 075 this.serverInfo = serverInfo; 076 this.logGbeans = logGbeans; 077 } 078 079 /** 080 * Gets the name of all logs used by this system. Typically there 081 * is only one, but specialized cases may use more. 082 * 083 * @return An array of all log names 084 * 085 */ 086 public String[] getLogNames() { 087 List logNames = new ArrayList(); 088 for (Iterator it = logGbeans.iterator(); it.hasNext();) { 089 ValveGBean logGBean = (ValveGBean) it.next(); 090 AccessLogValve logFile = (AccessLogValve) logGBean.getInternalObject(); 091 if(logFile != null) { 092 logNames.add( "var/catalina/logs/"+logFile.getPrefix()+LOG_FILE_NAME_FORMAT+logFile.getSuffix()); 093 } 094 } 095 return (String[]) logNames.toArray(new String[logNames.size()]); 096 } 097 098 /** 099 * Gets the names of all log files for this log name. 100 * 101 * @param logName The name of the log for which to return the specific file names. 102 * 103 * @return An array of log file names 104 * 105 */ 106 public String[] getLogFileNames(String logName) { 107 List names = new ArrayList(); 108 109 // Find all the files for this logName 110 File[] logFiles = getLogFiles(logName); 111 112 if (logFiles !=null) { 113 for (int i = 0; i < logFiles.length; i++) { 114 names.add(logFiles[i].getName()); 115 } 116 } 117 return (String[]) names.toArray(new String[names.size()]); 118 } 119 120 /** 121 * Gets the name of all log files used by this log. Typically there 122 * is only one, but specialized cases may use more. 123 * 124 * @param logName The name of the log for which to return the specific files. 125 * 126 * @return An array of all log file names 127 * 128 */ 129 private File[] getLogFiles(String logName) { 130 File[] logFiles = null; 131 132 try { 133 String fileNamePattern = logName; 134 if (fileNamePattern.indexOf(FILE_SEPARATOR_UNIX_STYLE) > -1) { 135 fileNamePattern = fileNamePattern.substring(fileNamePattern.lastIndexOf(FILE_SEPARATOR_UNIX_STYLE) + 1); 136 } else if (fileNamePattern.indexOf(FILE_SEPARATOR_WIN_STYLE) > -1) { 137 fileNamePattern = fileNamePattern.substring(fileNamePattern.lastIndexOf(FILE_SEPARATOR_WIN_STYLE) + 1); 138 } 139 140 String logFile = serverInfo.resolvePath(logName); 141 142 File parent = new File(logFile).getParentFile(); 143 144 if (parent != null) { 145 logFiles = parent.listFiles(new PatternFilenameFilter(fileNamePattern)); 146 } 147 } catch (Exception e) { 148 log.error("Exception attempting to locate Tomcat log files", e); 149 logFiles = new File[0]; 150 } 151 return logFiles; 152 } 153 154 /** 155 * Searches the log for records matching the specified parameters. The 156 * maximum results returned will be the lesser of 1000 and the 157 * provided maxResults argument. 158 * 159 * @see #MAX_SEARCH_RESULTS 160 */ 161 public SearchResults getMatchingItems(String logName, String host, String user, String method, String uri, 162 Date startDate, Date endDate, Integer skipResults, Integer maxResults) { 163 164 // Clean up the arguments so we know what we've really got 165 if(host != null && host.equals("")) host = null; 166 if(user != null && user.equals("")) user = null; 167 if(method != null && method.equals("")) method = null; 168 if(uri != null && uri.equals("")) uri = null; 169 170 long start = startDate == null ? 0 : startDate.getTime(); 171 long end = endDate == null ? 0 : endDate.getTime(); 172 173 List list = new LinkedList(); 174 boolean capped = false; 175 int lineCount = 0, fileLineCount = 0; 176 177 // Find all the files for this logName 178 File logFiles[] = getLogFiles(logName); 179 180 if (logFiles !=null) { 181 for (int i = 0; i < logFiles.length; i++) { 182 fileLineCount = 0; 183 try { 184 // Obtain the date for the current log file 185 String fileName = logFiles[i].getName(); 186 Matcher fileDate = FILENAME_DATE_PATTERN.matcher(fileName); 187 fileDate.find(); 188 SimpleDateFormat simpleFileDate = new SimpleDateFormat(LOG_FILE_NAME_FORMAT); 189 long logFileTime = simpleFileDate.parse(fileDate.group(GROUP_FILENAME_FULL_DATE)).getTime(); 190 Date logFileDate = new Date(logFileTime); 191 192 // Check if the dates are null (ignore) or fall within the search range 193 if ( (start==0 && end==0) 194 || (start>0 && start<=logFileTime && end>0 && end>=logFileTime)) { 195 196 // It's in the range, so process the file 197 RandomAccessFile raf = new RandomAccessFile(logFiles[i], "r"); 198 FileChannel fc = raf.getChannel(); 199 MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); 200 CharBuffer cb = Charset.forName("US-ASCII").decode(bb); //todo: does Tomcat use a different charset on a foreign PC? 201 Matcher lines = FULL_LINE_PATTERN.matcher(cb); 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 while(lines.find()) { 207 ++lineCount; 208 ++fileLineCount; 209 if(capped) { 210 continue; 211 } 212 CharSequence line = cb.subSequence(lines.start(), lines.end()); 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(fileLineCount,line.toString())); 248 } 249 } 250 fc.close(); 251 raf.close(); 252 } 253 } catch (Exception e) { 254 log.error("Unexpected error processing logs", e); 255 } 256 } 257 } 258 return new SearchResults(lineCount, (LogMessage[]) list.toArray(new LogMessage[list.size()]), capped); 259 } 260 261 262 public static final GBeanInfo GBEAN_INFO; 263 264 static { 265 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("Tomcat Log Manager", TomcatLogManagerImpl.class); 266 infoFactory.addReference("LogGBeans", ValveGBean.class); 267 infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean"); 268 infoFactory.addInterface(TomcatLogManager.class); 269 270 infoFactory.setConstructor(new String[]{"ServerInfo","LogGBeans"}); 271 GBEAN_INFO = infoFactory.getBeanInfo(); 272 } 273 274 public static GBeanInfo getGBeanInfo() { 275 return GBEAN_INFO; 276 } 277 278 /* 279 * Static inner class implementation of java.io.Filename. This will help us 280 * filter for only the files that we are interested in. 281 */ 282 static class PatternFilenameFilter implements FilenameFilter { 283 Pattern pattern; 284 285 PatternFilenameFilter(String fileNamePattern) { 286 fileNamePattern = fileNamePattern.replaceAll("yyyy", "\\\\d{4}"); 287 fileNamePattern = fileNamePattern.replaceAll("yy", "\\\\d{2}"); 288 fileNamePattern = fileNamePattern.replaceAll("mm", "\\\\d{2}"); 289 fileNamePattern = fileNamePattern.replaceAll("MM", "\\\\d{2}"); 290 fileNamePattern = fileNamePattern.replaceAll("dd", "\\\\d{2}") 291 + ".*"; 292 this.pattern = Pattern.compile(fileNamePattern); 293 } 294 295 public boolean accept(File file, String fileName) { 296 return pattern.matcher(fileName).matches(); 297 } 298 } 299 300 /* 301 public static void main(String[] args) { 302 String jetty = "127.0.0.1 - - [07/Sep/2005:19:54:41 +0000] \"GET /console/ HTTP/1.1\" 302 0 \"-\" \"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.10) Gecko/20050715 Firefox/1.0.6 SUSE/1.0.6-4.1\" -"; 303 String tomcat = "127.0.0.1 - - [07/Sep/2005:15:51:18 -0500] \"GET /console/portal/server/server_info HTTP/1.1\" 200 11708"; 304 305 SimpleDateFormat format = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss ZZZZ"); 306 try { 307 Pattern p = Pattern.compile("(\\S*) (\\S*) (\\S*) \\[(.*)\\] \\\"(\\S*) (\\S*).*?\\\" (\\S*) (\\S*).*"); 308 Matcher m = p.matcher(jetty); 309 if(m.matches()) { 310 System.out.println("Group 1: "+m.group(1)); // client 311 System.out.println("Group 2: "+m.group(2)); // ?? server host? 312 System.out.println("Group 3: "+m.group(3)); // username 313 System.out.println("Group 4: "+format.parse(m.group(4))); // date 314 System.out.println("Group 5: "+m.group(5)); // method 315 System.out.println("Group 5: "+m.group(6)); // URI 316 System.out.println("Group 6: "+m.group(7)); // response code 317 System.out.println("Group 7: "+m.group(8)); // response length 318 } else { 319 System.out.println("No match"); 320 } 321 m = p.matcher(tomcat); 322 if(m.matches()) { 323 System.out.println("Group 1: "+m.group(1)); 324 System.out.println("Group 2: "+m.group(2)); 325 System.out.println("Group 3: "+m.group(3)); 326 System.out.println("Group 4: "+format.parse(m.group(4))); 327 System.out.println("Group 5: "+m.group(5)); // method 328 System.out.println("Group 5: "+m.group(6)); // URI 329 System.out.println("Group 6: "+m.group(7)); // response code 330 System.out.println("Group 7: "+m.group(8)); // response length 331 } else { 332 System.out.println("No match"); 333 } 334 } catch (ParseException e) { 335 e.printStackTrace(); 336 } 337 } 338 */ 339 }