/* * 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.geode.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 org.apache.geode.cache.Cache; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.management.internal.cli.remote.CommandExecutionContext; import org.apache.geode.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() * * @since GemFire 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); } } /** * * @since GemFire 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 * * @since GemFire 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 {} } }