/** * Copyright 2008 Google Inc. * * 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 org.waveprotocol.wave.common.logging; /** * Skeleton debug logger. * * Implementations must override {@link #shouldLog} * */ public abstract class AbstractLogger implements LoggerBundle { /** * Constants for describing log levels */ public enum Level { FATAL("fatal", 0), ERROR("error", 1), TRACE("trace", 2); Level(String string, int value) { this.string = string; this.value = value; } private final String string; private final int value; public int value() { return value; } @Override public String toString() { return string; } /** * Return the Level that corresponds to an int value. */ public static Level getLevelByValue(int value) { if (value == TRACE.value()) { return TRACE; } else if (value == ERROR.value()) { return ERROR; } else { return FATAL; } } } private final LevelLogger errorLogger; private final LevelLogger fatalLogger; private final LevelLogger traceLogger; protected final LogSink sink; public AbstractLogger(LogSink sink) { this.sink = sink; errorLogger = new LevelLogger(Level.ERROR); fatalLogger = new LevelLogger(Level.FATAL); traceLogger = new LevelLogger(Level.TRACE); } @Override public final void log(Level level, Object... messages) { handleClientErrors(level, null, messages); if (shouldLog(level)) { doLog(level, messages); } } @Override public final Logger trace() { return traceLogger; } @Override public final Logger error() { return errorLogger; } @Override public final Logger fatal() { return fatalLogger; } // TODO(user): Eliminate this method. Make plain text the default and do the // escaping / wrapping in the sinks where appropriate. Add a log(SafeHtml) method // for cases where we actually want to output HTML protected void logPlainTextInner(Level level, String msg) { doLog(level, "<pre style='display:inline'>", LogUtils.xmlEscape(msg), "</pre>"); } /** * Logs messages to the sink. * * @param level level to log at * @param msgs Message to log */ protected final void doLog(Level level, Object... msgs) { sink.lazyLog(level, msgs); } /** * Determines whether this bundle is interested in logging events at * the provided level. * * @param level the level to check * @return true if the provided level is loggable */ protected abstract boolean shouldLog(Level level); /** * Should only be overridden by client.debug.logger.DomLogger. Performs error * handling that is specific to the client. * * @param level the level of the messages to be logged * @param t The exception. If null, a ClientLogException is created. * @param messages Messages to be concatenated to create the log message. */ // TODO(user): Eliminate this method. The client should handle its errors independently of // the logging infrastructure protected void handleClientErrors(Level level, Throwable t, Object... messages) { return; } /** * Represents an instance of a Logger, curried with a particular log level. * It's required to support the {@code bundle.fine().log("foo");}, pattern * in the LoggerBundle API. */ private final class LevelLogger implements Logger, NonNotifyingLogger { private final Level level; public LevelLogger(Level level) { this.level = level; } @Override public void log(String msg) { handleClientErrors(level, null, msg); if (shouldLog()) { doLog(level, msg); } } @Override public void log(Object... messages) { handleClientErrors(level, null, messages); if (shouldLog()) { doLog(level, messages); } } @Override public void logPlainText(String msg) { handleClientErrors(level, null, msg); if (shouldLog()) { // display:inline is nicer to display things because it does not have // extra padding at the top and bottom of the message. // This makes editor-io log look nice at least. logPlainTextInner(level, msg); } } @Override public void logPlainText(String msg, Throwable t) { handleClientErrors(level, t, msg); if (shouldLog()) { logPlainTextInner(level, msg + t.toString()); } } @Override public void logXml(String xml) { handleClientErrors(level, null, xml); if (shouldLog()) { doLog(level, LogUtils.xmlEscape(xml)); } } @Override public void log(String label, Object o) { handleClientErrors(level, null, label, o); if (shouldLog()) { doLog(level, (label.length() > 0 ? label + ": " : ""), LogUtils.printObjectAsHtml(o)); } } @Override public void log(Throwable t) { handleClientErrors(level, t, t.getMessage()); if (shouldLog()) { sink.lazyLog(level, t.toString(), t); } } @Override public void log(String label, Throwable t) { handleClientErrors(level, t, label); if (shouldLog()) { sink.lazyLog(level, label, t); } } @Override public void logLazyObjects(Object... objects) { handleClientErrors(level, null, objects); if (shouldLog()) { doLog(level, objects); } } @Override public boolean shouldLog() { return AbstractLogger.this.shouldLog(level); } @Override public void logWithoutNotifying(String message, Level level) { if (AbstractLogger.this instanceof NonNotifyingLogger) { ((NonNotifyingLogger) AbstractLogger.this).logWithoutNotifying(message, level); } } } /** * A logger which has a logWithoutNotifying method. Note that this does not * extend Logger because it's used by the LevelLogger above, as well as * by DomLogger (to avoid this package depending on the client). */ public interface NonNotifyingLogger { /** * Write a log entry without notifying the listeners. This is used internally * by the logging framework to write extra information about a log entry that * has already been sent to the log listeners. This method is required to * prevent looping of returned deobfuscated stack traces from WFE. * * @param message message to log * @param level level to log the message at */ void logWithoutNotifying(String message, Level level); } }