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 */ 017package org.apache.juneau.microservice.resources; 018 019import java.io.*; 020import java.nio.charset.*; 021import java.text.*; 022import java.util.*; 023import java.util.regex.*; 024 025/** 026 * Utility class for reading log files. 027 * 028 * <p> 029 * Provides the capability of returning splices of log files based on dates and filtering based on thread and logger 030 * names. 031 */ 032public class LogParser implements Iterable<LogParser.Entry>, Iterator<LogParser.Entry>, Closeable { 033 private BufferedReader br; 034 LogEntryFormatter formatter; 035 Date start, end; 036 Set<String> loggerFilter, severityFilter; 037 String threadFilter; 038 private Entry next; 039 040 /** 041 * Constructor. 042 * 043 * @param formatter The log entry formatter. 044 * @param f The log file. 045 * @param start Don't return rows before this date. If <jk>null</jk>, start from the beginning of the file. 046 * @param end Don't return rows after this date. If <jk>null</jk>, go to the end of the file. 047 * @param thread Only return log entries with this thread name. 048 * @param loggers Only return log entries produced by these loggers (simple class names). 049 * @param severity Only return log entries with the specified severity. 050 * @throws IOException Thrown by underlying stream. 051 */ 052 public LogParser(LogEntryFormatter formatter, File f, Date start, Date end, String thread, String[] loggers, String[] severity) throws IOException { 053 br = new BufferedReader(new InputStreamReader(new FileInputStream(f), Charset.defaultCharset())); 054 this.formatter = formatter; 055 this.start = start; 056 this.end = end; 057 this.threadFilter = thread; 058 if (loggers != null) 059 this.loggerFilter = new LinkedHashSet<>(Arrays.asList(loggers)); 060 if (severity != null) 061 this.severityFilter = new LinkedHashSet<>(Arrays.asList(severity)); 062 063 // Find the first line. 064 String line; 065 while (next == null && (line = br.readLine()) != null) { 066 Entry e = new Entry(line); 067 if (e.matches()) 068 next = e; 069 } 070 } 071 072 @Override /* Iterator */ 073 public boolean hasNext() { 074 return next != null; 075 } 076 077 @Override /* Iterator */ 078 public Entry next() { 079 Entry current = next; 080 Entry prev = next; 081 try { 082 next = null; 083 String line = null; 084 while (next == null && (line = br.readLine()) != null) { 085 Entry e = new Entry(line); 086 if (e.isRecord) { 087 if (e.matches()) 088 next = e; 089 prev = null; 090 } else { 091 if (prev != null) 092 prev.addText(e.line); 093 } 094 } 095 } catch (IOException e) { 096 throw new UncheckedIOException(e); 097 } 098 return current; 099 } 100 101 @Override /* Iterator */ 102 public void remove() { 103 throw new NoSuchMethodError(); 104 } 105 106 @Override /* Iterable */ 107 public Iterator<Entry> iterator() { 108 return this; 109 } 110 111 @Override /* Closeable */ 112 public void close() throws IOException { 113 br.close(); 114 } 115 116 /** 117 * Serializes the contents of the parsed log file to the specified writer and then closes the underlying reader. 118 * 119 * @param w The writer to write the log file to. 120 * @throws IOException Thrown by underlying stream. 121 */ 122 public void writeTo(Writer w) throws IOException { 123 try { 124 if (! hasNext()) 125 w.append("[EMPTY]"); 126 else for (LogParser.Entry le : this) 127 le.append(w); 128 } finally { 129 close(); 130 } 131 } 132 133 /** 134 * Represents a single line from the log file. 135 */ 136 @SuppressWarnings("javadoc") 137 public class Entry { 138 public Date date; 139 public String severity, logger; 140 protected String line, text; 141 protected String thread; 142 protected List<String> additionalText; 143 protected boolean isRecord; 144 145 Entry(String line) throws IOException { 146 try { 147 this.line = line; 148 Matcher m = formatter.getLogEntryPattern().matcher(line); 149 if (m.matches()) { 150 isRecord = true; 151 String s = formatter.getField("date", m); 152 if (s != null) 153 date = formatter.getDateFormat().parse(s); 154 thread = formatter.getField("thread", m); 155 severity = formatter.getField("level", m); 156 logger = formatter.getField("logger", m); 157 text = formatter.getField("msg", m); 158 if (logger != null && logger.indexOf('.') > -1) 159 logger = logger.substring(logger.lastIndexOf('.')+1); 160 } 161 } catch (ParseException e) { 162 throw new IOException(e); 163 } 164 } 165 166 void addText(String t) { 167 if (additionalText == null) 168 additionalText = new LinkedList<>(); 169 additionalText.add(t); 170 } 171 172 public String getText() { 173 if (additionalText == null) 174 return text; 175 int i = text.length(); 176 for (String s : additionalText) 177 i += s.length() + 1; 178 StringBuilder sb = new StringBuilder(i); 179 sb.append(text); 180 for (String s : additionalText) 181 sb.append('\n').append(s); 182 return sb.toString(); 183 } 184 185 public String getThread() { 186 return thread; 187 } 188 189 public Writer appendHtml(Writer w) throws IOException { 190 w.append(toHtml(line)).append("<br>"); 191 if (additionalText != null) 192 for (String t : additionalText) 193 w.append(toHtml(t)).append("<br>"); 194 return w; 195 } 196 197 protected Writer append(Writer w) throws IOException { 198 w.append(line).append('\n'); 199 if (additionalText != null) 200 for (String t : additionalText) 201 w.append(t).append('\n'); 202 return w; 203 } 204 205 boolean matches() { 206 if (! isRecord) 207 return false; 208 if (start != null && date.before(start)) 209 return false; 210 if (end != null && date.after(end)) 211 return false; 212 if (threadFilter != null && ! threadFilter.equals(thread)) 213 return false; 214 if (loggerFilter != null && ! loggerFilter.contains(logger)) 215 return false; 216 if (severityFilter != null && ! severityFilter.contains(severity)) 217 return false; 218 return true; 219 } 220 } 221 222 static String toHtml(String s) { 223 if (s.indexOf('<') != -1) 224 return s.replaceAll("<", "<");//$NON-NLS-2$ 225 return s; 226 } 227} 228