org.leialearns.executable.SimpleFormatter Maven / Gradle / Ivy
The newest version!
package org.leialearns.executable;
import org.leialearns.common.Setting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.NOPLogger;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.StringTokenizer;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.leialearns.common.Static.join;
/**
* Provides a formatter that can be used with java.util.logging
to produce single-line log-entries that
* are reasonably compact and are prefixed with a sort-proof time-stamp.
*
* If the verbosity level of the special logger named stack-trace
is at least DEBUG, then stack traces
* are reformatted to make it easier to dismiss the technicalities of AOP related stack frames. If the
* verbosity level of the special logger named stack-trace
is at least TRACE, these stack-frames are
* indented, otherwise every block of one or more of these stack frames is replaced by "[...]
".
*/
public class SimpleFormatter extends Formatter {
private static final Logger logger = LoggerFactory.getLogger("stack-trace");
private static final int SOURCE_PART_MAX_LENGTH = 60;
private static final Pattern ORGANISATION_RE = Pattern.compile("^([^.]*[.][^.]*)[.](.*)$");
private static final ThreadLocal DATE_FORMATTER = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
}
};
private static final String[] DISMISS_RE_PARTS = new String[] {
"com[.]sun[.]proxy[.]",
"java[.]lang[.]reflect|sun[.]reflect[.]",
"net[.]sf[.]cglib",
"org[.]apache[.]maven[.]",
"org[.]hibernate[.]",
"org[.]junit[.]",
"org[.]leialearns[.]bridge[.]BridgeFactory[.$]",
"org[.]neo4j[.]",
"org[.]springframework[.]",
"sun[.]proxy[.]",
"[^(]*CGLIB[$][$]",
};
private static final Pattern DISMISS_RE = Pattern.compile("^([\t ]*at )(" + join("|", DISMISS_RE_PARTS) + ")(.*\n)", Pattern.MULTILINE);
private static final Pattern CAUSED_BY_RE = Pattern.compile("^Caused by", Pattern.MULTILINE);
private static final Pattern HEAD_RE = Pattern.compile("^((?:(?:[\t ]*at |[^\t ])[^\n]*\n)*).*", Pattern.DOTALL);
private static final Pattern DELETE_RE = Pattern.compile("(^[\t ]*at .*\n)+", Pattern.MULTILINE);
private final Setting regexFormat = new SilentSetting<>("Regex format flag", false);
/**
* Creates a new SimpleFormatter
instance.
*/
public SimpleFormatter() {
}
/**
* Sets property regexFormat
. When this property is set to true
, the
* organization and sourceClass fields in the log record are filled by a very simple
* regular expression match on the sourceClassName
property of the logRecord
.
*
* @param flag The value to use for regexFormat
*/
@SuppressWarnings("unused")
public void setRegexFormat(boolean flag) {
regexFormat.set(flag);
}
/**
* Formats a logRecord
.
*
* @param logRecord The logRecord
to format
* @return The string that contains the formatted record
*/
@Override
public String format(LogRecord logRecord) {
StringBuilder builder = new StringBuilder();
long millis = logRecord.getMillis();
String timestamp = DATE_FORMATTER.get().format(new Date(millis));
String level = logRecord.getLevel().getName();
String message = logRecord.getMessage();
message = message.replace("\n", "\n ");
String organisation = "";
String sourceClass = logRecord.getSourceClassName();
if (regexFormat.get()) {
Matcher matcher = ORGANISATION_RE.matcher(sourceClass);
if (matcher.matches()) {
organisation = matcher.group(1);
sourceClass = matcher.group(2);
}
} else {
StringTokenizer tokenizer = new StringTokenizer(sourceClass, ".");
List organisationList = new ArrayList<>();
LinkedList sourceClassList = new LinkedList<>();
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (organisationList.size() < 2) {
organisationList.add(token);
} else {
sourceClassList.addFirst(token);
}
}
if (!sourceClassList.isEmpty()) {
organisation = implode(".", organisationList);
ListIterator it = sourceClassList.listIterator();
boolean first = true;
int length = 0;
do {
String part = it.next();
int partLength = part.length();
boolean wasFirst = first;
if (first) {
first = false;
} else {
partLength++;
}
if (wasFirst || length + partLength < SOURCE_PART_MAX_LENGTH) {
length += partLength;
} else {
it.previous();
break;
}
} while (it.hasNext());
List partList = new ArrayList<>();
while (it.hasPrevious()) {
String part = it.previous();
partList.add(part);
}
sourceClass = implode(".", partList);
}
}
String sourceMethod = logRecord.getSourceMethodName();
boolean first = true;
for (String part : new String[] { timestamp, fix(level, 8), organisation, sourceClass, sourceMethod, message }) {
if (first) {
first = false;
} else {
builder.append("|");
}
builder.append(part);
}
builder.append('\n');
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
Throwable thrown = logRecord.getThrown();
if (thrown != null && logger.isDebugEnabled()) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
thrown.printStackTrace(printWriter);
printWriter.close();
String stackTrace = stringWriter.toString();
stackTrace = DISMISS_RE.matcher(stackTrace).replaceAll("$1 $2$3");
if (!logger.isTraceEnabled()) {
try {
StringBuilder stackBuilder = new StringBuilder();
Matcher causedBy = CAUSED_BY_RE.matcher(stackTrace);
int pos = 0;
String block;
while (causedBy.find()) {
int nextPos = causedBy.start();
block = stackTrace.substring(pos, nextPos);
pos = nextPos;
stackBuilder.append(reduceStackBlock(block));
}
block = stackTrace.substring(pos);
stackBuilder.append(reduceStackBlock(block));
stackTrace = stackBuilder.toString();
} catch (Throwable throwable) {
stackTrace = ">>> REDUCE FAILED: " + throwable + "\n" + stackTrace;
}
}
builder.append(stackTrace);
}
return builder.toString();
}
protected String reduceStackBlock(String block) {
String result;
Matcher matcher = HEAD_RE.matcher(block);
if (!matcher.matches()) {
result = ">>> SPLIT FAILED\n" + block;
} else {
String head = matcher.group(1);
String tail = block.substring(matcher.end(1));
tail = DELETE_RE.matcher(tail).replaceAll("\t[...]\n");
result = head + tail;
}
return result;
}
protected String fix(String field, int width) {
StringBuilder result = new StringBuilder();
int n;
n = field.length();
if (n > width) {
result.append(field.substring(n - width));
} else {
result.append(field);
}
n = result.length();
while (n < width) {
result.insert(0, ' ');
n++;
}
return result.toString();
}
protected String implode(String separator, List parts) {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (String part : parts) {
if (first) {
first = false;
} else {
builder.append(separator);
}
builder.append(part);
}
return builder.toString();
}
protected class SilentSetting extends Setting {
protected SilentSetting(String label, T defaultValue) {
super(label, defaultValue);
}
@Override
public Logger getLogger() {
return NOPLogger.NOP_LOGGER;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy