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    }