blob: dd364259696aa6e8ea4e7b8a338f74129371dbe3 [file] [log] [blame]
/*
* Copyright 1999-2005 The Apache Software Foundation.
*
* 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.apache.cocoon.util.log;
import org.apache.avalon.framework.logger.LogKitLogger;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.util.location.LocatedException;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.log.ContextMap;
import org.apache.log.LogEvent;
import org.apache.log.Logger;
import java.util.Map;
/**
* An extended pattern formatter. New patterns defined by this class are:
* <ul>
* <li><code>class</code>: Outputs the name of the class that has logged the
* message. The optional <code>short</code> subformat removes the
* package name. Warning: This pattern works only if formatting occurs in
* the same thread as the call to Logger, i.e. it won't work with
* <code>AsyncLogTarget</code>.</li>
* <li><code>uri</code>: Outputs the request URI.</li>
* <li><code>query</code>: Outputs the request query string</li>
* <li><code>thread</code>: Outputs the name of the current thread (first element
* on the context stack).</li>
* <li><code>host</code>: Outputs the request host header.<li>
* <li><code>rootThrowable</code>: Outputs the root throwable message and
* stacktrace.<li>
* </ul>
*
* @version $Id$
*/
public class CocoonLogFormatter extends ExtensiblePatternFormatter
{
/**
* The constant defining the default stack depth when
* none other is specified.
*/
public static final int DEFAULT_STACK_DEPTH = 8;
protected final static int TYPE_CLASS = MAX_TYPE + 1;
protected final static int TYPE_URI = MAX_TYPE + 2;
protected final static int TYPE_THREAD = MAX_TYPE + 3;
protected final static int TYPE_HOST = MAX_TYPE + 4;
protected final static int TYPE_QUERY = MAX_TYPE + 5;
protected final static int TYPE_ROOTTHROWABLE = MAX_TYPE + 6;
protected final static String TYPE_CLASS_STR = "class";
protected final static String TYPE_CLASS_SHORT_STR = "short";
protected final static String TYPE_URI_STR = "uri";
protected final static String TYPE_THREAD_STR = "thread";
protected final static String TYPE_HOST_STR = "host";
protected final static String TYPE_QUERY_STR = "query";
protected final static String TYPE_ROOTTHROWABLE_STR = "rootThrowable";
private static final String DEFAULT_TIME_PATTERN = "(yyyy-MM-dd) HH:mm.ss:SSS";
private static final FastDateFormat dateFormatter = FastDateFormat.getInstance(DEFAULT_TIME_PATTERN);
/**
* Hack to get the call stack as an array of classes. The
* SecurityManager class provides it as a protected method, so
* change it to public through a new method !
*/
static class CallStack extends SecurityManager {
/**
* Returns the current execution stack as an array of classes.
* The length of the array is the number of methods on the execution
* stack. The element at index 0 is the class of the currently executing
* method, the element at index 1 is the class of that method's caller,
* and so on.
*
* @return current execution stack as an array of classes.
*/
public Class[] get() {
return getClassContext();
}
}
/**
* The class that we will search for in the call stack
* (Avalon logging abstraction)
*/
private final Class logkitClass = LogKitLogger.class;
/**
* The class that we will search for in the call stack
* (LogKit logger)
*/
private final Class loggerClass = Logger.class;
/**
* The SecurityManager implementation which gives us access to
* the stack frame
*/
private CallStack callStack;
public CocoonLogFormatter() {
try {
this.callStack = new CallStack();
} catch (SecurityException e) {
// Ignore security exception
}
}
protected int getTypeIdFor(String type) {
// Search for new patterns defined here, or else delegate
// to the parent class
if (type.equalsIgnoreCase(TYPE_CLASS_STR)) {
return TYPE_CLASS;
} else if (type.equalsIgnoreCase(TYPE_URI_STR)) {
return TYPE_URI;
} else if (type.equalsIgnoreCase(TYPE_THREAD_STR)) {
return TYPE_THREAD;
} else if (type.equalsIgnoreCase(TYPE_HOST_STR)) {
return TYPE_HOST;
} else if (type.equalsIgnoreCase(TYPE_QUERY_STR)) {
return TYPE_QUERY;
} else if (type.equalsIgnoreCase(TYPE_ROOTTHROWABLE_STR)) {
return TYPE_ROOTTHROWABLE;
} else {
return super.getTypeIdFor(type);
}
}
protected String formatPatternRun(LogEvent event, PatternRun run) {
// Format new patterns defined here, or else delegate to
// the parent class
switch (run.m_type) {
case TYPE_CLASS:
return getClass(run.m_format);
case TYPE_URI:
return getURI(event.getContextMap());
case TYPE_THREAD:
return getThread(event.getContextMap());
case TYPE_HOST:
return getHost(event.getContextMap());
case TYPE_QUERY:
return getQueryString(event.getContextMap());
case TYPE_ROOTTHROWABLE:
Throwable thr = event.getThrowable();
Throwable root = ExceptionUtils.getRootCause(thr); // Can be null if no cause
return getStackTrace(root == null ? thr : root, run.m_format);
}
return super.formatPatternRun(event, run);
}
/**
* Finds the class that has called Logger.
*/
private String getClass(String format) {
if (this.callStack != null) {
Class[] stack = this.callStack.get();
// Traverse the call stack in reverse order until we find a Logger
for (int i = stack.length - 1; i >= 0; i--) {
if (this.logkitClass.isAssignableFrom(stack[i]) ||
this.loggerClass.isAssignableFrom(stack[i])) {
// Found: the caller is the previous stack element
String className = stack[i + 1].getName();
// Handle optional format
if (TYPE_CLASS_SHORT_STR.equalsIgnoreCase(format)) {
className = ClassUtils.getShortClassName(className);
}
return className;
}
}
}
// No callStack: can occur when running under SecurityManager, or
// no logger found in call stack: can occur with AsyncLogTarget
// where formatting takes place in a different thread.
return "Unknown-Class";
}
/**
* Find the URI that is being processed.
*/
private String getURI(ContextMap ctxMap) {
// Get URI from the the object model.
if (ctxMap != null) {
final Object context = ctxMap.get("objectModel");
if (context != null && context instanceof Map) {
// Get the request
final Request request = ObjectModelHelper.getRequest((Map) context);
if (request != null) {
return request.getRequestURI();
}
}
}
return "Unknown-URI";
}
/**
* Find request query string
*/
private String getQueryString(ContextMap ctxMap) {
if (ctxMap != null) {
final Object context = ctxMap.get("objectModel");
if (context != null && context instanceof Map) {
// Get the request
final Request request = ObjectModelHelper.getRequest((Map) context);
if (request != null) {
final String queryString = request.getQueryString();
if (queryString != null) {
return "?" + queryString;
}
}
}
}
return "";
}
/**
* Find the host header of the request that is being processed.
*/
private String getHost(ContextMap ctxMap) {
// Get URI from the the object model.
if (ctxMap != null) {
final Object context = ctxMap.get("objectModel");
if (context != null && context instanceof Map) {
// Get the request
final Request request = ObjectModelHelper.getRequest((Map) context);
if (request != null) {
return request.getHeader("host");
}
}
}
return "Unknown-Host";
}
/**
* Find the thread that is logged this event.
*/
private String getThread(ContextMap ctxMap) {
// Get thread name from the context.
if (ctxMap != null) {
final String threadName = (String) ctxMap.get("threadName");
if (threadName != null) {
return threadName;
}
}
return "Unknown-Thread";
}
/**
* Utility method to format stack trace so that CascadingExceptions are
* formatted with all nested exceptions.
*
* <p>FIXME: copied from AvalonFormatter, to be removed if ExtensiblePatternFormatter
* replaces PatternFormatter.</p>
*
* @param throwable the throwable instance
* @param format ancilliary format parameter - allowed to be null
* @return the formatted string
*/
protected String getStackTrace(final Throwable throwable, final String format) {
if (throwable != null) {
LocatedException.ensureCauseChainIsSet(throwable);
return ExceptionUtils.getStackTrace(throwable);
//return ExceptionUtil.printStackTrace(throwable, m_stackDepth);
}
return null;
}
/**
* Utility method to format time.
*
* @param time the time
* @param pattern ancilliary pattern parameter - allowed to be null
* @return the formatted string
*/
protected String getTime(final long time, String pattern) {
if (pattern == null || DEFAULT_TIME_PATTERN.equals(pattern)) {
return dateFormatter.format(time);
}
return FastDateFormat.getInstance(pattern).format(time);
}
}