001 /**
002 *
003 * Copyright 2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * 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: 437623 $ $Date: 2006-08-28 02:48:23 -0700 (Mon, 28 Aug 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 }