001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.util.log;
018:
019: import java.util.Map;
020:
021: import org.apache.avalon.framework.logger.LogKitLogger;
022: import org.apache.cocoon.environment.ObjectModelHelper;
023: import org.apache.cocoon.environment.Request;
024: import org.apache.cocoon.util.location.LocatedException;
025: import org.apache.commons.lang.ClassUtils;
026: import org.apache.commons.lang.exception.ExceptionUtils;
027: import org.apache.commons.lang.time.FastDateFormat;
028: import org.apache.log.ContextMap;
029: import org.apache.log.LogEvent;
030: import org.apache.log.Logger;
031:
032: /**
033: * An extended pattern formatter. New patterns defined by this class are:
034: * <ul>
035: * <li><code>class</code>: Outputs the name of the class that has logged the
036: * message. The optional <code>short</code> subformat removes the
037: * package name. Warning: This pattern works only if formatting occurs in
038: * the same thread as the call to Logger, i.e. it won't work with
039: * <code>AsyncLogTarget</code>.</li>
040: * <li><code>uri</code>: Outputs the request URI.</li>
041: * <li><code>query</code>: Outputs the request query string</li>
042: * <li><code>thread</code>: Outputs the name of the current thread (first element
043: * on the context stack).</li>
044: * <li><code>host</code>: Outputs the request host header.<li>
045: * <li><code>rootThrowable</code>: Outputs the root throwable message and
046: * stacktrace.<li>
047: * </ul>
048: *
049: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
050: * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
051: * @deprecated This class will be removed in 2.2
052: * @version $Id: CocoonLogFormatter.java 433543 2006-08-22 06:22:54Z crossley $
053: */
054: public class CocoonLogFormatter extends ExtensiblePatternFormatter {
055: /**
056: * The constant defining the default stack depth when
057: * none other is specified.
058: */
059: public static final int DEFAULT_STACK_DEPTH = 8;
060:
061: protected final static int TYPE_CLASS = MAX_TYPE + 1;
062: protected final static int TYPE_URI = MAX_TYPE + 2;
063: protected final static int TYPE_THREAD = MAX_TYPE + 3;
064: protected final static int TYPE_HOST = MAX_TYPE + 4;
065: protected final static int TYPE_QUERY = MAX_TYPE + 5;
066: protected final static int TYPE_ROOTTHROWABLE = MAX_TYPE + 6;
067:
068: protected final static String TYPE_CLASS_STR = "class";
069: protected final static String TYPE_CLASS_SHORT_STR = "short";
070:
071: protected final static String TYPE_URI_STR = "uri";
072: protected final static String TYPE_THREAD_STR = "thread";
073: protected final static String TYPE_HOST_STR = "host";
074: protected final static String TYPE_QUERY_STR = "query";
075: protected final static String TYPE_ROOTTHROWABLE_STR = "rootThrowable";
076:
077: private static final String DEFAULT_TIME_PATTERN = "(yyyy-MM-dd) HH:mm.ss:SSS";
078: private static final FastDateFormat dateFormatter = FastDateFormat
079: .getInstance(DEFAULT_TIME_PATTERN);
080:
081: /**
082: * Hack to get the call stack as an array of classes. The
083: * SecurityManager class provides it as a protected method, so
084: * change it to public through a new method !
085: */
086: static class CallStack extends SecurityManager {
087: /**
088: * Returns the current execution stack as an array of classes.
089: * The length of the array is the number of methods on the execution
090: * stack. The element at index 0 is the class of the currently executing
091: * method, the element at index 1 is the class of that method's caller,
092: * and so on.
093: *
094: * @return current execution stack as an array of classes.
095: */
096: public Class[] get() {
097: return getClassContext();
098: }
099: }
100:
101: /**
102: * The class that we will search for in the call stack
103: * (Avalon logging abstraction)
104: */
105: private final Class logkitClass = LogKitLogger.class;
106:
107: /**
108: * The class that we will search for in the call stack
109: * (LogKit logger)
110: */
111: private final Class loggerClass = Logger.class;
112:
113: /**
114: * The SecurityManager implementation which gives us access to
115: * the stack frame
116: */
117: private CallStack callStack;
118:
119: /**
120: * The depth to which stacktraces are printed out
121: */
122: //private final int m_stackDepth;
123:
124: public CocoonLogFormatter() {
125: this (DEFAULT_STACK_DEPTH);
126: }
127:
128: public CocoonLogFormatter(int stackDepth) {
129: try {
130: this .callStack = new CallStack();
131: } catch (SecurityException e) {
132: // Ignore security exception
133: }
134: //this.m_stackDepth = stackDepth;
135: }
136:
137: protected int getTypeIdFor(String type) {
138: // Search for new patterns defined here, or else delegate
139: // to the parent class
140: if (type.equalsIgnoreCase(TYPE_CLASS_STR)) {
141: return TYPE_CLASS;
142: } else if (type.equalsIgnoreCase(TYPE_URI_STR)) {
143: return TYPE_URI;
144: } else if (type.equalsIgnoreCase(TYPE_THREAD_STR)) {
145: return TYPE_THREAD;
146: } else if (type.equalsIgnoreCase(TYPE_HOST_STR)) {
147: return TYPE_HOST;
148: } else if (type.equalsIgnoreCase(TYPE_QUERY_STR)) {
149: return TYPE_QUERY;
150: } else if (type.equalsIgnoreCase(TYPE_ROOTTHROWABLE_STR)) {
151: return TYPE_ROOTTHROWABLE;
152: } else {
153: return super .getTypeIdFor(type);
154: }
155: }
156:
157: protected String formatPatternRun(LogEvent event, PatternRun run) {
158: // Format new patterns defined here, or else delegate to
159: // the parent class
160: switch (run.m_type) {
161: case TYPE_CLASS:
162: return getClass(run.m_format);
163: case TYPE_URI:
164: return getURI(event.getContextMap());
165: case TYPE_THREAD:
166: return getThread(event.getContextMap());
167: case TYPE_HOST:
168: return getHost(event.getContextMap());
169: case TYPE_QUERY:
170: return getQueryString(event.getContextMap());
171: case TYPE_ROOTTHROWABLE:
172: Throwable thr = event.getThrowable();
173: Throwable root = ExceptionUtils.getRootCause(thr); // Can be null if no cause
174: return getStackTrace(root == null ? thr : root,
175: run.m_format);
176: }
177: return super .formatPatternRun(event, run);
178: }
179:
180: /**
181: * Finds the class that has called Logger.
182: */
183: private String getClass(String format) {
184: if (this .callStack != null) {
185: Class[] stack = this .callStack.get();
186:
187: // Traverse the call stack in reverse order until we find a Logger
188: for (int i = stack.length - 1; i >= 0; i--) {
189: if (this .logkitClass.isAssignableFrom(stack[i])
190: || this .loggerClass.isAssignableFrom(stack[i])) {
191: // Found: the caller is the previous stack element
192: String className = stack[i + 1].getName();
193: // Handle optional format
194: if (TYPE_CLASS_SHORT_STR.equalsIgnoreCase(format)) {
195: className = ClassUtils
196: .getShortClassName(className);
197: }
198: return className;
199: }
200: }
201: }
202:
203: // No callStack: can occur when running under SecurityManager, or
204: // no logger found in call stack: can occur with AsyncLogTarget
205: // where formatting takes place in a different thread.
206: return "Unknown-Class";
207: }
208:
209: /**
210: * Find the URI that is being processed.
211: */
212: private String getURI(ContextMap ctxMap) {
213: // Get URI from the the object model.
214: if (ctxMap != null) {
215: final Object context = ctxMap.get("objectModel");
216: if (context != null && context instanceof Map) {
217: // Get the request
218: final Request request = ObjectModelHelper
219: .getRequest((Map) context);
220: if (request != null) {
221: return request.getRequestURI();
222: }
223: }
224: }
225:
226: return "Unknown-URI";
227: }
228:
229: /**
230: * Find request query string
231: */
232: private String getQueryString(ContextMap ctxMap) {
233: if (ctxMap != null) {
234: final Object context = ctxMap.get("objectModel");
235: if (context != null && context instanceof Map) {
236: // Get the request
237: final Request request = ObjectModelHelper
238: .getRequest((Map) context);
239: if (request != null) {
240: final String queryString = request.getQueryString();
241: if (queryString != null) {
242: return "?" + queryString;
243: }
244: }
245: }
246: }
247: return "";
248: }
249:
250: /**
251: * Find the host header of the request that is being processed.
252: */
253: private String getHost(ContextMap ctxMap) {
254: // Get URI from the the object model.
255: if (ctxMap != null) {
256: final Object context = ctxMap.get("objectModel");
257: if (context != null && context instanceof Map) {
258: // Get the request
259: final Request request = ObjectModelHelper
260: .getRequest((Map) context);
261: if (request != null) {
262: return request.getHeader("host");
263: }
264: }
265: }
266:
267: return "Unknown-Host";
268: }
269:
270: /**
271: * Find the thread that is logged this event.
272: */
273: private String getThread(ContextMap ctxMap) {
274: // Get thread name from the context.
275: if (ctxMap != null) {
276: final String threadName = (String) ctxMap.get("threadName");
277: if (threadName != null) {
278: return threadName;
279: }
280: }
281:
282: return "Unknown-Thread";
283: }
284:
285: /**
286: * Utility method to format stack trace so that CascadingExceptions are
287: * formatted with all nested exceptions.
288: *
289: * <p>FIXME: copied from AvalonFormatter, to be removed if ExtensiblePatternFormatter
290: * replaces PatternFormatter.</p>
291: *
292: * @param throwable the throwable instance
293: * @param format ancilliary format parameter - allowed to be null
294: * @return the formatted string
295: */
296: protected String getStackTrace(final Throwable throwable,
297: final String format) {
298: if (throwable != null) {
299: LocatedException.ensureCauseChainIsSet(throwable);
300: return ExceptionUtils.getStackTrace(throwable);
301: //return ExceptionUtil.printStackTrace(throwable, m_stackDepth);
302: }
303:
304: return null;
305: }
306:
307: /**
308: * Utility method to format time.
309: *
310: * @param time the time
311: * @param pattern ancilliary pattern parameter - allowed to be null
312: * @return the formatted string
313: */
314: protected String getTime(final long time, String pattern) {
315: if (pattern == null || DEFAULT_TIME_PATTERN.equals(pattern)) {
316: return dateFormatter.format(time);
317: }
318: return FastDateFormat.getInstance(pattern).format(time);
319: }
320: }
|