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.jetty6.requestlog;
017:
018: import java.io.File;
019: import java.io.FilenameFilter;
020: import java.io.RandomAccessFile;
021: import java.nio.CharBuffer;
022: import java.nio.MappedByteBuffer;
023: import java.nio.channels.FileChannel;
024: import java.nio.charset.Charset;
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: 482336 $ $Date: 2006-12-04 12:12:19 -0800 (Mon, 04 Dec 2006) $
046: */
047: public class JettyLogManagerImpl implements JettyLogManager {
048: private final static Log log = LogFactory
049: .getLog(JettyLogManagerImpl.class);
050:
051: // Pattern that matches the date in the logfile name
052: private final static Pattern FILENAME_DATE_PATTERN = Pattern
053: .compile("[-_ /.](((19|20)\\d\\d)[-_ /.](0[1-9]|1[012])[-_ /.](0[1-9]|[12][0-9]|3[01]))");
054: private final static int GROUP_FILENAME_FULL_DATE = 1;
055: private final static int GROUP_FILENAME_YEAR = 2;
056: private final static int GROUP_FILENAME_MONTH = 4;
057: private final static int GROUP_FILENAME_DAY = 5;
058: // NOTE: The file separators are specified here rather than using something like File.separator because
059: // they are hard coded in config plans and sometimes in java code itself rather than being dependent
060: // upon the OS. This should be fixed someday, but for now we will manually check for either format.
061: private final static String FILE_SEPARATOR_UNIX_STYLE = "/";
062: private final static String FILE_SEPARATOR_WIN_STYLE = "\\";
063:
064: // Pattern that matches a single line (used to calculate line numbers)
065: private final static Pattern FULL_LINE_PATTERN = Pattern.compile(
066: "^.*", Pattern.MULTILINE);
067: private final static Pattern ACCESS_LOG_PATTERN = Pattern
068: .compile("(\\S*) (\\S*) (\\S*) \\[(.*)\\] \\\"(\\S*) (\\S*).*?\\\" (\\S*) (\\S*).*");
069: private final static int GROUP_HOST = 1;
070: private final static int GROUP_USER = 3;
071: private final static int GROUP_DATE = 4;
072: private final static int GROUP_METHOD = 5;
073: private final static int GROUP_URI = 6;
074: private final static int GROUP_RESPONSE_CODE = 7;
075: private final static int GROUP_RESPONSE_LENGTH = 8;
076: private final static String ACCESS_LOG_DATE_FORMAT = "dd/MMM/yyyy:HH:mm:ss ZZZZ";
077: private final static String LOG_FILE_NAME_FORMAT = "yyyy_MM_dd";
078: private final Collection logGbeans;
079: private final ServerInfo serverInfo;
080:
081: public JettyLogManagerImpl(ServerInfo serverInfo,
082: Collection logGbeans) {
083: this .serverInfo = serverInfo;
084: this .logGbeans = logGbeans;
085: }
086:
087: /**
088: * Gets the name of all logs used by this system. Typically there
089: * is only one, but specialized cases may use more.
090: *
091: * @return An array of all log names
092: *
093: */
094: public String[] getLogNames() {
095: List logNames = new ArrayList();
096: for (Iterator it = logGbeans.iterator(); it.hasNext();) {
097: JettyRequestLog jettyLog = (JettyRequestLog) it.next();
098: if (jettyLog.getFilename() != null) {
099: logNames.add(jettyLog.getFilename());
100: }
101: }
102: return (String[]) logNames.toArray(new String[logNames.size()]);
103: }
104:
105: /**
106: * Gets the names of all log files for this log name.
107: *
108: * @param logName The name of the log for which to return the specific file names.
109: *
110: * @return An array of log file names
111: *
112: */
113: public String[] getLogFileNames(String logName) {
114: List names = new ArrayList();
115:
116: // Find all the files for this logName
117: File[] logFiles = getLogFiles(logName);
118:
119: if (logFiles != null) {
120: for (int i = 0; i < logFiles.length; i++) {
121: names.add(logFiles[i].getName());
122: }
123: }
124: return (String[]) names.toArray(new String[names.size()]);
125: }
126:
127: /**
128: * Gets the name of all log files used by this log. Typically there
129: * is only one, but specialized cases may use more.
130: *
131: * @param logName The name of the log for which to return the specific files.
132: *
133: * @return An array of all log file names
134: *
135: */
136: private File[] getLogFiles(String logName) {
137: File[] logFiles = null;
138:
139: try {
140: String fileNamePattern = logName;
141: if (fileNamePattern.indexOf(FILE_SEPARATOR_UNIX_STYLE) > -1) {
142: fileNamePattern = fileNamePattern
143: .substring(fileNamePattern
144: .lastIndexOf(FILE_SEPARATOR_UNIX_STYLE) + 1);
145: } else if (fileNamePattern
146: .indexOf(FILE_SEPARATOR_WIN_STYLE) > -1) {
147: fileNamePattern = fileNamePattern
148: .substring(fileNamePattern
149: .lastIndexOf(FILE_SEPARATOR_WIN_STYLE) + 1);
150: }
151:
152: String logFile = serverInfo.resolvePath(logName);
153:
154: File parent = new File(logFile).getParentFile();
155:
156: if (parent != null) {
157: logFiles = parent.listFiles(new PatternFilenameFilter(
158: fileNamePattern));
159: }
160: } catch (Exception e) {
161: log.error("Exception attempting to locate Jetty 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, fileCount = 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: fileCount = 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:
214: // Check if the dates are null (ignore) or fall within the search range
215: if ((start == 0 && end == 0)
216: || (start > 0 && start <= logFileTime
217: && end > 0 && end >= logFileTime)) {
218:
219: // It's in the range, so process the file
220: RandomAccessFile raf = new RandomAccessFile(
221: logFiles[i], "r");
222: FileChannel fc = raf.getChannel();
223: MappedByteBuffer bb = fc.map(
224: FileChannel.MapMode.READ_ONLY, 0, fc
225: .size());
226: CharBuffer cb = Charset.forName("US-ASCII")
227: .decode(bb); //todo: does Jetty use a different charset on a foreign PC?
228: Matcher lines = FULL_LINE_PATTERN.matcher(cb);
229: Matcher target = ACCESS_LOG_PATTERN.matcher("");
230: SimpleDateFormat format = (start == 0 && end == 0) ? null
231: : new SimpleDateFormat(
232: ACCESS_LOG_DATE_FORMAT);
233: int max = maxResults == null ? MAX_SEARCH_RESULTS
234: : Math.min(maxResults.intValue(),
235: MAX_SEARCH_RESULTS);
236:
237: while (lines.find()) {
238: ++lineCount;
239: ++fileCount;
240: if (capped) {
241: continue;
242: }
243: CharSequence line = cb.subSequence(lines
244: .start(), lines.end());
245: target.reset(line);
246: if (target.find()) {
247: if (host != null
248: && !host.equals(target
249: .group(GROUP_HOST))) {
250: continue;
251: }
252: if (user != null
253: && !user.equals(target
254: .group(GROUP_USER))) {
255: continue;
256: }
257: if (method != null
258: && !method.equals(target
259: .group(GROUP_METHOD))) {
260: continue;
261: }
262: if (uri != null
263: && !target.group(GROUP_URI)
264: .startsWith(uri)) {
265: continue;
266: }
267: if (format != null) {
268: try {
269: long entry = format
270: .parse(
271: target
272: .group(GROUP_DATE))
273: .getTime();
274: if (start > entry) {
275: continue;
276: }
277: if (end > 0 && end < entry) {
278: continue;
279: }
280: } catch (ParseException e) {
281: // can't read the date, guess this record counts.
282: }
283: }
284: if (skipResults != null
285: && skipResults.intValue() > lineCount) {
286: continue;
287: }
288: if (list.size() > max) {
289: capped = true;
290: continue;
291: }
292: list.add(new LogMessage(fileCount, line
293: .toString()));
294: }
295: }
296: fc.close();
297: raf.close();
298: }
299: } catch (Exception e) {
300: log.error("Unexpected error processing logs", e);
301: }
302: }
303: }
304: return new SearchResults(lineCount, (LogMessage[]) list
305: .toArray(new LogMessage[list.size()]), capped);
306: }
307:
308: public static final GBeanInfo GBEAN_INFO;
309:
310: static {
311: GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(
312: "Jetty Log Manager", JettyLogManagerImpl.class);
313: infoFactory.addReference("LogGBeans", JettyRequestLog.class);
314: infoFactory.addReference("ServerInfo", ServerInfo.class,
315: "GBean");
316: infoFactory.addInterface(JettyLogManager.class);
317:
318: infoFactory.setConstructor(new String[] { "ServerInfo",
319: "LogGBeans" });
320: GBEAN_INFO = infoFactory.getBeanInfo();
321: }
322:
323: public static GBeanInfo getGBeanInfo() {
324: return GBEAN_INFO;
325: }
326:
327: /*
328: * Static inner class implementation of java.io.Filename. This will help us
329: * filter for only the files that we are interested in.
330: */
331: static class PatternFilenameFilter implements FilenameFilter {
332: Pattern pattern;
333:
334: //todo: put this pattern in a GBean parameter?
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("dd",
343: "\\\\d{2}");
344: this .pattern = Pattern.compile(fileNamePattern);
345: }
346:
347: public boolean accept(File file, String fileName) {
348: return pattern.matcher(fileName).matches();
349: }
350: }
351: }
|