/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.CascadingThrowable; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Request; import org.apache.commons.lang.ClassUtils; import org.apache.commons.lang.SystemUtils; import org.apache.log.ContextMap; import org.apache.log.LogEvent; import org.apache.log.Logger; import org.apache.log.format.Formatter; import java.io.StringWriter; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.StringTokenizer; /** * A refactoring of <code>org.apache.log.format.PatternFormatter</code> * and <code>org.apache.cocoon.util.log.CocoonLogFormatter</code> for * producing XML format. * This formater formats the LogEntries according to given input types. Each * log entry is inside a <log-entry> element and each information is * inside an own element. * * <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>. The class name is embeded by a <class> * element.</li> * <li><code>thread</code> : outputs the name of the current thread (first element * on the context stack). The thread name is surrounded by a <thread> * element.</li> * <li><code>uri</code> : outputs the request URI (<uri>).<li> * <li><code>category</code> : outputs the log category (<category>).<li> * <li><code>message</code> : outputs the message (<message>).<li> * <li><code>time</code> : outputs the time (<time>).<li> * <li><code>rtime</code> : outputs the relative time (<relative-time>).<li> * <li><code>throwable</code> : outputs the exception (<throwable>).<li> * <li><code>priority</code> : outputs the priority (<priority>).<li> * <li><code>host</code> : outputs the request host header (<priority>).<li> * </ul> * * @deprecated This class will be removed in 2.2 * @author <a href="mailto:donaldp@apache.org">Peter Donald</a> * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a> * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a> * @version CVS $Id$ */ public class XMLCocoonLogFormatter implements Formatter { protected final static String TYPE_CLASS_STR = "class"; protected final static String TYPE_CLASS_SHORT_STR = "short"; protected final static int TYPE_REQUEST_URI = 0; protected final static int TYPE_CATEGORY = 1; protected final static int TYPE_MESSAGE = 2; protected final static int TYPE_TIME = 3; protected final static int TYPE_RELATIVE_TIME = 4; protected final static int TYPE_THROWABLE = 5; protected final static int TYPE_PRIORITY = 6; protected final static int TYPE_CLASS = 7; protected final static int TYPE_CLASS_SHORT = 8; protected final static int TYPE_THREAD = 9; protected final static int TYPE_HOST = 10; public final static String[] typeStrings = new String[] { "uri", // 0 "category", // 1 "message", // 2 "time", // 3 "rtime", // 4 "throwable", // 5 "priority", // 6 "class", // 7 "class:short", // 8 "thread", // 9 "host"}; // 10 protected final SimpleDateFormat dateFormatter = new SimpleDateFormat("(yyyy-MM-dd) HH:mm.ss:SSS"); protected int[] types; /** * Format the event according to the pattern. * * @param event the event * @return the formatted output */ public String format( final LogEvent event ) { final StringBuffer sb = new StringBuffer(); sb.append("<log-entry>").append(SystemUtils.LINE_SEPARATOR); final String value = this.getRequestId(event.getContextMap()); if (value != null) { sb.append("<request-id>").append(value).append("</request-id>").append(SystemUtils.LINE_SEPARATOR); } for(int i = 0; i < this.types.length; i++) { switch(this.types[i]) { case TYPE_REQUEST_URI: sb.append("<uri>"); sb.append(this.getURI(event.getContextMap())); sb.append("</uri>").append(SystemUtils.LINE_SEPARATOR); break; case TYPE_CLASS: sb.append("<class>"); sb.append(this.getClass(TYPE_CLASS)); sb.append("</class>").append(SystemUtils.LINE_SEPARATOR); break; case TYPE_CLASS_SHORT: sb.append("<class>"); sb.append(this.getClass(TYPE_CLASS_SHORT)); sb.append("</class>").append(SystemUtils.LINE_SEPARATOR); break; case TYPE_THREAD: sb.append("<thread>"); sb.append(this.getThread(event.getContextMap())); sb.append("</thread>").append(SystemUtils.LINE_SEPARATOR); break; case TYPE_RELATIVE_TIME: sb.append("<relative-time>"); sb.append(event.getRelativeTime()); sb.append("</relative-time>").append(SystemUtils.LINE_SEPARATOR); break; case TYPE_TIME: sb.append("<time>"); sb.append(dateFormatter.format(new Date(event.getTime()))); sb.append("</time>").append(SystemUtils.LINE_SEPARATOR); break; case TYPE_THROWABLE: Throwable throwable = event.getThrowable(); if (throwable != null) { sb.append("<throwable><![CDATA[").append(SystemUtils.LINE_SEPARATOR); while (throwable != null) { final StringWriter sw = new StringWriter(); throwable.printStackTrace( new java.io.PrintWriter( sw ) ); sb.append(sw.toString()); if (throwable instanceof CascadingThrowable ) { throwable = ((CascadingThrowable)throwable).getCause(); } else { throwable = null; } } sb.append(SystemUtils.LINE_SEPARATOR).append("]]> </throwable>").append(SystemUtils.LINE_SEPARATOR); } break; case TYPE_MESSAGE: sb.append("<message><![CDATA[").append(SystemUtils.LINE_SEPARATOR); sb.append(event.getMessage()); sb.append(SystemUtils.LINE_SEPARATOR).append("]]> </message>").append(SystemUtils.LINE_SEPARATOR); break; case TYPE_CATEGORY: sb.append("<category>"); sb.append(event.getCategory()); sb.append("</category>").append(SystemUtils.LINE_SEPARATOR); break; case TYPE_PRIORITY: sb.append("<priority>"); sb.append(event.getPriority().getName()); sb.append("</priority>").append(SystemUtils.LINE_SEPARATOR); break; case TYPE_HOST: sb.append("<host>"); sb.append(getHost(event.getContextMap())); sb.append("</host>"); break; } } sb.append("</log-entry>"); sb.append(SystemUtils.LINE_SEPARATOR); return sb.toString(); } /** * 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; } private String getHost(ContextMap ctxMap) { String result = "Unknown-host"; 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 request id that is being processed. */ private String getRequestId(ContextMap ctxMap) { String result = null; // Get URI from the the object model. if (ctxMap != null) { Object context = ctxMap.get("request-id"); if (context != null) { result = context.toString(); } } return result; } /** * Finds the class that has called Logger. */ private String getClass(int 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.loggerClass.isAssignableFrom(stack[i])) { // Found : the caller is the previous stack element String className = stack[i+1].getName(); // Handle optional format if (format == TYPE_CLASS_SHORT) { className = ClassUtils.getShortClassName(className); } 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 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"; } } /** * Retrieve the type-id for a particular string. * * @param type the string * @return the type-id */ protected int getTypeIdFor(final String type) { for (int index = 0; index < typeStrings.length; index++) { if (type.equalsIgnoreCase(typeStrings[index])) { return index; } } throw new IllegalArgumentException( "Unknown Type - " + type ); } /** * Set the types from an array of strings. */ public void setTypes(String[] typeStrings) { if (typeStrings != null) { this.types = new int[typeStrings.length]; for (int i = 0; i < typeStrings.length; i++) { this.types[i] = this.getTypeIdFor(typeStrings[i]); } } else { this.types = new int[0]; } } /** * Set the types from a whitespace separated string */ public void setTypes(String typeString) { if (typeString == null) { this.types = new int[0]; } else { // this is not the best implementation, but it works... StringTokenizer st = new StringTokenizer(typeString); this.types = new int[st.countTokens()]; for (int i = 0; i < this.types.length; i++) { this.types[i] = this.getTypeIdFor(st.nextToken()); } } } /** The class that we will search for in the call stack */ private Class loggerClass = Logger.class; private CallStack callStack = new CallStack(); /** * 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(); } } }