blob: 6a547c22745fd2b600343797d3cb46eee1856055 [file] [log] [blame]
/*
* Copyright 1999-2004 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 java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.apache.avalon.framework.ExceptionUtil;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.log.ContextMap;
import org.apache.log.LogEvent;
/**
* An extended pattern formatter. New patterns are 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>thread</code> : outputs the name of the current thread (first element
* on the context stack).</li>
* <li><code>uri</code> : outputs the request URI.<li>
* </ul>
*
* @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
* @version CVS $Id: CocoonLogFormatter.java,v 1.3 2004/03/05 13:03:01 bdelacretaz Exp $
*/
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 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 SimpleDateFormat dateFormatter = new SimpleDateFormat("(yyyy-MM-dd) HH:mm.ss:SSS");
/**
* 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 public 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 Class logkitClass = org.apache.avalon.framework.logger.LogKitLogger.class;
/**
* The class that we will search for in the call stack
* (LogKit logger)
*/
private Class loggerClass = org.apache.log.Logger.class;
private CallStack callStack = new CallStack();
//The depth to which stacktraces are printed out
private final int m_stackDepth;
public CocoonLogFormatter()
{
this( DEFAULT_STACK_DEPTH );
}
public CocoonLogFormatter( int stackDepth )
{
m_stackDepth = stackDepth;
}
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
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());
}
return super.formatPatternRun(event, run);
}
/**
* Finds the class that has called Logger.
*/
private String getClass(String format) {
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))
{
int pos = className.lastIndexOf('.');
if (pos >= 0)
className = className.substring(pos + 1);
}
return className;
}
}
// 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) {
String result = "Unknown-URI";
// Get URI from the the object model.
if (ctxMap != null) {
Object context = ctxMap.get("objectModel");
if (context != null && context instanceof Map) {
// Get the request
Request request = ObjectModelHelper.getRequest((Map)context);
if (request != null) {
result = request.getRequestURI();
}
}
}
return result;
}
/**
* Find the host header of the request that is being processed.
*/
private String getHost(ContextMap ctxMap) {
String result = "Unknown-host";
// Get URI from the the object model.
if (ctxMap != null) {
Object context = ctxMap.get("objectModel");
if (context != null && context instanceof Map) {
// Get the request
Request request = ObjectModelHelper.getRequest((Map)context);
if (request != null) {
result = request.getHeader("host");
}
}
}
return result;
}
/**
* Find the thread that is logged this event.
*/
private String getThread(ContextMap ctxMap) {
if (ctxMap != null && ctxMap.get("threadName") != null)
return (String)ctxMap.get("threadName");
else
return "Unknown-thread";
}
/**
* Utility method to format stack trace so that CascadingExceptions are
* formatted with all nested exceptions.
*
* FIXME : copied from AvalonFormatter, to be removed if ExtensiblePatternFormatter
* replaces PatternFormatter.
*
* @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 )
{
return throwable == null ? null : ExceptionUtil.printStackTrace(throwable,m_stackDepth);
}
/**
* Utility method to format time.
*
* @param time the time
* @param format ancilliary format parameter - allowed to be null
* @return the formatted string
*/
protected String getTime( final long time, final String format )
{
return this.dateFormatter.format(new Date());
}
}