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