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.jetty6.requestlog;
018
019 import java.io.BufferedReader;
020 import java.io.File;
021 import java.io.FileInputStream;
022 import java.io.FilenameFilter;
023 import java.io.IOException;
024 import java.io.InputStreamReader;
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: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
046 */
047 public class JettyLogManagerImpl implements JettyLogManager {
048 private final static Log log = LogFactory.getLog(JettyLogManagerImpl.class);
049
050 // Pattern that matches the date in the logfile name
051 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]))");
052 private final static int GROUP_FILENAME_FULL_DATE = 1;
053 private final static int GROUP_FILENAME_YEAR = 2;
054 private final static int GROUP_FILENAME_MONTH = 4;
055 private final static int GROUP_FILENAME_DAY = 5;
056 // NOTE: The file separators are specified here rather than using something like File.separator because
057 // they are hard coded in config plans and sometimes in java code itself rather than being dependent
058 // upon the OS. This should be fixed someday, but for now we will manually check for either format.
059 private final static String FILE_SEPARATOR_UNIX_STYLE = "/";
060 private final static String FILE_SEPARATOR_WIN_STYLE = "\\";
061
062 // Pattern that matches a single line (used to calculate line numbers)
063 private final static Pattern FULL_LINE_PATTERN = Pattern.compile("^.*", Pattern.MULTILINE);
064 private final static Pattern ACCESS_LOG_PATTERN = Pattern.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 JettyLogManagerImpl(ServerInfo serverInfo, Collection logGbeans) {
078 this.serverInfo = serverInfo;
079 this.logGbeans = logGbeans;
080 }
081
082 /**
083 * Gets the name of all logs used by this system. Typically there
084 * is only one, but specialized cases may use more.
085 *
086 * @return An array of all log names
087 *
088 */
089 public String[] getLogNames() {
090 List logNames = new ArrayList();
091 for (Iterator it = logGbeans.iterator(); it.hasNext();) {
092 JettyRequestLog jettyLog = (JettyRequestLog) it.next();
093 if(jettyLog.getFilename() != null) {
094 logNames.add(jettyLog.getFilename());
095 }
096 }
097 return (String[]) logNames.toArray(new String[logNames.size()]);
098 }
099
100 /**
101 * Gets the names of all log files for this log name.
102 *
103 * @param logName The name of the log for which to return the specific file names.
104 *
105 * @return An array of log file names
106 *
107 */
108 public String[] getLogFileNames(String logName) {
109 List names = new ArrayList();
110
111 // Find all the files for this logName
112 File[] logFiles = getLogFiles(logName);
113
114 if (logFiles !=null) {
115 for (int i = 0; i < logFiles.length; i++) {
116 names.add(logFiles[i].getName());
117 }
118 }
119 return (String[]) names.toArray(new String[names.size()]);
120 }
121
122 /**
123 * Gets the name of all log files used by this log. Typically there
124 * is only one, but specialized cases may use more.
125 *
126 * @param logName The name of the log for which to return the specific files.
127 *
128 * @return An array of all log file names
129 *
130 */
131 private File[] getLogFiles(String logName) {
132 File[] logFiles = null;
133
134 try {
135 String fileNamePattern = logName;
136 if (fileNamePattern.indexOf(FILE_SEPARATOR_UNIX_STYLE) > -1) {
137 fileNamePattern = fileNamePattern.substring(fileNamePattern.lastIndexOf(FILE_SEPARATOR_UNIX_STYLE) + 1);
138 } else if (fileNamePattern.indexOf(FILE_SEPARATOR_WIN_STYLE) > -1) {
139 fileNamePattern = fileNamePattern.substring(fileNamePattern.lastIndexOf(FILE_SEPARATOR_WIN_STYLE) + 1);
140 }
141
142 String logFile = serverInfo.resolveServerPath(logName);
143
144 File parent = new File(logFile).getParentFile();
145
146 if (parent != null) {
147 logFiles = parent.listFiles(new PatternFilenameFilter(fileNamePattern));
148 }
149 } catch (Exception e) {
150 log.error("Exception attempting to locate Jetty log files", e);
151 logFiles = new File[0];
152 }
153 return logFiles;
154 }
155
156 /**
157 * Searches the log for records matching the specified parameters. The
158 * maximum results returned will be the lesser of 1000 and the
159 * provided maxResults argument.
160 *
161 * @see #MAX_SEARCH_RESULTS
162 */
163 public SearchResults getMatchingItems(String logName, String host, String user, String method, String uri, Date startDate,
164 Date endDate, Integer skipResults, Integer maxResults) {
165
166 // Clean up the arguments so we know what we've really got
167 if(host != null && host.equals("")) host = null;
168 if(user != null && user.equals("")) user = null;
169 if(method != null && method.equals("")) method = null;
170 if(uri != null && uri.equals("")) uri = null;
171
172 long start = startDate == null ? 0 : startDate.getTime();
173 long end = endDate == null ? 0 : endDate.getTime();
174
175 List list = new LinkedList();
176 boolean capped = false;
177 int lineCount = 0, fileCount = 0;
178
179 // Find all the files for this logName
180 File logFiles[] = getLogFiles(logName);
181
182 if (logFiles !=null) {
183 for (int i = 0; i < logFiles.length; i++) {
184 fileCount = 0;
185 FileInputStream logInputStream = null;
186 try {
187 // Obtain the date for the current log file
188 String fileName = logFiles[i].getName();
189 Matcher fileDate = FILENAME_DATE_PATTERN.matcher(fileName);
190 fileDate.find();
191 SimpleDateFormat simpleFileDate = new SimpleDateFormat(LOG_FILE_NAME_FORMAT);
192 long logFileTime = simpleFileDate.parse(fileDate.group(GROUP_FILENAME_FULL_DATE)).getTime();
193
194 // Check if the dates are null (ignore) or fall within the search range
195 if ( (start==0 && end==0)
196 || (start>0 && start<=logFileTime && end>0 && end>=logFileTime)) {
197
198 // It's in the range, so process the file
199 logInputStream = new FileInputStream(logFiles[i]);
200 BufferedReader reader = new BufferedReader(new InputStreamReader(logInputStream, "US-ASCII"));
201
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 String line;
207 while ((line = reader.readLine()) != null) {
208 ++lineCount;
209 ++fileCount;
210 if(capped) {
211 continue;
212 }
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(fileCount,line.toString()));
248 }
249 }
250 }
251 } catch (Exception e) {
252 log.error("Unexpected error processing logs", e);
253 } finally {
254 if (logInputStream != null) {
255 try {
256 logInputStream.close();
257 } catch (IOException e) {
258 // ignore
259 }
260 }
261 }
262 }
263 }
264 return new SearchResults(lineCount, (LogMessage[]) list.toArray(new LogMessage[list.size()]), capped);
265 }
266
267
268 public static final GBeanInfo GBEAN_INFO;
269
270 static {
271 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("Jetty Log Manager", JettyLogManagerImpl.class);
272 infoFactory.addReference("LogGBeans", JettyRequestLog.class);
273 infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
274 infoFactory.addInterface(JettyLogManager.class);
275
276 infoFactory.setConstructor(new String[]{"ServerInfo","LogGBeans"});
277 GBEAN_INFO = infoFactory.getBeanInfo();
278 }
279
280 public static GBeanInfo getGBeanInfo() {
281 return GBEAN_INFO;
282 }
283
284 /*
285 * Static inner class implementation of java.io.Filename. This will help us
286 * filter for only the files that we are interested in.
287 */
288 static class PatternFilenameFilter implements FilenameFilter {
289 Pattern pattern;
290 //todo: put this pattern in a GBean parameter?
291 PatternFilenameFilter(String fileNamePattern) {
292 fileNamePattern = fileNamePattern.replaceAll("yyyy", "\\\\d{4}");
293 fileNamePattern = fileNamePattern.replaceAll("yy", "\\\\d{2}");
294 fileNamePattern = fileNamePattern.replaceAll("mm", "\\\\d{2}");
295 fileNamePattern = fileNamePattern.replaceAll("dd", "\\\\d{2}");
296 this.pattern = Pattern.compile(fileNamePattern);
297 }
298
299 public boolean accept(File file, String fileName) {
300 return pattern.matcher(fileName).matches();
301 }
302 }
303 }