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 org.apache.avalon.framework.CascadingThrowable;
020: import org.apache.cocoon.environment.ObjectModelHelper;
021: import org.apache.cocoon.environment.Request;
022: import org.apache.commons.lang.ClassUtils;
023: import org.apache.commons.lang.SystemUtils;
024: import org.apache.log.ContextMap;
025: import org.apache.log.LogEvent;
026: import org.apache.log.Logger;
027: import org.apache.log.format.Formatter;
028:
029: import java.io.StringWriter;
030: import java.text.SimpleDateFormat;
031: import java.util.Date;
032: import java.util.Map;
033: import java.util.StringTokenizer;
034:
035: /**
036: * A refactoring of <code>org.apache.log.format.PatternFormatter</code>
037: * and <code>org.apache.cocoon.util.log.CocoonLogFormatter</code> for
038: * producing XML format.
039: * This formater formats the LogEntries according to given input types. Each
040: * log entry is inside a <log-entry> element and each information is
041: * inside an own element.
042: *
043: * <ul>
044: * <li><code>class</code> : outputs the name of the class that has logged the
045: * message. The optional <code>short</code> subformat removes the
046: * package name. Warning : this pattern works only if formatting occurs in
047: * the same thread as the call to Logger, i.e. it won't work with
048: * <code>AsyncLogTarget</code>. The class name is embeded by a <class>
049: * element.</li>
050: * <li><code>thread</code> : outputs the name of the current thread (first element
051: * on the context stack). The thread name is surrounded by a <thread>
052: * element.</li>
053: * <li><code>uri</code> : outputs the request URI (<uri>).<li>
054: * <li><code>category</code> : outputs the log category (<category>).<li>
055: * <li><code>message</code> : outputs the message (<message>).<li>
056: * <li><code>time</code> : outputs the time (<time>).<li>
057: * <li><code>rtime</code> : outputs the relative time (<relative-time>).<li>
058: * <li><code>throwable</code> : outputs the exception (<throwable>).<li>
059: * <li><code>priority</code> : outputs the priority (<priority>).<li>
060: * <li><code>host</code> : outputs the request host header (<priority>).<li>
061: * </ul>
062: *
063: * @deprecated This class will be removed in 2.2
064: * @author <a href="mailto:donaldp@apache.org">Peter Donald</a>
065: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
066: * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
067: * @version CVS $Id: XMLCocoonLogFormatter.java 433543 2006-08-22 06:22:54Z crossley $
068: */
069: public class XMLCocoonLogFormatter implements Formatter {
070:
071: protected final static String TYPE_CLASS_STR = "class";
072: protected final static String TYPE_CLASS_SHORT_STR = "short";
073:
074: protected final static int TYPE_REQUEST_URI = 0;
075: protected final static int TYPE_CATEGORY = 1;
076: protected final static int TYPE_MESSAGE = 2;
077: protected final static int TYPE_TIME = 3;
078: protected final static int TYPE_RELATIVE_TIME = 4;
079: protected final static int TYPE_THROWABLE = 5;
080: protected final static int TYPE_PRIORITY = 6;
081: protected final static int TYPE_CLASS = 7;
082: protected final static int TYPE_CLASS_SHORT = 8;
083: protected final static int TYPE_THREAD = 9;
084: protected final static int TYPE_HOST = 10;
085:
086: public final static String[] typeStrings = new String[] { "uri", // 0
087: "category", // 1
088: "message", // 2
089: "time", // 3
090: "rtime", // 4
091: "throwable", // 5
092: "priority", // 6
093: "class", // 7
094: "class:short", // 8
095: "thread", // 9
096: "host" }; // 10
097:
098: protected final SimpleDateFormat dateFormatter = new SimpleDateFormat(
099: "(yyyy-MM-dd) HH:mm.ss:SSS");
100:
101: protected int[] types;
102:
103: /**
104: * Format the event according to the pattern.
105: *
106: * @param event the event
107: * @return the formatted output
108: */
109: public String format(final LogEvent event) {
110: final StringBuffer sb = new StringBuffer();
111: sb.append("<log-entry>").append(SystemUtils.LINE_SEPARATOR);
112: final String value = this .getRequestId(event.getContextMap());
113: if (value != null) {
114: sb.append("<request-id>").append(value).append(
115: "</request-id>").append(SystemUtils.LINE_SEPARATOR);
116: }
117: for (int i = 0; i < this .types.length; i++) {
118: switch (this .types[i]) {
119: case TYPE_REQUEST_URI:
120: sb.append("<uri>");
121: sb.append(this .getURI(event.getContextMap()));
122: sb.append("</uri>").append(SystemUtils.LINE_SEPARATOR);
123: break;
124: case TYPE_CLASS:
125: sb.append("<class>");
126: sb.append(this .getClass(TYPE_CLASS));
127: sb.append("</class>")
128: .append(SystemUtils.LINE_SEPARATOR);
129: break;
130: case TYPE_CLASS_SHORT:
131: sb.append("<class>");
132: sb.append(this .getClass(TYPE_CLASS_SHORT));
133: sb.append("</class>")
134: .append(SystemUtils.LINE_SEPARATOR);
135: break;
136: case TYPE_THREAD:
137: sb.append("<thread>");
138: sb.append(this .getThread(event.getContextMap()));
139: sb.append("</thread>").append(
140: SystemUtils.LINE_SEPARATOR);
141: break;
142: case TYPE_RELATIVE_TIME:
143: sb.append("<relative-time>");
144: sb.append(event.getRelativeTime());
145: sb.append("</relative-time>").append(
146: SystemUtils.LINE_SEPARATOR);
147: break;
148: case TYPE_TIME:
149: sb.append("<time>");
150: sb.append(dateFormatter
151: .format(new Date(event.getTime())));
152: sb.append("</time>").append(SystemUtils.LINE_SEPARATOR);
153: break;
154: case TYPE_THROWABLE:
155: Throwable throwable = event.getThrowable();
156: if (throwable != null) {
157: sb.append("<throwable><![CDATA[").append(
158: SystemUtils.LINE_SEPARATOR);
159: while (throwable != null) {
160: final StringWriter sw = new StringWriter();
161: throwable
162: .printStackTrace(new java.io.PrintWriter(
163: sw));
164: sb.append(sw.toString());
165: if (throwable instanceof CascadingThrowable) {
166: throwable = ((CascadingThrowable) throwable)
167: .getCause();
168: } else {
169: throwable = null;
170: }
171: }
172: sb.append(SystemUtils.LINE_SEPARATOR).append(
173: "]]> </throwable>").append(
174: SystemUtils.LINE_SEPARATOR);
175: }
176: break;
177: case TYPE_MESSAGE:
178: sb.append("<message><![CDATA[").append(
179: SystemUtils.LINE_SEPARATOR);
180: sb.append(event.getMessage());
181: sb.append(SystemUtils.LINE_SEPARATOR).append(
182: "]]> </message>").append(
183: SystemUtils.LINE_SEPARATOR);
184: break;
185: case TYPE_CATEGORY:
186: sb.append("<category>");
187: sb.append(event.getCategory());
188: sb.append("</category>").append(
189: SystemUtils.LINE_SEPARATOR);
190: break;
191: case TYPE_PRIORITY:
192: sb.append("<priority>");
193: sb.append(event.getPriority().getName());
194: sb.append("</priority>").append(
195: SystemUtils.LINE_SEPARATOR);
196: break;
197: case TYPE_HOST:
198: sb.append("<host>");
199: sb.append(getHost(event.getContextMap()));
200: sb.append("</host>");
201: break;
202: }
203: }
204: sb.append("</log-entry>");
205: sb.append(SystemUtils.LINE_SEPARATOR);
206: return sb.toString();
207: }
208:
209: /**
210: * Find the URI that is being processed.
211: */
212: private String getURI(ContextMap ctxMap) {
213: String result = "Unknown-URI";
214:
215: // Get URI from the the object model.
216: if (ctxMap != null) {
217: Object context = ctxMap.get("objectModel");
218: if (context != null && context instanceof Map) {
219: // Get the request
220: Request request = ObjectModelHelper
221: .getRequest((Map) context);
222: if (request != null) {
223: result = request.getRequestURI();
224: }
225: }
226: }
227: return result;
228: }
229:
230: private String getHost(ContextMap ctxMap) {
231: String result = "Unknown-host";
232:
233: if (ctxMap != null) {
234: Object context = ctxMap.get("objectModel");
235: if (context != null && context instanceof Map) {
236: // Get the request
237: Request request = ObjectModelHelper
238: .getRequest((Map) context);
239: if (request != null) {
240: result = request.getHeader("host");
241: }
242: }
243: }
244: return result;
245: }
246:
247: /**
248: * Find the request id that is being processed.
249: */
250: private String getRequestId(ContextMap ctxMap) {
251: String result = null;
252:
253: // Get URI from the the object model.
254: if (ctxMap != null) {
255: Object context = ctxMap.get("request-id");
256: if (context != null) {
257: result = context.toString();
258: }
259: }
260: return result;
261: }
262:
263: /**
264: * Finds the class that has called Logger.
265: */
266: private String getClass(int format) {
267:
268: Class[] stack = this .callStack.get();
269:
270: // Traverse the call stack in reverse order until we find a Logger
271: for (int i = stack.length - 1; i >= 0; i--) {
272: if (this .loggerClass.isAssignableFrom(stack[i])) {
273:
274: // Found : the caller is the previous stack element
275: String className = stack[i + 1].getName();
276:
277: // Handle optional format
278: if (format == TYPE_CLASS_SHORT) {
279: className = ClassUtils.getShortClassName(className);
280: }
281: return className;
282: }
283: }
284:
285: // No Logger found in call stack : can occur with AsyncLogTarget
286: // where formatting takes place in a different thread.
287: return "Unknown-class";
288: }
289:
290: /**
291: * Find the thread that is logged this event.
292: */
293: private String getThread(ContextMap ctxMap) {
294: if (ctxMap != null && ctxMap.get("threadName") != null) {
295: return (String) ctxMap.get("threadName");
296: } else {
297: return "Unknown-thread";
298: }
299: }
300:
301: /**
302: * Retrieve the type-id for a particular string.
303: *
304: * @param type the string
305: * @return the type-id
306: */
307: protected int getTypeIdFor(final String type) {
308: for (int index = 0; index < typeStrings.length; index++) {
309: if (type.equalsIgnoreCase(typeStrings[index])) {
310: return index;
311: }
312: }
313: throw new IllegalArgumentException("Unknown Type - " + type);
314: }
315:
316: /**
317: * Set the types from an array of strings.
318: */
319: public void setTypes(String[] typeStrings) {
320: if (typeStrings != null) {
321: this .types = new int[typeStrings.length];
322: for (int i = 0; i < typeStrings.length; i++) {
323: this .types[i] = this .getTypeIdFor(typeStrings[i]);
324: }
325: } else {
326: this .types = new int[0];
327: }
328: }
329:
330: /**
331: * Set the types from a whitespace separated string
332: */
333: public void setTypes(String typeString) {
334: if (typeString == null) {
335: this .types = new int[0];
336: } else {
337: // this is not the best implementation, but it works...
338: StringTokenizer st = new StringTokenizer(typeString);
339: this .types = new int[st.countTokens()];
340: for (int i = 0; i < this .types.length; i++) {
341: this .types[i] = this .getTypeIdFor(st.nextToken());
342: }
343: }
344: }
345:
346: /** The class that we will search for in the call stack */
347: private Class loggerClass = Logger.class;
348: private CallStack callStack = new CallStack();
349:
350: /**
351: * Hack to get the call stack as an array of classes. The
352: * SecurityManager class provides it as a protected method, so
353: * change it to public through a new method !
354: */
355: static public class CallStack extends SecurityManager {
356: /**
357: * Returns the current execution stack as an array of classes.
358: * The length of the array is the number of methods on the execution
359: * stack. The element at index 0 is the class of the currently executing
360: * method, the element at index 1 is the class of that method's caller,
361: * and so on.
362: *
363: * @return current execution stack as an array of classes.
364: */
365: public Class[] get() {
366: return getClassContext();
367: }
368: }
369: }
|