/* * Copyright 2007 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 com.google.gwt.dev.util.log; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import java.util.HashSet; /** * Abstract base class for TreeLoggers. */ public abstract class AbstractTreeLogger extends TreeLogger { private static class UncommittedBranchData { public final Throwable caught; public final String message; public final TreeLogger.Type type; private final HelpInfo helpInfo; public UncommittedBranchData(Type type, String message, Throwable caught, HelpInfo helpInfo) { this.caught = caught; this.message = message; this.type = type; this.helpInfo = helpInfo; } } // This message is package-protected so that the unit test can access it. static final String OUT_OF_MEMORY_MSG = "Out of memory; to increase the " + "amount of memory, use the -Xmx flag at startup (java -Xmx128M ...)"; // This message is package-protected so that the unit test can access it. static final String STACK_OVERFLOW_MSG = "Stack overflow; to increase the " + "stack size, use the -Xss flag at startup (java -Xss1M ...)"; public static String getStackTraceAsString(Throwable e) { // Show the exception info for anything other than "UnableToComplete". if (e == null || e instanceof UnableToCompleteException) { return null; } // For each cause, print the requested number of entries of its stack // trace, being careful to avoid getting stuck in an infinite loop. // StringBuffer message = new StringBuffer(); Throwable currentCause = e; String causedBy = ""; HashSet<Throwable> seenCauses = new HashSet<Throwable>(); while (currentCause != null && !seenCauses.contains(currentCause)) { seenCauses.add(currentCause); message.append(causedBy); causedBy = "\nCaused by: "; // after 1st, all say "caused by" message.append(currentCause.getClass().getName()); message.append(": " + currentCause.getMessage()); StackTraceElement[] stackElems = currentCause.getStackTrace(); if (stackElems != null) { for (int i = 0; i < stackElems.length; ++i) { message.append("\n\tat "); message.append(stackElems[i].toString()); } } currentCause = currentCause.getCause(); } return message.toString(); } protected static String getExceptionName(Throwable e) { if (e == null || e instanceof UnableToCompleteException) { return null; } return e.getClass().getSimpleName(); } protected TreeLogger.Type logLevel = TreeLogger.ALL; protected AbstractTreeLogger parent; private int indexWithinMyParent; private int nextChildIndex; private final Object nextChildIndexLock = new Object(); private UncommittedBranchData uncommitted; /** * The constructor used when creating a top-level logger. */ protected AbstractTreeLogger() { } /** * Implements branching behavior that supports lazy logging for low-priority * branched loggers. */ @Override public final synchronized TreeLogger branch(TreeLogger.Type type, String msg, Throwable caught, HelpInfo helpInfo) { if (msg == null) { msg = "(Null branch message)"; } // Compute at which index the new child will be placed. // int childIndex = allocateNextChildIndex(); // The derived class creates the child logger. AbstractTreeLogger childLogger = doBranch(); // Set up the child logger. // // Unsynchronized operations on childLogger are safe since no other // thread could have a reference to it yet. childLogger.logLevel = logLevel; // Take a snapshot of the index that the branched child should have. // childLogger.indexWithinMyParent = childIndex; // Have the child hang onto this (its parent logger). // childLogger.parent = this; // We can avoid committing this branch entry until and unless some // child (or grandchild) tries to log something that is loggable, // in which case there will be cascading commits of the parent branches. // childLogger.uncommitted = new UncommittedBranchData(type, msg, caught, helpInfo); // This logic is intertwined with log(). If a log message is associated // with a special error condition, then we turn it into a branch, // so this method can be called directly from log(). It is of course // also possible for someone to call branch() directly. In either case, we // (1) turn the original message into an ERROR and // (2) drop an extra log message that explains how to recover String specialErrorMessage = causedBySpecialError(caught); if (specialErrorMessage != null) { type = TreeLogger.ERROR; childLogger.log(type, specialErrorMessage, null); } // Decide whether we want to log the branch message eagerly or lazily. // if (isLoggable(type)) { // We can commit this branch entry eagerly since it is a-priori loggable. // Commit the parent logger if necessary before continuing. // childLogger.commitMyBranchEntryInMyParentLogger(); } return childLogger; } public final int getBranchedIndex() { return indexWithinMyParent; } public final synchronized TreeLogger.Type getMaxDetail() { return logLevel; } public final AbstractTreeLogger getParentLogger() { return parent; } @Override public final synchronized boolean isLoggable(TreeLogger.Type type) { return !type.isLowerPriorityThan(logLevel); } /** * Immediately logs or ignores the specified messages, based on the specified * message type and this logger's settings. If the message is loggable, then * parent branches may be lazily created before the log can take place. */ @Override public final synchronized void log(TreeLogger.Type type, String msg, Throwable caught, HelpInfo helpInfo) { if (msg == null) { msg = "(Null log message)"; } // If this log message is caused by out of memory or stack overflow, we // provide a little extra help by creating a child log message. if (causedBySpecialError(caught) != null) { branch(TreeLogger.ERROR, msg, caught); return; } int childIndex = allocateNextChildIndex(); if (isLoggable(type)) { commitMyBranchEntryInMyParentLogger(); doLog(childIndex, type, msg, caught, helpInfo); } } /** * @param type the log type representing the most detailed level of logging * that the caller is interested in, or <code>null</code> to choose * the default level. */ public final synchronized void setMaxDetail(TreeLogger.Type type) { if (type == null) { type = TreeLogger.INFO; } logLevel = type; } @Override public String toString() { return getLoggerId(); } protected int allocateNextChildIndex() { synchronized (nextChildIndexLock) { // postincrement because we want indices to start at 0 return nextChildIndex++; } } /** * Commits the branch after ensuring that the parent logger (if there is one) * has been committed first. */ protected synchronized void commitMyBranchEntryInMyParentLogger() { // (Only the root logger doesn't have a parent.) // if (parent != null) { if (uncommitted != null) { // Commit the parent first. // parent.commitMyBranchEntryInMyParentLogger(); // Let the subclass do its thing to commit this branch. // parent.doCommitBranch(this, uncommitted.type, uncommitted.message, uncommitted.caught, uncommitted.helpInfo); // Release the uncommitted state. // uncommitted = null; } } } /** * Derived classes should override this method to return a branched logger. */ protected abstract AbstractTreeLogger doBranch(); /** * @deprecated This method has been deprecated; override * {@link #doCommitBranch(AbstractTreeLogger, com.google.gwt.core.ext.TreeLogger.Type, String, Throwable, com.google.gwt.core.ext.TreeLogger.HelpInfo)} * instead. * * @param childBeingCommitted * @param type * @param msg * @param caught */ @Deprecated protected final void doCommitBranch(AbstractTreeLogger childBeingCommitted, TreeLogger.Type type, String msg, Throwable caught) { } /** * Derived classes should override this method to actually commit the * specified message associated with this the root of this branch. */ protected abstract void doCommitBranch( AbstractTreeLogger childBeingCommitted, TreeLogger.Type type, String msg, Throwable caught, HelpInfo helpInfo); /** * @deprecated This method has been deprecated; override * {@link #branch(com.google.gwt.core.ext.TreeLogger.Type, String, Throwable, com.google.gwt.core.ext.TreeLogger.HelpInfo) * instead. * * @param indexOfLogEntryWithinParentLogger * @param type * @param msg * @param caught */ @Deprecated protected final void doLog(int indexOfLogEntryWithinParentLogger, TreeLogger.Type type, String msg, Throwable caught) { } /** * Derived classes should override this method to actually write a log * message. Note that {@link #isLoggable(TreeLogger.Type)} will have already * been called. */ protected abstract void doLog(int indexOfLogEntryWithinParentLogger, TreeLogger.Type type, String msg, Throwable caught, HelpInfo helpInfo); /** * Scans <code>t</code> and its causes for {@link OutOfMemoryError} or * {@link StackOverflowError}. * * @param t a possibly null {@link Throwable} * @return true if {@link OutOfMemoryError} or {@link StackOverflowError} * appears anywhere in the cause list or if <code>t</code> is an * {@link OutOfMemoryError} or {@link StackOverflowError. */ private String causedBySpecialError(Throwable t) { while (t != null) { if (t instanceof OutOfMemoryError) { return OUT_OF_MEMORY_MSG; } else if (t instanceof StackOverflowError) { return STACK_OVERFLOW_MSG; } t = t.getCause(); } return null; } private String getLoggerId() { if (parent != null) { if (parent.parent == null) { // Top-level return parent.getLoggerId() + getBranchedIndex(); } else { // Nested return parent.getLoggerId() + "." + getBranchedIndex(); } } else { // The root return "#"; } } }