// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.log; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Vespa log formatting utility methods. * Contains some code based on Util.java in Cloudname https://github.com/Cloudname/cloudname * written by Bjørn Borud, licensed under the Apache 2.0 license. * * @author arnej27959 */ public class VespaFormat { private static final Pattern special = Pattern.compile("[\r\\\n\\\t\\\\]+"); private static final Pattern newLine = Pattern.compile("\n"); private static final Pattern carriage = Pattern.compile("\r"); private static final Pattern tab = Pattern.compile("\t"); private static final Pattern backSlash = Pattern.compile("\\\\"); private static final String hostname; private static final String processID; static { hostname = Util.getHostName(); processID = Util.getPID(); } /** * This static method is used to detect if a message needs * to be escaped, and if so, performs the escaping. Since the * common case is most likely that escaping is not * needed, the code is optimized for this case. The forbidden * characters are: * *
* Also handles the case where the message is null
* and replaces the null message with a tag saying that the
* value was "(empty)".
*
* @param s String that might need escaping
* @return Returns escaped string
*
*/
public static String escape (String s) {
if (s == null) {
return "(empty)";
}
Matcher m = special.matcher(s);
if (! m.find()) {
return s;
}
// invariant: we had special characters
m = backSlash.matcher(s);
if (m.find()) {
s = m.replaceAll("\\\\\\\\");
}
m = newLine.matcher(s);
if (m.find()) {
s = m.replaceAll("\\\\n");
}
m = carriage.matcher(s);
if (m.find()) {
s = m.replaceAll("\\\\r");
}
m = tab.matcher(s);
if (m.find()) {
s = m.replaceAll("\\\\t");
}
return s;
}
/**
* It is easier to slice and dice strings in Java than formatting
* numbers...
*/
public static void formatTime (long time, StringBuilder sbuffer) {
String timeString = Long.toString(time);
int len = timeString.length();
// something wrong. handle it by just returning the input
// long as a string. we prefer this to just crashing in
// the substring handling.
if (len < 3) {
sbuffer.append(timeString);
return;
}
sbuffer.append(timeString.substring(0, len - 3));
sbuffer.append('.');
sbuffer.append(timeString.substring(len - 3));
}
public static String format(String levelName,
String component,
String componentPrefix,
long millis,
String threadId,
String serviceName,
String formattedMessage,
Throwable t)
{
StringBuilder sbuf = new StringBuilder(300); // initial guess
// format the time
formatTime(millis, sbuf);
sbuf.append("\t");
sbuf.append(hostname).append("\t");
sbuf.append(processID);
if (threadId != null) {
sbuf.append("/").append(threadId);
}
sbuf.append("\t");
sbuf.append(serviceName).append("\t");
if (component == null && componentPrefix == null) {
sbuf.append("-");
} else if (component == null) {
sbuf.append(componentPrefix);
} else if (componentPrefix == null) {
sbuf.append(".").append(component);
} else {
sbuf.append(componentPrefix).append(".").append(component);
}
sbuf.append("\t");
sbuf.append(levelName).append("\t");
sbuf.append(escape(formattedMessage));
if (t != null) {
formatException(t, sbuf);
}
sbuf.append("\n");
return sbuf.toString();
}
/**
* Format throwable into given StringBuffer.
*
* @param t The Throwable we want to format
* @param sbuf The stringbuffer into which we wish to
* format the Throwable
*/
public static void formatException (Throwable t, StringBuilder sbuf) {
Throwable last = t;
int depth = 0;
while (last != null) {
sbuf.append("\\nmsg=\"");
sbuf.append(escape(last.getMessage()));
sbuf.append("\"\\nname=\"");
sbuf.append(escape(last.getClass().getName()));
sbuf.append("\"\\nstack=\"\\n");
// loop through stack frames and format them
StackTraceElement[] st = last.getStackTrace();
int stopAt = Math.min(st.length, 15);
boolean first = true;
for (int i = 0; i < stopAt; i++) {
if (first) {
first = false;
} else {
sbuf.append("\\n");
}
sbuf.append(escape(st[i].toString()));
}
// tell the reader if we chopped off part of the stacktrace
if (stopAt < st.length) {
sbuf.append("\\n[...]");
}
sbuf.append("\\n\"");
last = last.getCause();
depth++;
}
sbuf.append(" nesting=").append(depth);
}
}