// *************************************************************************************************************************** | |
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * | |
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * | |
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * | |
// * with the License. You may obtain a copy of the License at * | |
// * * | |
// * http://www.apache.org/licenses/LICENSE-2.0 * | |
// * * | |
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * | |
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * | |
// * specific language governing permissions and limitations under the License. * | |
// *************************************************************************************************************************** | |
package org.apache.juneau.microservice.resources; | |
import java.io.*; | |
import java.nio.charset.*; | |
import java.text.*; | |
import java.util.*; | |
import java.util.regex.*; | |
/** | |
* Utility class for reading log files. | |
* | |
* <p> | |
* Provides the capability of returning splices of log files based on dates and filtering based on thread and logger | |
* names. | |
*/ | |
public final class LogParser implements Iterable<LogParser.Entry>, Iterator<LogParser.Entry>, Closeable { | |
private BufferedReader br; | |
LogEntryFormatter formatter; | |
Date start, end; | |
Set<String> loggerFilter, severityFilter; | |
String threadFilter; | |
private Entry next; | |
/** | |
* Constructor. | |
* | |
* @param formatter The log entry formatter. | |
* @param f The log file. | |
* @param start Don't return rows before this date. If <jk>null</jk>, start from the beginning of the file. | |
* @param end Don't return rows after this date. If <jk>null</jk>, go to the end of the file. | |
* @param thread Only return log entries with this thread name. | |
* @param loggers Only return log entries produced by these loggers (simple class names). | |
* @param severity Only return log entries with the specified severity. | |
* @throws IOException Thrown by underlying stream. | |
*/ | |
public LogParser(LogEntryFormatter formatter, File f, Date start, Date end, String thread, String[] loggers, String[] severity) throws IOException { | |
br = new BufferedReader(new InputStreamReader(new FileInputStream(f), Charset.defaultCharset())); | |
this.formatter = formatter; | |
this.start = start; | |
this.end = end; | |
this.threadFilter = thread; | |
if (loggers != null) | |
this.loggerFilter = new HashSet<>(Arrays.asList(loggers)); | |
if (severity != null) | |
this.severityFilter = new HashSet<>(Arrays.asList(severity)); | |
// Find the first line. | |
String line; | |
while (next == null && (line = br.readLine()) != null) { | |
Entry e = new Entry(line); | |
if (e.matches()) | |
next = e; | |
} | |
} | |
@Override /* Iterator */ | |
public boolean hasNext() { | |
return next != null; | |
} | |
@Override /* Iterator */ | |
public Entry next() { | |
Entry current = next; | |
Entry prev = next; | |
try { | |
next = null; | |
String line = null; | |
while (next == null && (line = br.readLine()) != null) { | |
Entry e = new Entry(line); | |
if (e.isRecord) { | |
if (e.matches()) | |
next = e; | |
prev = null; | |
} else { | |
if (prev != null) | |
prev.addText(e.line); | |
} | |
} | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
return current; | |
} | |
@Override /* Iterator */ | |
public void remove() { | |
throw new NoSuchMethodError(); | |
} | |
@Override /* Iterable */ | |
public Iterator<Entry> iterator() { | |
return this; | |
} | |
@Override /* Closeable */ | |
public void close() throws IOException { | |
br.close(); | |
} | |
/** | |
* Serializes the contents of the parsed log file to the specified writer and then closes the underlying reader. | |
* | |
* @param w The writer to write the log file to. | |
* @throws IOException Thrown by underlying stream. | |
*/ | |
public void writeTo(Writer w) throws IOException { | |
try { | |
if (! hasNext()) | |
w.append("[EMPTY]"); | |
else for (LogParser.Entry le : this) | |
le.append(w); | |
} finally { | |
close(); | |
} | |
} | |
/** | |
* Represents a single line from the log file. | |
*/ | |
@SuppressWarnings("javadoc") | |
public final class Entry { | |
public Date date; | |
public String severity, logger; | |
protected String line, text; | |
protected String thread; | |
protected List<String> additionalText; | |
protected boolean isRecord; | |
Entry(String line) throws IOException { | |
try { | |
this.line = line; | |
Matcher m = formatter.getLogEntryPattern().matcher(line); | |
if (m.matches()) { | |
isRecord = true; | |
String s = formatter.getField("date", m); | |
if (s != null) | |
date = formatter.getDateFormat().parse(s); | |
thread = formatter.getField("thread", m); | |
severity = formatter.getField("level", m); | |
logger = formatter.getField("logger", m); | |
text = formatter.getField("msg", m); | |
if (logger != null && logger.indexOf('.') > -1) | |
logger = logger.substring(logger.lastIndexOf('.')+1); | |
} | |
} catch (ParseException e) { | |
throw new IOException(e); | |
} | |
} | |
void addText(String t) { | |
if (additionalText == null) | |
additionalText = new LinkedList<>(); | |
additionalText.add(t); | |
} | |
public String getText() { | |
if (additionalText == null) | |
return text; | |
int i = text.length(); | |
for (String s : additionalText) | |
i += s.length() + 1; | |
StringBuilder sb = new StringBuilder(i); | |
sb.append(text); | |
for (String s : additionalText) | |
sb.append('\n').append(s); | |
return sb.toString(); | |
} | |
public String getThread() { | |
return thread; | |
} | |
public Writer appendHtml(Writer w) throws IOException { | |
w.append(toHtml(line)).append("<br>"); | |
if (additionalText != null) | |
for (String t : additionalText) | |
w.append(toHtml(t)).append("<br>"); | |
return w; | |
} | |
protected Writer append(Writer w) throws IOException { | |
w.append(line).append('\n'); | |
if (additionalText != null) | |
for (String t : additionalText) | |
w.append(t).append('\n'); | |
return w; | |
} | |
boolean matches() { | |
if (! isRecord) | |
return false; | |
if (start != null && date.before(start)) | |
return false; | |
if (end != null && date.after(end)) | |
return false; | |
if (threadFilter != null && ! threadFilter.equals(thread)) | |
return false; | |
if (loggerFilter != null && ! loggerFilter.contains(logger)) | |
return false; | |
if (severityFilter != null && ! severityFilter.contains(severity)) | |
return false; | |
return true; | |
} | |
} | |
static final String toHtml(String s) { | |
if (s.indexOf('<') != -1) | |
return s.replaceAll("<", "<");//$NON-NLS-2$ | |
return s; | |
} | |
} | |