/***************************************************************************
* Copyright (c) 2013 VMware, Inc. All Rights Reserved.
* Licensed 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.
***************************************************************************/
/***************************************************************************
* Copyright (c) 2012 VMware, Inc. All Rights Reserved.
* Licensed 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 com.vmware.vhadoop.util;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.IllegalFormatConversionException;
import java.util.Map;
import java.util.Set;
import java.util.UnknownFormatConversionException;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class LogFormatter extends Formatter {
public static final Map<String, String> _vmIdToNameMapper = Collections.synchronizedMap(new HashMap<String, String>());
public static final Map<String, String> _clusterIdToNameMapper = Collections.synchronizedMap(new HashMap<String, String>());
public static final String VMID_PREFIX = "<%V";
public static final String VMID_POSTFIX = "%V>";
public static final String CLUSTERID_PREFIX = "<%C";
public static final String CLUSTERID_POSTFIX = "%C>";
public static final String NEWLINE = System.getProperty("line.separator");
private static final int NEWLINE_LENGTH = NEWLINE == null ? 0 : NEWLINE.length();
private boolean decorated;
protected LogFormatter(boolean decorated) {
this.decorated = decorated;
}
public LogFormatter() {
}
@Override
public String format(LogRecord record) {
String name = record.getLoggerName();
if (name == null) {
name = "root";
} else {
int ix = name.lastIndexOf('.');
if (ix != -1) {
name = name.substring(ix + 1);
}
}
StringBuilder result = new StringBuilder();
// timestamp prefix (e.g. 2012 Sep 17 17:20:20.852)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy MMM dd HH:mm:ss.SSS");
result.append(sdf.format(new Date(record.getMillis())));
if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
result.append(" **");
} else {
result.append(" ");
}
if (decorated) {
result.append(" [").append(Thread.currentThread().getName()).append("-").append(name);
/* Fine logging is for method entry/exit so we add the method name */
if (record.getLevel().equals(Level.FINE) || record.getLevel().equals(Level.FINER)
|| record.getLevel().equals(Level.FINEST)) {
result.append(".").append(record.getSourceMethodName());
}
result.append("] ");
} else {
result.append(" ");
}
Object[] params = record.getParameters();
String rawMessage = null;
if ((params != null) && (params.length > 0)) {
try {
rawMessage = String.format(record.getMessage(), params);
result.append(rawMessage);
} catch (IllegalFormatConversionException e) {
} catch (UnknownFormatConversionException e) {
result.append("FOUND BADLY FORMATTED LOG MSG: ").append(record.getMessage());
}
} else {
result.append((rawMessage = record.getMessage()));
}
result.append(NEWLINE);
if (record.getThrown() != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(baos);
record.getThrown().printStackTrace(pw);
pw.close();
result.append(baos.toString());
result.append(NEWLINE);
}
try {
result = swapIdsForNames(result);
} catch (Exception e) {
result.insert(0, "FOUND BADLY FORMATTED LOG MSG: ");
}
// if (record.getLevel().equals(Level.SEVERE) || record.getLevel().equals(Level.WARNING)) {
// throw new RuntimeException("Severe or warning log message received: "+result);
// }
return result.toString();
}
private static class IntWrapper {
int _nextKey;
}
private static int substituteIdForName(StringBuilder hasIds, StringBuilder result, Map<String, String> mapper,
IntWrapper state, String prefixStr, String postfixStr, int nextInbetweenText) {
int postfixLength = postfixStr.length();
int prefixLength = prefixStr.length();
int hasIdsLength = hasIds.length();
int startCurrentKey = state._nextKey;
int endCurrentKey = hasIds.indexOf(postfixStr, startCurrentKey);
int newInbetweenText = nextInbetweenText;
/* If the current key has no postfix, it is either the end of the string or is badly formatted */
if (endCurrentKey < 0) {
int nextSpace = hasIds.indexOf(" ", state._nextKey); /* Is it the end of the String? */
postfixLength = 0;
if (nextSpace < 0) { /* Yes, it is the end of the String */
endCurrentKey = hasIdsLength;
if (hasIds.substring(hasIdsLength - NEWLINE_LENGTH).equals(NEWLINE)) {
endCurrentKey-= NEWLINE_LENGTH;
}
}
}
/* If this is well formatted */
if (endCurrentKey > 0) {
String id = hasIds.substring(startCurrentKey + prefixLength, endCurrentKey);
String name = mapper.get(id);
result.append(hasIds.substring(nextInbetweenText, state._nextKey)); /* Append the bit before the substitution */
result.append((name == null) ? id : name); /* Append the substitution */
state._nextKey = endCurrentKey;
newInbetweenText = endCurrentKey + postfixLength;
} else {
/* Badly formatted - skip on */
state._nextKey = state._nextKey + prefixLength;
}
/* Return the index of the text immediately after the substitution */
return newInbetweenText;
}
/* Formatter substitution for VM and Cluster Ids:
* A VM ID should be wrapped in <%V %V>, unless it is the last part of the String, in which case <%V will suffice
* A Cluster ID should be wrapped in <%C %C>, unless it is the last part of the String, in which case <%C will suffice
* Eg. "This is a vm <%V"+vmid+"%V> that I'm printing"
* Eg. "This is the last word on vm <%V"+vmId
* If a VM or ClusterId is unrecognized, the formatting is stripped out and the Id is used
*/
static StringBuilder swapIdsForNames(StringBuilder hasIds) {
StringBuilder result = hasIds;
boolean hasVMKey = false;
boolean hasClusterKey = false;
IntWrapper vmKeyState = new IntWrapper();
IntWrapper clusterKeyState = new IntWrapper();
int nextInbetweenText = -1;
do {
if (vmKeyState._nextKey >= 0) {
vmKeyState._nextKey = hasIds.indexOf(VMID_PREFIX, vmKeyState._nextKey);
}
if (clusterKeyState._nextKey >= 0) {
clusterKeyState._nextKey = hasIds.indexOf(CLUSTERID_PREFIX, clusterKeyState._nextKey);
}
hasVMKey = vmKeyState._nextKey >= 0;
hasClusterKey = clusterKeyState._nextKey >= 0;
if (hasVMKey || hasClusterKey) {
if (result == hasIds) {
/* We're going to transform the string, so don't return the original, build a new one */
result = new StringBuilder();
}
if (nextInbetweenText == -1) {
nextInbetweenText = 0;
}
if (hasVMKey && (!hasClusterKey || (vmKeyState._nextKey < clusterKeyState._nextKey))) {
nextInbetweenText = substituteIdForName(hasIds, result, _vmIdToNameMapper, vmKeyState,
VMID_PREFIX, VMID_POSTFIX, nextInbetweenText);
} else
if (hasClusterKey && (!hasVMKey || (clusterKeyState._nextKey < vmKeyState._nextKey))) {
nextInbetweenText = substituteIdForName(hasIds, result, _clusterIdToNameMapper, clusterKeyState,
CLUSTERID_PREFIX, CLUSTERID_POSTFIX, nextInbetweenText);
}
}
} while (hasVMKey || hasClusterKey);
if (result != hasIds) {
result.append(hasIds.substring(nextInbetweenText));
}
return result;
}
private static String constructListOfLoggableEntities(Set<String> ids, String prefix, String postfix) {
if (ids == null) {
return "null";
}
if (ids.isEmpty()) {
return "[]";
}
StringBuilder sb = new StringBuilder();
for (String id : ids) {
sb.append(prefix).append(id).append(postfix).append(", ");
}
sb.delete(sb.length()-2, sb.length());
return sb.toString();
}
public static String constructListOfLoggableVms(Set<String> vmIds) {
return constructListOfLoggableEntities(vmIds, "<%V", "%V>");
}
public static String constructListOfLoggableClusters(Set<String> clusterIds) {
return constructListOfLoggableEntities(clusterIds, "<%C", "%C>");
}
public static boolean isDetailLogging(Logger logger) {
if (logger == null) {
return false;
}
Level logLevel = logger.getLevel();
Logger parent = logger;
while ((logLevel == null) && (parent = parent.getParent()) != null) {
logLevel = parent.getLevel();
}
return (logLevel != null) && ((logLevel.equals(Level.FINE) || logLevel.equals(Level.FINER) || logLevel.equals(Level.FINEST)));
}
}