001 package hirondelle.web4jtools.logview.simpleview; 002 003 import static hirondelle.web4j.util.Consts.NEW_LINE; 004 import hirondelle.web4j.database.DAOException; 005 import hirondelle.web4j.util.Util; 006 import hirondelle.web4jtools.logview.directories.LogInfo; 007 import hirondelle.web4jtools.logview.directories.LogInfoDAO; 008 import hirondelle.web4jtools.logview.parsedview.LoggerRecord; 009 import hirondelle.web4jtools.logview.parsedview.ParsedCriteria; 010 import hirondelle.web4jtools.util.Ensure; 011 import hirondelle.web4jtools.logview.parser.LogParser; 012 import hirondelle.web4jtools.logview.parser.LogParserInstance; 013 014 import java.io.File; 015 import java.io.FileFilter; 016 import java.io.FileNotFoundException; 017 import java.util.*; 018 import java.util.logging.Logger; 019 020 import javax.servlet.ServletConfig; 021 import javax.servlet.http.HttpServletRequest; 022 023 /** 024 * Data Access Object for log files. 025 * 026 *<P>This class is used both for viewing log files as plain text, and for 027 * parsing log files into {@link hirondelle.web4jtools.logview.parsedview.LoggerRecord}s. 028 */ 029 public final class LogFileDAO { 030 031 /** Read in config from <tt>web.xml</tt>. */ 032 public static void readConfig(ServletConfig aConfig){ 033 fAPP_ENCODING = aConfig.getInitParameter(APP_ENCODING); 034 fSERVER_ENCODING = aConfig.getInitParameter(SERVER_ENCODING); 035 Ensure.isPresentInWebXml(APP_ENCODING, fAPP_ENCODING); 036 Ensure.isPresentInWebXml(SERVER_ENCODING, fSERVER_ENCODING); 037 } 038 039 /** Full constructor. */ 040 public LogFileDAO(HttpServletRequest aRequest){ 041 fDirInfoDAO = new LogInfoDAO(aRequest); 042 } 043 044 /** 045 * Return the most recent log file in a given directory. 046 * 047 * Returns <tt>null</tt> if no file found in the logging directory. 048 */ 049 public File getMostRecentLogFile(LogFor aLogFor) throws DAOException { 050 return getMostRecentFile(aLogFor); 051 } 052 053 /** 054 * Return the contents of a log file as a <tt>String</tt>. 055 * 056 * <P>If the given criteria desire only a section of the file, then only the first or last 057 * section is returned; otherwise, the contents of the entire file is returned. 058 */ 059 public String getLogFileContents(File aFile, SimpleCriteria aCriteria) throws DAOException { 060 String result = null; 061 if( aCriteria.getSection() == null || aCriteria.getSection() == Section.First) { 062 result = getAllOrFirst(aFile, aCriteria.getNumLines()); 063 } 064 else { 065 result = getEnd(aFile, aCriteria.getNumLines()); 066 } 067 return result; 068 } 069 070 /** 071 * Return the entire content of a log file as a {@code List<LoggerRecord>}. 072 */ 073 public List<LoggerRecord> getParsedLogFile(File aFile, ParsedCriteria aCriteria) throws DAOException { 074 LogParser parser = LogParserInstance.getFor(aCriteria.getLogFor()); 075 List<LoggerRecord> result = parser.parse(getAllOrFirst(aFile, null), aCriteria); 076 if( aCriteria.getReverseOrder() ) { 077 Collections.reverse(result); 078 } 079 return result; 080 } 081 082 /** 083 * Return a <tt>List</tt> of all <em>application</em> log files. 084 * 085 * Any files having zero size (such as lock files) are ignored. The files are returned in order of increasing 086 * 'last-modified' date, such that the oldest log is first. This method is used when calculating down-times 087 * from logs. 088 */ 089 public List<File> listAllAppLogFiles(){ 090 File appLogDir = getDirectory(LogFor.Application); 091 File[] logFiles = appLogDir.listFiles(getFilter(LogFor.Application)); 092 List<File> result = Arrays.asList(logFiles); //assumes all are logs, with no directories underneath 093 Collections.sort(result, byModifiedDateDesc()); 094 Collections.reverse(result); 095 fLogger.fine("Number of app log files in app log directory : " + result.size()); 096 return result; 097 } 098 099 /** 100 * Return a <tt>List</tt> containing the <em>first</em> {@link LoggerRecord} in each 101 * of the given log files. This method is used when calculating down times from logs. 102 */ 103 public List<LoggerRecord> listFirstRecordsFor(List<File> aAllLogFiles) throws DAOException { 104 List<LoggerRecord> result = new ArrayList<LoggerRecord>(); 105 for(File file : aAllLogFiles){ 106 result.add(firstRecordFor(file)); 107 } 108 return result; 109 } 110 111 // PRIVATE // 112 113 private final LogInfoDAO fDirInfoDAO; 114 115 private static String SERVER_ENCODING = "ServerLogFileEncoding"; 116 private static String fSERVER_ENCODING; 117 118 private static String APP_ENCODING = "ApplicationLogFileEncoding"; 119 private static String fAPP_ENCODING; 120 121 private static final Logger fLogger = Util.getLogger(LogFileDAO.class); 122 123 /** Returns null if no file in the directory at all. */ 124 private File getMostRecentFile(LogFor aLogFor) { 125 File result = null; 126 List<File> allFiles = Arrays.asList(getDirectory(aLogFor).listFiles(getFilter(aLogFor))); 127 Collections.sort( allFiles, byModifiedDateDesc() ); 128 if ( ! allFiles.isEmpty() ) { 129 result = allFiles.get(0); 130 fLogger.fine("Most recent " + aLogFor + " log file : " + Util.quote(result.getAbsolutePath())); 131 } 132 else { 133 fLogger.fine("No log files detected in directory : " + getDirectory(aLogFor)); 134 } 135 return result; 136 } 137 138 private File getDirectory(LogFor aLogFor){ 139 File result = null; 140 if( LogFor.Server == aLogFor ) { 141 result = new File(fDirInfoDAO.fetch().getServerLoggingDirectory().getRawString()); 142 } 143 else if ( LogFor.Application == aLogFor ) { 144 result = new File(fDirInfoDAO.fetch().getAppLoggingDirectory().getRawString()); 145 } 146 else { 147 throw new AssertionError("Unknown value of LogFor enumeration."); 148 } 149 return result; 150 } 151 152 /** Sort files by date. */ 153 private Comparator<File> byModifiedDateDesc(){ 154 return new Comparator<File>() { 155 public int compare(File aThis, File aThat) { 156 Long thisDate = aThis.lastModified(); 157 Long thatDate = aThat.lastModified(); 158 return thisDate.compareTo(thatDate) * (-1); 159 } 160 }; 161 } 162 163 /** 164 * Return either all or a portion of a file as a String. 165 * 166 * @param aNumLines num lines to return; if <tt>null</tt>, then all lines are returned. 167 */ 168 private String getAllOrFirst(File aFile, Integer aNumLines) throws DAOException { 169 fLogger.fine("Getting either all of log file, or first section."); 170 StringBuilder result = new StringBuilder(); 171 Scanner scanner = null; 172 try { 173 scanner = new Scanner(aFile); 174 } 175 catch(FileNotFoundException ex){ 176 throw new DAOException("Cannot find file " + Util.quote(aFile.getAbsolutePath()), ex); 177 } 178 int lineCount = 0; 179 while ( scanner.hasNextLine()) { 180 result.append(scanner.nextLine() + NEW_LINE); 181 lineCount++; 182 if ( aNumLines != null && lineCount == aNumLines) break; 183 } 184 scanner.close(); 185 fLogger.fine("Number of log lines returned : " + lineCount); 186 return result.toString(); 187 } 188 189 /** 190 * Return the end section of a file as a String. 191 * 192 * @param aNumLinesForDisplay number of lines to return. 193 */ 194 private String getEnd(File aFile, Integer aNumLinesForDisplay) throws DAOException { 195 fLogger.fine("Getting up to " + aNumLinesForDisplay + " lines from the end of the log file."); 196 String result = null; 197 Integer numLinesInFile = getNumLines(aFile); 198 if ( numLinesInFile <= aNumLinesForDisplay ) { 199 result = getAllOrFirst(aFile, null); 200 } 201 else { 202 result = getOnlyFinalLines(aFile, numLinesInFile, aNumLinesForDisplay); 203 } 204 return result; 205 } 206 207 /** Return the number of lines in the given file. */ 208 private int getNumLines(File aFile) throws DAOException { 209 int result = 0; 210 Scanner scanner = null; 211 try { 212 scanner = new Scanner(aFile); 213 } 214 catch(FileNotFoundException ex){ 215 throw new DAOException("Cannot find file " + Util.quote(aFile.getAbsolutePath()), ex); 216 } 217 while ( scanner.hasNextLine()) { 218 scanner.nextLine(); 219 result++; 220 } 221 scanner.close(); 222 fLogger.fine("Number of lines in the file : " + result); 223 return result; 224 } 225 226 /** Truncate file content to a gven number of lines at the end of the file. */ 227 private String getOnlyFinalLines(File aFile, Integer aTotalLinesInFile, Integer aNumLinesForDisplay) throws DAOException { 228 fLogger.fine("Truncating. Total lines in file : " + aTotalLinesInFile + " Num Lines desired for display : " + aNumLinesForDisplay); 229 StringBuilder result = new StringBuilder(); 230 assert(aTotalLinesInFile >= aNumLinesForDisplay); 231 int start = aTotalLinesInFile - aNumLinesForDisplay; 232 Scanner scanner = null; 233 try { 234 scanner = new Scanner(aFile); 235 } 236 catch(FileNotFoundException ex){ 237 throw new DAOException("Cannot find file " + Util.quote(aFile.getAbsolutePath()), ex); 238 } 239 int lineCount = 0; 240 while ( scanner.hasNextLine()) { 241 String line = scanner.nextLine(); 242 lineCount++; 243 if ( lineCount >= start ) { 244 result.append(line + NEW_LINE); 245 } 246 } 247 scanner.close(); 248 return result.toString(); 249 } 250 251 /** Return a FileFilter appropriate for the type of log. */ 252 private FileFilter getFilter(LogFor aLogFor){ 253 FileFilter result = null; 254 final LogInfo dirInfo = fDirInfoDAO.fetch(); 255 if( LogFor.Server == aLogFor ) { 256 result = acceptIfNameStartsWithSpecificText(dirInfo); 257 } 258 else if ( LogFor.Application == aLogFor ) { 259 result = rejectEmptyFiles(); 260 } 261 return result; 262 } 263 264 /** Accept if non-zero size and name starts with specific text. */ 265 private FileFilter acceptIfNameStartsWithSpecificText(LogInfo aDirInfo){ 266 final LogInfo dirInfo = aDirInfo; 267 return new FileFilter() { 268 public boolean accept(File aFile) { 269 boolean nonZeroSize = (aFile.length() > 0); 270 boolean fileNameStartsWith = aFile.getAbsolutePath().startsWith(dirInfo.getServerLoggingDirectory().getRawString() + dirInfo.getServerLogFileStartsWith()); 271 return nonZeroSize && fileNameStartsWith; 272 } 273 }; 274 } 275 276 /** Accept only if non-zero size. */ 277 private FileFilter rejectEmptyFiles(){ 278 return new FileFilter() { 279 public boolean accept(File aFile) { 280 return (aFile.length() > 0); 281 } 282 }; 283 } 284 285 /** 286 * Return the first {@link LoggerRecord} appearing in a file. 287 */ 288 private LoggerRecord firstRecordFor(File aLogFile) throws DAOException { 289 //first N lines - will often cut off record at the end 290 fLogger.fine("Getting first record for " + aLogFile.getName()); 291 String firstLines = getAllOrFirst(aLogFile, 100); 292 LogParser parser = LogParserInstance.getFor(LogFor.Application); 293 List<LoggerRecord> records = parser.parse(firstLines, ParsedCriteria.forDowntimeListing()); 294 fLogger.fine("Num logger records at start of file " + records.size()); 295 return records.get(0); 296 } 297 }