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