blob: e56bb30a45ce512da4ab8ff1dc1046717aceb71b [file] [log] [blame]
/*
* =========================================================================
* Copyright (c) 2002-2014 Pivotal Software, Inc. All Rights Reserved.
* This product is protected by U.S. and international copyright
* and intellectual property laws. Pivotal products are covered by
* more patents listed at http://www.pivotal.io/patents.
* ========================================================================
*/
package com.gemstone.gemfire.management.internal.cli;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.BreakIterator;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.management.internal.cli.remote.CommandExecutionContext;
import com.gemstone.gemfire.management.internal.cli.shell.GfshConfig;
/**
* NOTE: Should be used only in
* 1. gfsh process
* 2. on a Manager "if" log is required to be sent back to gfsh too. For
* logging only on manager use, cache.getLogger()
*
* @author Abhishek Chaudhari
* @author John Blum
* @since 7.0
*/
public class LogWrapper {
private static Object INSTANCE_LOCK = new Object();
private volatile static LogWrapper INSTANCE = null;
private Logger logger;
private LogWrapper() {
logger = Logger.getLogger(this.getClass().getCanonicalName());
Cache cache = CliUtil.getCacheIfExists();
if (cache != null && !cache.isClosed()) {
//TODO - Abhishek how to set different log levels for different handlers???
logger.addHandler(cache.getLogger().getHandler());
CommandResponseWriterHandler handler = new CommandResponseWriterHandler();
handler.setFilter(new Filter() {
@Override
public boolean isLoggable(LogRecord record) {
return record.getLevel().intValue() >= Level.FINE.intValue();
}
});
handler.setLevel(Level.FINE);
logger.addHandler(handler);
}
logger.setUseParentHandlers(false);
}
public static LogWrapper getInstance() {
if (INSTANCE == null) {
synchronized (INSTANCE_LOCK) {
if (INSTANCE == null) {
INSTANCE = new LogWrapper();
}
}
}
return INSTANCE;
}
public void configure(GfshConfig config) {
if (config.isLoggingEnabled()) {
try {
FileHandler fileHandler = new FileHandler(config.getLogFilePath(),
config.getLogFileSizeLimit(),
config.getLogFileCount(),
true /*append*/);
fileHandler.setFormatter(new GemFireFormatter());
fileHandler.setLevel(config.getLogLevel());
logger.addHandler(fileHandler);
logger.setLevel(config.getLogLevel());
} catch (SecurityException e) {
addDefaultConsoleHandler(logger, e.getMessage(), config.getLogFilePath());
} catch (IOException e) {
addDefaultConsoleHandler(logger, e.getMessage(), config.getLogFilePath());
}
}
}
private static void addDefaultConsoleHandler(Logger logger, String errorMessage, String logFilePath) {
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new GemFireFormatter());
logger.addHandler(consoleHandler);
System.err.println("ERROR: Could not log to file: " + logFilePath + ". Reason: " + errorMessage);
System.err.println("Logs will be written on Console.");
try {
Thread.sleep(3000); //sleep for 3 secs for the message to appear
} catch (InterruptedException ignore) {}
}
/**
* Closes the current LogWrapper.
*/
public static void close() {
synchronized (INSTANCE_LOCK) {
if (INSTANCE != null) {
Logger innerLogger = INSTANCE.logger;
// remove any existing handlers
cleanupLogger(innerLogger);
}
// make singleton null
INSTANCE = null;
}
}
/**
* Removed all the handlers of the given {@link Logger} instance.
*
* @param logger {@link Logger} to be cleaned up.
*/
private static void cleanupLogger(Logger logger) {
if (logger != null) {
Handler[] handlers = logger.getHandlers();
for (Handler handler : handlers) {
handler.close();
logger.removeHandler(handler);
}
}
}
/**
* Make logger null when the singleton (which was referred by INSTANCE) gets
* garbage collected. Makes an attempt at removing associated {@link Handler}s
* of the {@link Logger}.
*/
protected void finalize() throws Throwable {
cleanupLogger(this.logger);
this.logger = null;
}
public void setParentFor(Logger otherLogger) {
if (otherLogger.getParent() != logger) {
otherLogger.setParent(logger);
}
}
public void setLogLevel(Level newLevel) {
if (logger.getLevel() != newLevel) {
logger.setLevel(newLevel);
}
}
public Level getLogLevel() {
return logger.getLevel();
}
//TODO - Abhishek - ideally shouldn't be exposed outside.
/*package*/ Logger getLogger() {
return logger;
}
public boolean severeEnabled() {
return logger.isLoggable(Level.SEVERE);
}
public void severe(String message) {
if (severeEnabled()) {
logger.severe(message);
}
}
public void severe(String message, Throwable t) {
if (severeEnabled()) {
logger.log(Level.SEVERE, message, t);
}
}
//TODO - Abhishek - Check whether we can use GemFireLevel.ERROR
// public boolean errorEnabled() {
// return severeEnabled();
// }
//
// public void error(String message) {
// logger.severe(message);
// }
//
// public void error(String message, Throwable t) {
// logger.log(Level.SEVERE, message, t);
// }
public boolean warningEnabled() {
return logger.isLoggable(Level.WARNING);
}
public void warning(String message) {
if (warningEnabled()) {
logger.warning(message);
}
}
public void warning(String message, Throwable t) {
if (warningEnabled()) {
logger.log(Level.WARNING, message, t);
}
}
public boolean infoEnabled() {
return logger.isLoggable(Level.INFO);
}
public void info(String message) {
if (infoEnabled()) {
logger.info(message);
}
}
public void info(String message, Throwable t) {
if (infoEnabled()) {
logger.log(Level.INFO, message, t);
}
}
public boolean configEnabled() {
return logger.isLoggable(Level.CONFIG);
}
public void config(String message) {
if (configEnabled()) {
logger.config(message);
}
}
public void config(String message, Throwable t) {
if (configEnabled()) {
logger.log(Level.CONFIG, message, t);
}
}
public boolean fineEnabled() {
return logger.isLoggable(Level.FINE);
}
public void fine(String message) {
if (fineEnabled()) {
logger.fine(message);
}
}
public void fine(String message, Throwable t) {
if (fineEnabled()) {
logger.log(Level.FINE, message, t);
}
}
public boolean finerEnabled() {
return logger.isLoggable(Level.FINER);
}
public void finer(String message) {
if (finerEnabled()) {
logger.finer(message);
}
}
public void finer(String message, Throwable t) {
if (finerEnabled()) {
logger.log(Level.FINER, message, t);
}
}
public boolean finestEnabled() {
return logger.isLoggable(Level.FINEST);
}
public void finest(String message) {
if (finestEnabled()) {
logger.finest(message);
}
}
public void finest(String message, Throwable t) {
if (finestEnabled()) {
logger.log(Level.FINEST, message, t);
}
}
/**
*
* @author Abhishek Chaudhari
* @since 7.0
*/
// Formatter code "copied" from LogWriterImpl
static class GemFireFormatter extends Formatter {
private final static String FORMAT = "yyyy/MM/dd HH:mm:ss.SSS z";
private SimpleDateFormat sdf = new SimpleDateFormat(FORMAT);
@Override
public synchronized String format(LogRecord record) {
java.io.StringWriter sw = new java.io.StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println();
pw.print('[');
pw.print(record.getLevel().getName().toLowerCase());
pw.print(' ');
pw.print(formatDate(new Date(record.getMillis())));
String threadName = Thread.currentThread().getName();
if (threadName != null) {
pw.print(' ');
pw.print(threadName);
}
pw.print(" tid=0x");
pw.print(Long.toHexString(Thread.currentThread().getId()));
pw.print("] ");
pw.print("(msgTID=");
pw.print(record.getThreadID());
pw.print(" msgSN=");
pw.print(record.getSequenceNumber());
pw.print(") ");
String msg = record.getMessage();
if (msg != null) {
try {
formatText(pw, msg, 40);
} catch (RuntimeException e) {
pw.println(msg);
pw.println(LocalizedStrings.GemFireFormatter_IGNORING_THE_FOLLOWING_EXCEPTION.toLocalizedString());
e.printStackTrace(pw);
}
} else {
pw.println();
}
if (record.getThrown() != null) {
record.getThrown().printStackTrace(pw);
}
pw.close();
try {
sw.close();
} catch (java.io.IOException ignore) {}
String result = sw.toString();
return result;
}
private void formatText(PrintWriter writer, String target, int initialLength) {
BreakIterator boundary = BreakIterator.getLineInstance();
boundary.setText(target);
int start = boundary.first();
int end = boundary.next();
int lineLength = initialLength;
while (end != BreakIterator.DONE) {
// Look at the end and only accept whitespace breaks
char endChar = target.charAt(end-1);
while (!Character.isWhitespace(endChar)) {
int lastEnd = end;
end = boundary.next();
if (end == BreakIterator.DONE) {
// give up. We are at the end of the string
end = lastEnd;
break;
}
endChar = target.charAt(end-1);
}
int wordEnd = end;
if (endChar == '\n') {
// trim off the \n since println will do it for us
wordEnd--;
if (wordEnd > 0 && target.charAt(wordEnd-1) == '\r') {
wordEnd--;
}
} else if (endChar == '\t') {
// figure tabs use 8 characters
lineLength += 7;
}
String word = target.substring(start, wordEnd);
lineLength += word.length();
writer.print(word);
if (endChar == '\n' || endChar == '\r') {
// force end of line
writer.println();
writer.print(" ");
lineLength = 2;
}
start = end;
end = boundary.next();
}
if (lineLength != 0) {
writer.println();
}
}
private String formatDate(Date date) {
return sdf.format(date);
}
}
/**
* Handler to write to CommandResponseWriter
*
* @author Abhishek Chaudhari
* @since 7.0
*/
static class CommandResponseWriterHandler extends Handler {
public CommandResponseWriterHandler() {
setFormatter(new GemFireFormatter());
}
@Override
public void publish(LogRecord record) {
CommandResponseWriter responseWriter = CommandExecutionContext.getAndCreateIfAbsentCommandResponseWriter();
responseWriter.println(getFormatter().format(record));
}
@Override
public void flush() {
}
@Override
public void close() throws SecurityException {
}
}
}