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: */package org.apache.geronimo.tomcat;
017:
018: import org.apache.geronimo.gbean.GBeanInfo;
019: import org.apache.geronimo.gbean.GBeanInfoBuilder;
020: import org.apache.geronimo.system.serverinfo.ServerInfo;
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023: import org.apache.catalina.valves.AccessLogValve;
024:
025: import java.util.*;
026: import java.util.regex.Pattern;
027: import java.util.regex.Matcher;
028: import java.io.File;
029: import java.io.FilenameFilter;
030: import java.io.RandomAccessFile;
031: import java.nio.channels.FileChannel;
032: import java.nio.MappedByteBuffer;
033: import java.nio.CharBuffer;
034: import java.nio.charset.Charset;
035: import java.text.SimpleDateFormat;
036: import java.text.ParseException;
037:
038: /**
039: * Tomcat implementation of the WebAccessLog management interface.
040: *
041: * @version $Rev: 476049 $ $Date: 2006-11-16 20:35:17 -0800 (Thu, 16 Nov 2006) $
042: */
043: public class TomcatLogManagerImpl implements TomcatLogManager {
044: private final static Log log = LogFactory
045: .getLog(TomcatLogManagerImpl.class);
046:
047: // Pattern that matches the date in the logfile name
048: private final static Pattern FILENAME_DATE_PATTERN = Pattern
049: .compile("[-_ /.](((19|20)\\d\\d)[-_ /.](0[1-9]|1[012])[-_ /.](0[1-9]|[12][0-9]|3[01]))");
050: private final static int GROUP_FILENAME_FULL_DATE = 1;
051: private final static int GROUP_FILENAME_YEAR = 2;
052: private final static int GROUP_FILENAME_MONTH = 4;
053: private final static int GROUP_FILENAME_DAY = 5;
054: // NOTE: The file separators are specified here rather than using something like File.separator because
055: // they are hard coded in config plans and sometimes in java code itself rather than being dependent
056: // upon the OS. This should be fixed someday, but for now we will manually check for either format.
057: private final static String FILE_SEPARATOR_UNIX_STYLE = "/";
058: private final static String FILE_SEPARATOR_WIN_STYLE = "\\";
059:
060: // Pattern that matches a single line (used to calculate line numbers)
061: private final static Pattern FULL_LINE_PATTERN = Pattern.compile(
062: "^.*", Pattern.MULTILINE);
063: private final static Pattern ACCESS_LOG_PATTERN = Pattern
064: .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 TomcatLogManagerImpl(ServerInfo serverInfo,
078: 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
095: .getInternalObject();
096: if (logFile != null) {
097: logNames.add("var/catalina/logs/" + logFile.getPrefix()
098: + LOG_FILE_NAME_FORMAT + logFile.getSuffix());
099: }
100: }
101: return (String[]) logNames.toArray(new String[logNames.size()]);
102: }
103:
104: /**
105: * Gets the names of all log files for this log name.
106: *
107: * @param logName The name of the log for which to return the specific file names.
108: *
109: * @return An array of log file names
110: *
111: */
112: public String[] getLogFileNames(String logName) {
113: List names = new ArrayList();
114:
115: // Find all the files for this logName
116: File[] logFiles = getLogFiles(logName);
117:
118: if (logFiles != null) {
119: for (int i = 0; i < logFiles.length; i++) {
120: names.add(logFiles[i].getName());
121: }
122: }
123: return (String[]) names.toArray(new String[names.size()]);
124: }
125:
126: /**
127: * Gets the name of all log files used by this log. Typically there
128: * is only one, but specialized cases may use more.
129: *
130: * @param logName The name of the log for which to return the specific files.
131: *
132: * @return An array of all log file names
133: *
134: */
135: private File[] getLogFiles(String logName) {
136: File[] logFiles = null;
137:
138: try {
139: String fileNamePattern = logName;
140: if (fileNamePattern.indexOf(FILE_SEPARATOR_UNIX_STYLE) > -1) {
141: fileNamePattern = fileNamePattern
142: .substring(fileNamePattern
143: .lastIndexOf(FILE_SEPARATOR_UNIX_STYLE) + 1);
144: } else if (fileNamePattern
145: .indexOf(FILE_SEPARATOR_WIN_STYLE) > -1) {
146: fileNamePattern = fileNamePattern
147: .substring(fileNamePattern
148: .lastIndexOf(FILE_SEPARATOR_WIN_STYLE) + 1);
149: }
150:
151: String logFile = serverInfo.resolvePath(logName);
152:
153: File parent = new File(logFile).getParentFile();
154:
155: if (parent != null) {
156: logFiles = parent.listFiles(new PatternFilenameFilter(
157: fileNamePattern));
158: }
159: } catch (Exception e) {
160: log.error(
161: "Exception attempting to locate Tomcat log files",
162: e);
163: logFiles = new File[0];
164: }
165: return logFiles;
166: }
167:
168: /**
169: * Searches the log for records matching the specified parameters. The
170: * maximum results returned will be the lesser of 1000 and the
171: * provided maxResults argument.
172: *
173: * @see #MAX_SEARCH_RESULTS
174: */
175: public SearchResults getMatchingItems(String logName, String host,
176: String user, String method, String uri, Date startDate,
177: Date endDate, Integer skipResults, Integer maxResults) {
178:
179: // Clean up the arguments so we know what we've really got
180: if (host != null && host.equals(""))
181: host = null;
182: if (user != null && user.equals(""))
183: user = null;
184: if (method != null && method.equals(""))
185: method = null;
186: if (uri != null && uri.equals(""))
187: uri = null;
188:
189: long start = startDate == null ? 0 : startDate.getTime();
190: long end = endDate == null ? 0 : endDate.getTime();
191:
192: List list = new LinkedList();
193: boolean capped = false;
194: int lineCount = 0, fileLineCount = 0;
195:
196: // Find all the files for this logName
197: File logFiles[] = getLogFiles(logName);
198:
199: if (logFiles != null) {
200: for (int i = 0; i < logFiles.length; i++) {
201: fileLineCount = 0;
202: try {
203: // Obtain the date for the current log file
204: String fileName = logFiles[i].getName();
205: Matcher fileDate = FILENAME_DATE_PATTERN
206: .matcher(fileName);
207: fileDate.find();
208: SimpleDateFormat simpleFileDate = new SimpleDateFormat(
209: LOG_FILE_NAME_FORMAT);
210: long logFileTime = simpleFileDate.parse(
211: fileDate.group(GROUP_FILENAME_FULL_DATE))
212: .getTime();
213: Date logFileDate = new Date(logFileTime);
214:
215: // Check if the dates are null (ignore) or fall within the search range
216: if ((start == 0 && end == 0)
217: || (start > 0 && start <= logFileTime
218: && end > 0 && end >= logFileTime)) {
219:
220: // It's in the range, so process the file
221: RandomAccessFile raf = new RandomAccessFile(
222: logFiles[i], "r");
223: FileChannel fc = raf.getChannel();
224: MappedByteBuffer bb = fc.map(
225: FileChannel.MapMode.READ_ONLY, 0, fc
226: .size());
227: CharBuffer cb = Charset.forName("US-ASCII")
228: .decode(bb); //todo: does Tomcat use a different charset on a foreign PC?
229: Matcher lines = FULL_LINE_PATTERN.matcher(cb);
230: Matcher target = ACCESS_LOG_PATTERN.matcher("");
231: SimpleDateFormat format = (start == 0 && end == 0) ? null
232: : new SimpleDateFormat(
233: ACCESS_LOG_DATE_FORMAT);
234: int max = maxResults == null ? MAX_SEARCH_RESULTS
235: : Math.min(maxResults.intValue(),
236: MAX_SEARCH_RESULTS);
237:
238: while (lines.find()) {
239: ++lineCount;
240: ++fileLineCount;
241: if (capped) {
242: continue;
243: }
244: CharSequence line = cb.subSequence(lines
245: .start(), lines.end());
246: target.reset(line);
247: if (target.find()) {
248: if (host != null
249: && !host.equals(target
250: .group(GROUP_HOST))) {
251: continue;
252: }
253: if (user != null
254: && !user.equals(target
255: .group(GROUP_USER))) {
256: continue;
257: }
258: if (method != null
259: && !method.equals(target
260: .group(GROUP_METHOD))) {
261: continue;
262: }
263: if (uri != null
264: && !target.group(GROUP_URI)
265: .startsWith(uri)) {
266: continue;
267: }
268: if (format != null) {
269: try {
270: long entry = format
271: .parse(
272: target
273: .group(GROUP_DATE))
274: .getTime();
275: if (start > entry) {
276: continue;
277: }
278: if (end > 0 && end < entry) {
279: continue;
280: }
281: } catch (ParseException e) {
282: // can't read the date, guess this record counts.
283: }
284: }
285: if (skipResults != null
286: && skipResults.intValue() > lineCount) {
287: continue;
288: }
289: if (list.size() > max) {
290: capped = true;
291: continue;
292: }
293: list.add(new LogMessage(fileLineCount,
294: line.toString()));
295: }
296: }
297: fc.close();
298: raf.close();
299: }
300: } catch (Exception e) {
301: log.error("Unexpected error processing logs", e);
302: }
303: }
304: }
305: return new SearchResults(lineCount, (LogMessage[]) list
306: .toArray(new LogMessage[list.size()]), capped);
307: }
308:
309: public static final GBeanInfo GBEAN_INFO;
310:
311: static {
312: GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(
313: "Tomcat Log Manager", TomcatLogManagerImpl.class);
314: infoFactory.addReference("LogGBeans", ValveGBean.class);
315: infoFactory.addReference("ServerInfo", ServerInfo.class,
316: "GBean");
317: infoFactory.addInterface(TomcatLogManager.class);
318:
319: infoFactory.setConstructor(new String[] { "ServerInfo",
320: "LogGBeans" });
321: GBEAN_INFO = infoFactory.getBeanInfo();
322: }
323:
324: public static GBeanInfo getGBeanInfo() {
325: return GBEAN_INFO;
326: }
327:
328: /*
329: * Static inner class implementation of java.io.Filename. This will help us
330: * filter for only the files that we are interested in.
331: */
332: static class PatternFilenameFilter implements FilenameFilter {
333: Pattern pattern;
334:
335: PatternFilenameFilter(String fileNamePattern) {
336: fileNamePattern = fileNamePattern.replaceAll("yyyy",
337: "\\\\d{4}");
338: fileNamePattern = fileNamePattern.replaceAll("yy",
339: "\\\\d{2}");
340: fileNamePattern = fileNamePattern.replaceAll("mm",
341: "\\\\d{2}");
342: fileNamePattern = fileNamePattern.replaceAll("MM",
343: "\\\\d{2}");
344: fileNamePattern = fileNamePattern.replaceAll("dd",
345: "\\\\d{2}")
346: + ".*";
347: this .pattern = Pattern.compile(fileNamePattern);
348: }
349:
350: public boolean accept(File file, String fileName) {
351: return pattern.matcher(fileName).matches();
352: }
353: }
354:
355: /*
356: public static void main(String[] args) {
357: 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\" -";
358: String tomcat = "127.0.0.1 - - [07/Sep/2005:15:51:18 -0500] \"GET /console/portal/server/server_info HTTP/1.1\" 200 11708";
359:
360: SimpleDateFormat format = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss ZZZZ");
361: try {
362: Pattern p = Pattern.compile("(\\S*) (\\S*) (\\S*) \\[(.*)\\] \\\"(\\S*) (\\S*).*?\\\" (\\S*) (\\S*).*");
363: Matcher m = p.matcher(jetty);
364: if(m.matches()) {
365: System.out.println("Group 1: "+m.group(1)); // client
366: System.out.println("Group 2: "+m.group(2)); // ?? server host?
367: System.out.println("Group 3: "+m.group(3)); // username
368: System.out.println("Group 4: "+format.parse(m.group(4))); // date
369: System.out.println("Group 5: "+m.group(5)); // method
370: System.out.println("Group 5: "+m.group(6)); // URI
371: System.out.println("Group 6: "+m.group(7)); // response code
372: System.out.println("Group 7: "+m.group(8)); // response length
373: } else {
374: System.out.println("No match");
375: }
376: m = p.matcher(tomcat);
377: if(m.matches()) {
378: System.out.println("Group 1: "+m.group(1));
379: System.out.println("Group 2: "+m.group(2));
380: System.out.println("Group 3: "+m.group(3));
381: System.out.println("Group 4: "+format.parse(m.group(4)));
382: System.out.println("Group 5: "+m.group(5)); // method
383: System.out.println("Group 5: "+m.group(6)); // URI
384: System.out.println("Group 6: "+m.group(7)); // response code
385: System.out.println("Group 7: "+m.group(8)); // response length
386: } else {
387: System.out.println("No match");
388: }
389: } catch (ParseException e) {
390: e.printStackTrace();
391: }
392: }
393: */
394: }
|