001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.util.log;
017:
018: import com.google.gwt.core.ext.TreeLogger;
019:
020: import java.util.ArrayList;
021: import java.util.HashSet;
022:
023: /**
024: * Abstract base class for TreeLoggers.
025: */
026: public abstract class AbstractTreeLogger implements TreeLogger {
027:
028: // This message is package-protected so that the unit test can access it.
029: static final String OUT_OF_MEMORY_MSG = "Out of memory; to increase the "
030: + "amount of memory, use the -Xmx flag at startup (java -Xmx128M ...)";
031:
032: private static class UncommittedBranchData {
033:
034: public final Throwable caught;
035:
036: public final String message;
037: public final TreeLogger.Type type;
038:
039: public UncommittedBranchData(Type type, String message,
040: Throwable exception) {
041: caught = exception;
042: this .message = message;
043: this .type = type;
044: }
045: }
046:
047: public static String getStackTraceAsString(Throwable e) {
048: // For each cause, print the requested number of entries of its stack trace,
049: // being careful to avoid getting stuck in an infinite loop.
050: //
051: StringBuffer message = new StringBuffer();
052: Throwable currentCause = e;
053: String causedBy = "";
054: HashSet<Throwable> seenCauses = new HashSet<Throwable>();
055: while (currentCause != null
056: && !seenCauses.contains(currentCause)) {
057: seenCauses.add(currentCause);
058:
059: StackTraceElement[] trace = currentCause.getStackTrace();
060: message.append(causedBy);
061: causedBy = "\nCaused by: "; // after 1st, all say "caused by"
062: message.append(currentCause.getClass().getName());
063: message.append(": " + currentCause.getMessage());
064: StackTraceElement[] stackElems = findMeaningfulStackTraceElements(trace);
065: if (stackElems != null) {
066: for (int i = 0; i < stackElems.length; ++i) {
067: message.append("\n\tat ");
068: message.append(stackElems[i].toString());
069: }
070: }
071:
072: currentCause = currentCause.getCause();
073: }
074: return message.toString();
075: }
076:
077: private static StackTraceElement[] findMeaningfulStackTraceElements(
078: StackTraceElement[] elems) {
079: ArrayList<StackTraceElement> goodElems = new ArrayList<StackTraceElement>();
080: StackTraceElement prevElem = null;
081: for (int i = 0; i < elems.length; i++) {
082: StackTraceElement elem = elems[i];
083: if (elem.getLineNumber() > 0) {
084: goodElems.add(elem);
085: if (goodElems.size() < 10
086: || prevElem.getClassName().equals(
087: elem.getClassName())) {
088: // Keep going.
089: prevElem = elem;
090: } else {
091: // That's enough.
092: break;
093: }
094: }
095: }
096: if (goodElems.size() > 0) {
097: return goodElems.toArray(new StackTraceElement[goodElems
098: .size()]);
099: } else {
100: return null;
101: }
102: }
103:
104: public int indexWithinMyParent;
105:
106: private TreeLogger.Type logLevel = TreeLogger.ALL;
107:
108: private int nextChildIndex;
109:
110: private final Object nextChildIndexLock = new Object();
111:
112: private AbstractTreeLogger parent;
113:
114: private UncommittedBranchData uncommitted;
115:
116: /**
117: * The constructor used when creating a top-level logger.
118: */
119: protected AbstractTreeLogger() {
120: }
121:
122: /**
123: * Implements branching behavior that supports lazy logging for low-priority
124: * branched loggers.
125: */
126: public final synchronized TreeLogger branch(TreeLogger.Type type,
127: String msg, Throwable caught) {
128:
129: if (msg == null) {
130: msg = "(Null branch message)";
131: }
132:
133: // Compute at which index the new child will be placed.
134: //
135: int childIndex = allocateNextChildIndex();
136:
137: // The derived class creates the child logger.
138: AbstractTreeLogger childLogger = doBranch();
139:
140: // Set up the child logger.
141: childLogger.logLevel = logLevel;
142:
143: // Take a snapshot of the index that the branched child should have.
144: //
145: childLogger.indexWithinMyParent = childIndex;
146:
147: // Have the child hang onto this (its parent logger).
148: //
149: childLogger.parent = this ;
150:
151: // We can avoid committing this branch entry until and unless some
152: // child (or grandchild) tries to log something that is loggable,
153: // in which case there will be cascading commits of the parent branches.
154: //
155: childLogger.uncommitted = new UncommittedBranchData(type, msg,
156: caught);
157:
158: // This logic is intertwined with log(). If a log message is associated
159: // with an out-of-memory condition, then we turn it into a branch,
160: // so this method can be called directly from log(). It is of course
161: // also possible for someone to call branch() directly. In either case, we
162: // (1) turn the original message into an ERROR and
163: // (2) drop an extra log message that explains how to recover
164: if (causedByOutOfMemory(caught)) {
165: type = TreeLogger.ERROR;
166: childLogger.log(type, OUT_OF_MEMORY_MSG, null);
167: }
168:
169: // Decide whether we want to log the branch message eagerly or lazily.
170: //
171: if (isLoggable(type)) {
172: // We can commit this branch entry eagerly since it is a-priori loggable.
173: // Commit the parent logger if necessary before continuing.
174: //
175: childLogger.commitMyBranchEntryInMyParentLogger();
176: }
177:
178: return childLogger;
179: }
180:
181: public final int getBranchedIndex() {
182: return indexWithinMyParent;
183: }
184:
185: public final AbstractTreeLogger getParentLogger() {
186: return parent;
187: }
188:
189: public final synchronized boolean isLoggable(TreeLogger.Type type) {
190: return !type.isLowerPriorityThan(logLevel);
191: }
192:
193: /**
194: * Immediately logs or ignores the specified messages, based on the specified
195: * message type and this logger's settings. If the message is loggable, then
196: * parent branches may be lazily created before the log can take place.
197: */
198: public final synchronized void log(TreeLogger.Type type,
199: String msg, Throwable caught) {
200:
201: if (msg == null) {
202: msg = "(Null log message)";
203: }
204:
205: // If this log message is caused by being out of memory, we
206: // provide a little extra help by creating a child log message.
207: if (causedByOutOfMemory(caught)) {
208: branch(TreeLogger.ERROR, msg, caught);
209: return;
210: }
211:
212: int childIndex = allocateNextChildIndex();
213: if (isLoggable(type)) {
214: commitMyBranchEntryInMyParentLogger();
215: doLog(childIndex, type, msg, caught);
216: }
217: }
218:
219: /**
220: * @param type the log type representing the most detailed level of logging
221: * that the caller is interested in, or <code>null</code> to
222: * choose the default level.
223: */
224: public final synchronized void setMaxDetail(TreeLogger.Type type) {
225: if (type == null) {
226: type = TreeLogger.INFO;
227: }
228: logLevel = type;
229: }
230:
231: @Override
232: public String toString() {
233: return getLoggerId();
234: }
235:
236: /**
237: * Derived classes should override this method to return a branched logger.
238: */
239: protected abstract AbstractTreeLogger doBranch();
240:
241: /**
242: * Derived classes should override this method to actually commit the
243: * specified message associated with this the root of this branch.
244: */
245: protected abstract void doCommitBranch(
246: AbstractTreeLogger childBeingCommitted,
247: TreeLogger.Type type, String msg, Throwable caught);
248:
249: /**
250: * Dervied classes should override this method to actually write a log
251: * message. Note that {@link #isLoggable(TreeLogger.Type)} will have already
252: * been called.
253: */
254: protected abstract void doLog(
255: int indexOfLogEntryWithinParentLogger,
256: TreeLogger.Type type, String msg, Throwable caught);
257:
258: private int allocateNextChildIndex() {
259: synchronized (nextChildIndexLock) {
260: // postincrement because we want indices to start at 0
261: return nextChildIndex++;
262: }
263: }
264:
265: /**
266: * Scans <code>t</code> and its causes for {@link OutOfMemoryError}.
267: *
268: * @param t a possibly null {@link Throwable}
269: * @return true if {@link OutOfMemoryError} appears anywhere in the cause list
270: * or if <code>t</code> is an {@link OutOfMemoryError}.
271: */
272: private boolean causedByOutOfMemory(Throwable t) {
273:
274: while (t != null) {
275: if (t instanceof OutOfMemoryError) {
276: return true;
277: }
278: t = t.getCause();
279: }
280:
281: return false;
282: }
283:
284: /**
285: * Commits the branch after ensuring that the parent logger (if there is one)
286: * has been committed first.
287: */
288: private synchronized void commitMyBranchEntryInMyParentLogger() {
289: // (Only the root logger doesn't have a parent.)
290: //
291: if (parent != null) {
292: if (uncommitted != null) {
293: // Commit the parent first.
294: //
295: parent.commitMyBranchEntryInMyParentLogger();
296:
297: // Let the subclass do its thing to commit this branch.
298: //
299: parent.doCommitBranch(this , uncommitted.type,
300: uncommitted.message, uncommitted.caught);
301:
302: // Release the uncommitted state.
303: //
304: uncommitted = null;
305: }
306: }
307: }
308:
309: private String getLoggerId() {
310: if (parent != null) {
311: if (parent.parent == null) {
312: // Top-level
313: return parent.getLoggerId() + getBranchedIndex();
314: } else {
315: // Nested
316: return parent.getLoggerId() + "." + getBranchedIndex();
317: }
318: } else {
319: // The root
320: return "#";
321: }
322: }
323: }
|