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.ArrayList;
027 import java.util.Collection;
028 import java.util.Date;
029 import java.util.Iterator;
030 import java.util.LinkedList;
031 import java.util.List;
032 import java.util.regex.Pattern;
033 import java.util.regex.Matcher;
034 import java.io.BufferedReader;
035 import java.io.File;
036 import java.io.FileInputStream;
037 import java.io.FilenameFilter;
038 import java.io.IOException;
039 import java.io.InputStreamReader;
040 import java.text.SimpleDateFormat;
041 import java.text.ParseException;
042
043 /**
044 * Tomcat implementation of the WebAccessLog management interface.
045 *
046 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
047 */
048 public class TomcatLogManagerImpl implements TomcatLogManager {
049 private final static Log log = LogFactory.getLog(TomcatLogManagerImpl.class);
050
051 // Pattern that matches the date in the logfile name
052 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]))");
053 private final static int GROUP_FILENAME_FULL_DATE = 1;
054 private final static int GROUP_FILENAME_YEAR = 2;
055 private final static int GROUP_FILENAME_MONTH = 4;
056 private final static int GROUP_FILENAME_DAY = 5;
057 // NOTE: The file separators are specified here rather than using something like File.separator because
058 // they are hard coded in config plans and sometimes in java code itself rather than being dependent
059 // upon the OS. This should be fixed someday, but for now we will manually check for either format.
060 private final static String FILE_SEPARATOR_UNIX_STYLE = "/";
061 private final static String FILE_SEPARATOR_WIN_STYLE = "\\";
062
063 // Pattern that matches a single line (used to calculate line numbers)
064 private final static Pattern FULL_LINE_PATTERN = Pattern.compile("^.*", Pattern.MULTILINE);
065 private final static Pattern ACCESS_LOG_PATTERN = Pattern.compile("(\\S*) (\\S*) (\\S*) \\[(.*)\\] \\\"(\\S*) (\\S*).*?\\\" (\\S*) (\\S*).*");
066 private final static int GROUP_HOST = 1;
067 private final static int GROUP_USER = 3;
068 private final static int GROUP_DATE = 4;
069 private final static int GROUP_METHOD = 5;
070 private final static int GROUP_URI = 6;
071 private final static int GROUP_RESPONSE_CODE = 7;
072 private final static int GROUP_RESPONSE_LENGTH = 8;
073 private final static String ACCESS_LOG_DATE_FORMAT = "dd/MMM/yyyy:HH:mm:ss ZZZZ";
074 private final static String LOG_FILE_NAME_FORMAT = "yyyy-MM-dd";
075 private final Collection logGbeans;
076 private final ServerInfo serverInfo;
077
078 public TomcatLogManagerImpl(ServerInfo serverInfo, Collection logGbeans) {
079 this.serverInfo = serverInfo;
080 this.logGbeans = logGbeans;
081 }
082
083 /**
084 * Gets the name of all logs used by this system. Typically there
085 * is only one, but specialized cases may use more.
086 *
087 * @return An array of all log names
088 *
089 */
090 public String[] getLogNames() {
091 List logNames = new ArrayList();
092 for (Iterator it = logGbeans.iterator(); it.hasNext();) {
093 ValveGBean logGBean = (ValveGBean) it.next();
094 AccessLogValve logFile = (AccessLogValve) logGBean.getInternalObject();
095 if(logFile != null) {
096 logNames.add( "var/catalina/logs/"+logFile.getPrefix()+LOG_FILE_NAME_FORMAT+logFile.getSuffix());
097 }
098 }
099 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.resolveServerPath(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 Tomcat 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,
166 Date startDate, 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, fileLineCount = 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 fileLineCount = 0;
187 FileInputStream logInputStream = null;
188 try {
189 // Obtain the date for the current log file
190 String fileName = logFiles[i].getName();
191 Matcher fileDate = FILENAME_DATE_PATTERN.matcher(fileName);
192 fileDate.find();
193 SimpleDateFormat simpleFileDate = new SimpleDateFormat(LOG_FILE_NAME_FORMAT);
194 long logFileTime = simpleFileDate.parse(fileDate.group(GROUP_FILENAME_FULL_DATE)).getTime();
195
196 // Check if the dates are null (ignore) or fall within the search range
197 if ( (start==0 && end==0)
198 || (start>0 && start<=logFileTime && end>0 && end>=logFileTime)) {
199
200 // It's in the range, so process the file
201 logInputStream = new FileInputStream(logFiles[i]);
202 BufferedReader reader = new BufferedReader(new InputStreamReader(logInputStream, "US-ASCII"));
203
204 Matcher target = ACCESS_LOG_PATTERN.matcher("");
205 SimpleDateFormat format = (start == 0 && end == 0) ? null : new SimpleDateFormat(ACCESS_LOG_DATE_FORMAT);
206 int max = maxResults == null ? MAX_SEARCH_RESULTS : Math.min(maxResults.intValue(), MAX_SEARCH_RESULTS);
207
208 String line;
209 while ((line = reader.readLine()) != null) {
210 ++lineCount;
211 ++fileLineCount;
212 if(capped) {
213 continue;
214 }
215 target.reset(line);
216 if(target.find()) {
217 if(host != null && !host.equals(target.group(GROUP_HOST))) {
218 continue;
219 }
220 if(user != null && !user.equals(target.group(GROUP_USER))) {
221 continue;
222 }
223 if(method != null && !method.equals(target.group(GROUP_METHOD))) {
224 continue;
225 }
226 if(uri != null && !target.group(GROUP_URI).startsWith(uri)) {
227 continue;
228 }
229 if(format != null) {
230 try {
231 long entry = format.parse(target.group(GROUP_DATE)).getTime();
232 if(start > entry) {
233 continue;
234 }
235 if(end > 0 && end < entry) {
236 continue;
237 }
238 } catch (ParseException e) {
239 // can't read the date, guess this record counts.
240 }
241 }
242 if(skipResults != null && skipResults.intValue() > lineCount) {
243 continue;
244 }
245 if(list.size() > max) {
246 capped = true;
247 continue;
248 }
249 list.add(new LogMessage(fileLineCount,line.toString()));
250 }
251 }
252 }
253 } catch (Exception e) {
254 log.error("Unexpected error processing logs", e);
255 } finally {
256 if (logInputStream != null) {
257 try {
258 logInputStream.close();
259 } catch (IOException e) {
260 // ignore
261 }
262 }
263 }
264 }
265 }
266 return new SearchResults(lineCount, (LogMessage[]) list.toArray(new LogMessage[list.size()]), capped);
267 }
268
269
270 public static final GBeanInfo GBEAN_INFO;
271
272 static {
273 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("Tomcat Log Manager", TomcatLogManagerImpl.class);
274 infoFactory.addReference("LogGBeans", ValveGBean.class);
275 infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
276 infoFactory.addInterface(TomcatLogManager.class);
277
278 infoFactory.setConstructor(new String[]{"ServerInfo","LogGBeans"});
279 GBEAN_INFO = infoFactory.getBeanInfo();
280 }
281
282 public static GBeanInfo getGBeanInfo() {
283 return GBEAN_INFO;
284 }
285
286 /*
287 * Static inner class implementation of java.io.Filename. This will help us
288 * filter for only the files that we are interested in.
289 */
290 static class PatternFilenameFilter implements FilenameFilter {
291 Pattern pattern;
292
293 PatternFilenameFilter(String fileNamePattern) {
294 fileNamePattern = fileNamePattern.replaceAll("yyyy", "\\\\d{4}");
295 fileNamePattern = fileNamePattern.replaceAll("yy", "\\\\d{2}");
296 fileNamePattern = fileNamePattern.replaceAll("mm", "\\\\d{2}");
297 fileNamePattern = fileNamePattern.replaceAll("MM", "\\\\d{2}");
298 fileNamePattern = fileNamePattern.replaceAll("dd", "\\\\d{2}")
299 + ".*";
300 this.pattern = Pattern.compile(fileNamePattern);
301 }
302
303 public boolean accept(File file, String fileName) {
304 return pattern.matcher(fileName).matches();
305 }
306 }
307
308 /*
309 public static void main(String[] args) {
310 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\" -";
311 String tomcat = "127.0.0.1 - - [07/Sep/2005:15:51:18 -0500] \"GET /console/portal/server/server_info HTTP/1.1\" 200 11708";
312
313 SimpleDateFormat format = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss ZZZZ");
314 try {
315 Pattern p = Pattern.compile("(\\S*) (\\S*) (\\S*) \\[(.*)\\] \\\"(\\S*) (\\S*).*?\\\" (\\S*) (\\S*).*");
316 Matcher m = p.matcher(jetty);
317 if(m.matches()) {
318 System.out.println("Group 1: "+m.group(1)); // client
319 System.out.println("Group 2: "+m.group(2)); // ?? server host?
320 System.out.println("Group 3: "+m.group(3)); // username
321 System.out.println("Group 4: "+format.parse(m.group(4))); // date
322 System.out.println("Group 5: "+m.group(5)); // method
323 System.out.println("Group 5: "+m.group(6)); // URI
324 System.out.println("Group 6: "+m.group(7)); // response code
325 System.out.println("Group 7: "+m.group(8)); // response length
326 } else {
327 System.out.println("No match");
328 }
329 m = p.matcher(tomcat);
330 if(m.matches()) {
331 System.out.println("Group 1: "+m.group(1));
332 System.out.println("Group 2: "+m.group(2));
333 System.out.println("Group 3: "+m.group(3));
334 System.out.println("Group 4: "+format.parse(m.group(4)));
335 System.out.println("Group 5: "+m.group(5)); // method
336 System.out.println("Group 5: "+m.group(6)); // URI
337 System.out.println("Group 6: "+m.group(7)); // response code
338 System.out.println("Group 7: "+m.group(8)); // response length
339 } else {
340 System.out.println("No match");
341 }
342 } catch (ParseException e) {
343 e.printStackTrace();
344 }
345 }
346 */
347 }