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